go-playground / validator

:100:Go Struct and Field validation, including Cross Field, Cross Struct, Map, Slice and Array diving
MIT License
16.64k stars 1.31k forks source link

Expose baked in validators for use with custom validators #845

Open AaronRobson opened 3 years ago

AaronRobson commented 3 years ago

Package version eg. v9, v10:

v10

Issue, Question or Enhancement:

Enhancement - to allow custom validators to use the baked in ones as building blocks (https://github.com/go-playground/validator/blob/d4271985b44b735c6f76abc7a06532ee997f9476/baked_in.go#L477)

If the isUuid etc. functions started with a capital letter (i.e. IsUuid etc.) they'd be available to users of the library.

Code sample, to showcase or reproduce:

package main

import (
    "strings"

    "github.com/go-playground/validator/v10"
)

type Example struct {
    Value string `json:"value" validate:"required"`
}

// use a single instance of Validate, it caches struct info
var validate *validator.Validate

func main() {
    validate = validator.New()
    validate.RegisterValidation("uuidwithprefix", validateUuidWithPrefix)

    s := Example{Value: "invalid|74a740c3-ef1d-4333-9f28-930ed602590d"}
    err := validate.Struct(s)
    if err != nil {
        fmt.Printf("Err(s):\n%+v\n", err)
    }

    s.String = "expected-prefix|74a740c3-ef1d-4333-9f28-930ed602590d"
    err = validate.Struct(s)
    if err != nil {
        fmt.Printf("Err(s):\n%+v\n", err)
    }
}

func validateUuidWithPrefix(fl validator.FieldLevel) bool {
    parts := string.Split(fl.Field().String(), "|")
    if len(parts) != 2 {
        return false
    }
    if parts[0] != "expected-prefix" {
        return false
    }
    return validators.isUuid(parts[1])  <- This fails as isUuid is not exposed by the library
}

Modelled after: https://github.com/go-playground/validator/blob/master/_examples/custom-validation/main.go

deankarn commented 2 years ago

Hey @AaronRobson this is not the first time I've gotten this requisition for various reasons. I can see how having these functions exposed could be beneficial.

My main concern is that it would dramatically increase the surface area of this library.

WDYT if I created a separate repo with these raw exposed validations and then changed this lib to use it? then can get the best of both worlds?

AaronRobson commented 2 years ago

Sounds like a good solution to me. Thank you for the consideration.

AdrianCio commented 1 year ago

It's unclear to me, what was the resolution of this? I bumped into the same situation.

I have a string of comma separated country codes that is omitempty but I also want to be able to validate it as a 2 letter country code list with iso3166_1_alpha2. I cannot just put the baked in validator because the comma separated list is not seen as an array for dive.

safareli commented 9 months ago

My main concern is that it would dramatically increase the surface area of this library.

This functions are not exposed as go functions but if implementation of for example isUuid was changed in a braking way, it will be braking change for the lib as well. So this means to me that validations are already part of api surface of this library.

zzJinux commented 6 months ago

@deankarn We can use specific baked-in tags to trigger specific baked-in validation functions associated with the tags. How validations work is already part of the API.

By exposing baked-in functions, what's increased in the surface area is that users can now use validations not only on struct fields but also on everything they are concerned about. I think that the following are all safe to be exposed:

  1. The tag aaaa calls the function IsAAAA.
  2. The function IsAAAA has a specific signature (namely, func (FieldLevel) bool)
esnunes commented 5 months ago

I have a similar use case for exposing baked in validators, I would like to extend them to cover additional types, e.g. github.com/shopspring/decimal.Decimal.

In the meanwhile I have been using go's go:linkname compile directive to have access to the private functions.

import (
    _ "unsafe"

    "github.com/go-playground/validator/v10"
)

//go:linkname hasMinOf github.com/go-playground/validator/v10.hasMinOf
func hasMinOf(fl validator.FieldLevel) bool

func MinOf(fl validator.FieldLevel) bool {
    switch v := fl.Field().Interface().(type) {
    case decimal.Decimal:
        param, err := decimal.NewFromString(fl.Param())
        if err != nil {
            return false
        }
        return v.GreaterThan(param)

    default:
        return hasMinOf(fl)
    }
}

// validatorInstance.RegisterValidation("min", MinOf)