jellydator / validation

An idiomatic validation package. Supports configurable and extensible validation rules (validators) using normal language constructs instead of error-prone struct tags.
MIT License
39 stars 6 forks source link

Add function to get the value of the json tag for a field in a struct #5

Closed slessard closed 1 year ago

slessard commented 1 year ago

This enables the use-case where a mutual exclusion validation rule needs to know the name of a field other than the field currently being validated

swithek commented 1 year ago

This function doesn't do any validation. Could you give an example of its use case?

slessard commented 1 year ago

This validation library is quite useful and powerful but is designed around evaluating a single field at a time. You are correct that this function doesn't do any validation, but it enables, in part, a validation use case which is mutual exclusion where validation requires evaluating two fields rather than the usual one field. It is for this reason I wrote the FindStructFieldJSONName function. Let me show you how it works. BTW you can try it for yourself in the Go Playground at https://go.dev/play/p/1uf-XFD_2rL

Here's the code that builds a new validation error instance specifically for use with mutual exclusion validation. NOTE: this function is only concerned with the "other" field

func newValidationErrorMutualExclusionWithField( //nolint:ireturn // the concrete type is not known
    otherFieldJSONTag string,
) validation.Error {
    newError := validation.NewError("validation_mutually_exclusive", "must be mutually exclusive with {{.otherField}}")
    newError = newError.SetParams(map[string]interface{}{"otherField": otherFieldJSONTag})
    return newError
}

Mutual exclusivity validation requires evaluation of two (or more) fields.

// Get the JSON tag for the "other" field
jsonTagGeoLocation, err2 := FindStructFieldJSONName(&request, &request.GeoLocation)
if err2 != nil {
    panic("GeoLocation field not found in struct")
}

// Use the JSON tag of the "other" field to build an instance of the error to be used
// in case mutual exclusion validation fails
errorMutualExclusionWithGeoLocation := newValidationErrorMutualExclusionWithField(jsonTagGeoLocation)

// Build the validation rule for "this" field, passing in the error created above
// How this rule works... When "this" field has a value the "other" field must be empty
rulesRegions := []validation.Rule{
    validation.When(
        strings.TrimSpace(request.GeoLocation) != "",
        validation.Empty.ErrorObject(errorMutualExclusionWithGeoLocation)),
}

// Now validate "this" field for mutual exclusivity with the "other" field
err := validation.ValidateStruct(&request,
    validation.Field(&request.Regions, rulesRegions...),
)

fmt.Printf("err = %v", err)

The end result is this output

err = regions: must be mutually exclusive with field geo_location.
slessard commented 1 year ago

Everything should be fixed now