rus0en
rus0en

Reputation: 11

Run different validation depending on the HTTP Method of the request [Go Gin]

So I'm currently developing a simple API using Go and the Gin library and wanted to have some validation of the data that I receive from the client before further checking. I have come across the usage of tags and this is a clear example of what I have so far:

type Model struct {
    ID           primitive.ObjectID `bson:"_id,omitempty"`
    Name         string             `json:"name" bson:"name" binding:"required"`
    ProductId    int                `json:"productId" bson:"productId" binding:"required,min=1"`
    Brand        string             `json:"brand" bson:"brand" binding:"required"`
    Model        string             `json:"model" bson:"model" binding:"required"`
    Weight       string             `json:"weight" bson:"weight"`
    Observations string             `json:"observations" bson:"observations"`
}

Thanks to the binding tags I can check that these fields are included in the body when I get a POST request. Now my question is... What happens if, for example, I don't want to require all of these fields for a PATCH request? The user may want to update only the observations or only the Name without having to provide all of the rest of the things since they're already saved in the database.

What is the best approach here? Write a custom validator that uses the methods of the HTTP request? Create different structs to use in different HTTP methods?

Thank you so much for your help.

Upvotes: 1

Views: 760

Answers (3)

Wiktor
Wiktor

Reputation: 775

Personally, I'd most likely go for different structs - create a base model with fields persistent in every type of request, then inherit it in specific structs for GET/PATCH etc.

Upvotes: 0

Prem
Prem

Reputation: 1

You may use custom struct tags instead of "binding" and have a validator function that will check for those tags.

type Model struct {
    ID           primitive.ObjectID `bson:"_id,omitempty"`
    Name         string             `json:"name" bson:"name" post:"required"`
    ProductId    int                `json:"productId" bson:"productId" post:"required,min=1"`
    Brand        string             `json:"brand" bson:"brand" post:"required"`
    Model        string             `json:"model" bson:"model" post:"required"`
    Weight       string             `json:"weight" bson:"weight"`
    Observations string             `json:"observations" bson:"observations"`
}

func validateStruct(data interface{}) error {
    value := reflect.ValueOf(data)
    if value.Kind() != reflect.Struct {
        return fmt.Errorf("validateStruct: expected a struct, got %T", data)
    }
    
    for i := 0; i < value.NumField(); i++ {
        field := value.Type().Field(i)
        tag := field.Tag.Get("post")
        // fmt.Println("Binding", field.Tag.Get("binding"))
        if tag != "" {
            rules := strings.Split(tag, ",")
            for _, rule := range rules {
                // fmt.Println(rule)
                if rule == "required" {
                    fieldValue := value.Field(i).Interface()
                    zero := reflect.Zero(field.Type).Interface()

                    if reflect.DeepEqual(fieldValue, zero) {
                        return fmt.Errorf("%s is required", field.Name)
                    }
                } 
            }
        }
    }

    return nil
}

Upvotes: 0

Charlie Moog
Charlie Moog

Reputation: 21

I think best pattern here is to decouple the HTTP interface from the underlying data model. So in this case, you should have separate structs for each route/method combination, and marshal those structs into a common model type after validation.

Upvotes: 1

Related Questions