github.com/galdor/go-ejson@v0.0.0-20231201100034-d335379f26b0/validator.go (about)

     1  package ejson
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"net/url"
     7  	"reflect"
     8  	"regexp"
     9  	"strconv"
    10  	"unicode/utf8"
    11  
    12  	"github.com/galdor/go-uuid"
    13  )
    14  
    15  type ValidationError struct {
    16  	Pointer Pointer `json:"pointer"`
    17  	Code    string  `json:"code"`
    18  	Message string  `json:"message"`
    19  }
    20  
    21  type ValidationErrors []*ValidationError
    22  
    23  type Validator struct {
    24  	Pointer Pointer
    25  	Errors  ValidationErrors
    26  }
    27  
    28  type Validatable interface {
    29  	ValidateJSON(v *Validator)
    30  }
    31  
    32  func (err ValidationError) Error() string {
    33  	if len(err.Pointer) == 0 {
    34  		return err.Message
    35  	} else {
    36  		return fmt.Sprintf("%v: %s", err.Pointer, err.Message)
    37  	}
    38  }
    39  
    40  func (errs ValidationErrors) Error() string {
    41  	var buf bytes.Buffer
    42  
    43  	buf.WriteString("invalid data")
    44  
    45  	if len(errs) > 0 {
    46  		buf.WriteByte(':')
    47  	}
    48  
    49  	for _, err := range errs {
    50  		buf.WriteString("\n  ")
    51  		buf.WriteString(err.Error())
    52  	}
    53  
    54  	return buf.String()
    55  }
    56  
    57  func Validate(value interface{}) error {
    58  	v := NewValidator()
    59  
    60  	if validatableValue, ok := value.(Validatable); ok {
    61  		validatableValue.ValidateJSON(v)
    62  	}
    63  
    64  	if len(v.Errors) > 0 {
    65  		return v.Error()
    66  	}
    67  
    68  	return nil
    69  }
    70  
    71  func NewValidator() *Validator {
    72  	return &Validator{}
    73  }
    74  
    75  func (v *Validator) Error() error {
    76  	if len(v.Errors) == 0 {
    77  		return nil
    78  	}
    79  
    80  	return v.Errors
    81  }
    82  
    83  func (v *Validator) Push(token interface{}) {
    84  	v.Pointer = v.Pointer.Child(token)
    85  }
    86  
    87  func (v *Validator) Pop() {
    88  	v.Pointer = v.Pointer.Parent()
    89  }
    90  
    91  func (v *Validator) WithChild(token interface{}, fn func()) {
    92  	v.Push(token)
    93  	defer v.Pop()
    94  
    95  	fn()
    96  }
    97  
    98  func (v *Validator) AddError(token interface{}, code, format string, args ...interface{}) {
    99  	pointer := v.Pointer.Child(token)
   100  
   101  	err := ValidationError{
   102  		Pointer: pointer,
   103  		Code:    code,
   104  		Message: fmt.Sprintf(format, args...),
   105  	}
   106  
   107  	v.Errors = append(v.Errors, &err)
   108  }
   109  
   110  func (v *Validator) Check(token interface{}, value bool, code, format string, args ...interface{}) bool {
   111  	if !value {
   112  		v.AddError(token, code, format, args...)
   113  	}
   114  
   115  	return value
   116  }
   117  
   118  func (v *Validator) CheckIntMin(token interface{}, i int, min int) bool {
   119  	return v.Check(token, i >= min, "integer_too_small",
   120  		"integer must be greater or equal to %d", min)
   121  }
   122  
   123  func (v *Validator) CheckIntMax(token interface{}, i int, max int) bool {
   124  	return v.Check(token, i <= max, "integer_too_large",
   125  		"integer must be lower or equal to %d", max)
   126  }
   127  
   128  func (v *Validator) CheckIntMinMax(token interface{}, i int, min, max int) bool {
   129  	if !v.CheckIntMin(token, i, min) {
   130  		return false
   131  	}
   132  
   133  	return v.CheckIntMax(token, i, max)
   134  }
   135  
   136  func (v *Validator) CheckInt64Min(token interface{}, i, min int64) bool {
   137  	return v.Check(token, i >= min, "integer_too_small",
   138  		"integer must be greater or equal to %d", min)
   139  }
   140  
   141  func (v *Validator) CheckInt64Max(token interface{}, i, max int64) bool {
   142  	return v.Check(token, i <= max, "integer_too_large",
   143  		"integer must be lower or equal to %d", max)
   144  }
   145  
   146  func (v *Validator) CheckInt64MinMax(token interface{}, i, min, max int64) bool {
   147  	if !v.CheckInt64Min(token, i, min) {
   148  		return false
   149  	}
   150  
   151  	return v.CheckInt64Max(token, i, max)
   152  }
   153  
   154  func (v *Validator) CheckFloatMin(token interface{}, i, min float64) bool {
   155  	return v.Check(token, i >= min, "float_too_small",
   156  		"float %f must be greater or equal to %f", i, min)
   157  }
   158  
   159  func (v *Validator) CheckFloatMax(token interface{}, i, max float64) bool {
   160  	return v.Check(token, i <= max, "float_too_large",
   161  		"float %f must be lower or equal to %f", i, max)
   162  }
   163  
   164  func (v *Validator) CheckFloatMinMax(token interface{}, i, min, max float64) bool {
   165  	if !v.CheckFloatMin(token, i, min) {
   166  		return false
   167  	}
   168  
   169  	return v.CheckFloatMax(token, i, max)
   170  }
   171  
   172  func (v *Validator) CheckStringLengthMin(token interface{}, s string, min int) bool {
   173  	length := utf8.RuneCountInString(s)
   174  	return v.Check(token, length >= min, "string_too_short",
   175  		"string length must be greater or equal to %d", min)
   176  }
   177  
   178  func (v *Validator) CheckStringLengthMax(token interface{}, s string, max int) bool {
   179  	length := utf8.RuneCountInString(s)
   180  	return v.Check(token, length <= max, "string_too_long",
   181  		"string length must be lower or equal to %d", max)
   182  }
   183  
   184  func (v *Validator) CheckStringLengthMinMax(token interface{}, s string, min, max int) bool {
   185  	if !v.CheckStringLengthMin(token, s, min) {
   186  		return false
   187  	}
   188  
   189  	return v.CheckStringLengthMax(token, s, max)
   190  }
   191  
   192  func (v *Validator) CheckStringNotEmpty(token interface{}, s string) bool {
   193  	return v.Check(token, s != "", "missing_or_empty_string",
   194  		"missing or empty string")
   195  }
   196  
   197  func (v *Validator) CheckStringValue(token interface{}, value interface{}, values interface{}) bool {
   198  	valueType := reflect.TypeOf(value)
   199  	if valueType.Kind() != reflect.String {
   200  		panic(fmt.Sprintf("value %#v (%T) is not a string", value, value))
   201  	}
   202  
   203  	s := reflect.ValueOf(value).String()
   204  
   205  	valuesType := reflect.TypeOf(values)
   206  	if valuesType.Kind() != reflect.Slice {
   207  		panic(fmt.Sprintf("values %#v (%T) are not a slice", values, values))
   208  	}
   209  	if valuesType.Elem().Kind() != reflect.String {
   210  		panic(fmt.Sprintf("values %#v (%T) are not a slice of strings",
   211  			values, values))
   212  	}
   213  
   214  	valuesValue := reflect.ValueOf(values)
   215  
   216  	found := false
   217  	for i := 0; i < valuesValue.Len(); i++ {
   218  		s2 := valuesValue.Index(i).String()
   219  		if s == s2 {
   220  			found = true
   221  		}
   222  	}
   223  
   224  	if !found {
   225  		var buf bytes.Buffer
   226  
   227  		buf.WriteString("value must be one of the following strings: ")
   228  
   229  		for i := 0; i < valuesValue.Len(); i++ {
   230  			if i > 0 {
   231  				buf.WriteString(", ")
   232  			}
   233  
   234  			s2 := valuesValue.Index(i).String()
   235  			buf.WriteString(s2)
   236  		}
   237  
   238  		v.AddError(token, "invalid_value", "%s", buf.String())
   239  	}
   240  
   241  	return found
   242  }
   243  
   244  func (v *Validator) CheckStringMatch(token interface{}, s string, re *regexp.Regexp) bool {
   245  	return v.CheckStringMatch2(token, s, re, "invalid_string_format",
   246  		"string must match the following regular expression: %s",
   247  		re.String())
   248  }
   249  
   250  func (v *Validator) CheckStringMatch2(token interface{}, s string, re *regexp.Regexp, code, format string, args ...interface{}) bool {
   251  	if !re.MatchString(s) {
   252  		v.AddError(token, code, format, args...)
   253  		return false
   254  	}
   255  
   256  	return true
   257  }
   258  
   259  func (v *Validator) CheckStringURI(token interface{}, s string) bool {
   260  	// The url.Parse function parses URI references. Most of the time we are
   261  	// interested in URIs, so we check that there is a schema.
   262  
   263  	uri, err := url.Parse(s)
   264  	if err != nil {
   265  		v.AddError(token, "invalid_uri_format", "string must be a valid uri")
   266  		return false
   267  	}
   268  
   269  	if uri.Scheme == "" {
   270  		v.AddError(token, "missing_uri_scheme", "uri must have a scheme")
   271  		return false
   272  	}
   273  
   274  	return true
   275  }
   276  
   277  func (v *Validator) CheckUUID(token interface{}, value string) bool {
   278  	var id uuid.UUID
   279  
   280  	if !v.CheckStringNotEmpty(token, value) {
   281  		return false
   282  	}
   283  
   284  	ok := v.Check(token, id.Parse(value) == nil, "invalid_uuid",
   285  		"string must be a valid uuid")
   286  	if !ok {
   287  		return false
   288  	}
   289  
   290  	return v.Check(token, !id.Equal(uuid.Nil), "invalid_uuid",
   291  		"string must not be a null uuid")
   292  }
   293  
   294  func (v *Validator) CheckArrayLengthMin(token interface{}, value interface{}, min int) bool {
   295  	var length int
   296  
   297  	checkArray(value, &length)
   298  
   299  	return v.Check(token, length >= min, "array_too_small",
   300  		"array must contain %d or more elements", min)
   301  }
   302  
   303  func (v *Validator) CheckArrayLengthMax(token interface{}, value interface{}, max int) bool {
   304  	var length int
   305  
   306  	checkArray(value, &length)
   307  
   308  	return v.Check(token, length <= max, "array_too_large",
   309  		"array must contain %d or less elements", max)
   310  }
   311  
   312  func (v *Validator) CheckArrayLengthMinMax(token interface{}, value interface{}, min, max int) bool {
   313  	if !v.CheckArrayLengthMin(token, value, min) {
   314  		return false
   315  	}
   316  
   317  	return v.CheckArrayLengthMax(token, value, max)
   318  }
   319  
   320  func (v *Validator) CheckArrayNotEmpty(token interface{}, value interface{}) bool {
   321  	var length int
   322  
   323  	checkArray(value, &length)
   324  
   325  	return v.Check(token, length > 0, "empty_array", "array must not be empty")
   326  }
   327  
   328  func checkArray(value interface{}, plen *int) {
   329  	valueType := reflect.TypeOf(value)
   330  
   331  	switch valueType.Kind() {
   332  	case reflect.Slice:
   333  		*plen = reflect.ValueOf(value).Len()
   334  
   335  	case reflect.Array:
   336  		*plen = valueType.Len()
   337  
   338  	default:
   339  		panic(fmt.Sprintf("value is not a slice or array"))
   340  	}
   341  }
   342  
   343  func (v *Validator) CheckOptionalObject(token interface{}, value interface{}) bool {
   344  	if !checkObject(value) {
   345  		return true
   346  	}
   347  
   348  	return v.doCheckObject(token, value)
   349  }
   350  
   351  func (v *Validator) CheckObject(token interface{}, value interface{}) bool {
   352  	if !checkObject(value) {
   353  		v.AddError(token, "missing_value", "missing value")
   354  		return false
   355  	}
   356  
   357  	return v.doCheckObject(token, value)
   358  }
   359  
   360  func (v *Validator) CheckObjectArray(token interface{}, value interface{}) bool {
   361  	valueType := reflect.TypeOf(value)
   362  	kind := valueType.Kind()
   363  
   364  	if kind != reflect.Array && kind != reflect.Slice {
   365  		panic(fmt.Sprintf("value %#v (%T) is not an array or slice",
   366  			value, value))
   367  	}
   368  
   369  	ok := true
   370  
   371  	v.WithChild(token, func() {
   372  		values := reflect.ValueOf(value)
   373  
   374  		for i := 0; i < values.Len(); i++ {
   375  			child := values.Index(i).Interface()
   376  			childOk := v.CheckObject(strconv.Itoa(i), child)
   377  			ok = ok && childOk
   378  		}
   379  	})
   380  
   381  	return ok
   382  }
   383  
   384  func (v *Validator) CheckObjectMap(token interface{}, value interface{}) bool {
   385  	valueType := reflect.TypeOf(value)
   386  	if valueType.Kind() != reflect.Map {
   387  		panic(fmt.Sprintf("value %#v (%T) is not a map", value, value))
   388  	}
   389  
   390  	ok := true
   391  
   392  	v.WithChild(token, func() {
   393  		values := reflect.ValueOf(value)
   394  
   395  		iter := values.MapRange()
   396  		for iter.Next() {
   397  			key := iter.Key()
   398  			if key.Kind() != reflect.String {
   399  				panic(fmt.Sprintf("value %#v (%T) is a map whose keys are "+
   400  					"not strings", value, value))
   401  			}
   402  			keyString := key.Interface().(string)
   403  
   404  			value := iter.Value().Interface()
   405  
   406  			valueOk := v.CheckObject(keyString, value)
   407  			ok = ok && valueOk
   408  		}
   409  	})
   410  
   411  	return ok
   412  }
   413  
   414  func (v *Validator) doCheckObject(token interface{}, value interface{}) bool {
   415  	nbErrors := len(v.Errors)
   416  
   417  	value2, ok := value.(Validatable)
   418  	if !ok {
   419  		return true
   420  	}
   421  
   422  	v.Push(token)
   423  	value2.ValidateJSON(v)
   424  	v.Pop()
   425  
   426  	return len(v.Errors) == nbErrors
   427  }
   428  
   429  func checkObject(value interface{}) bool {
   430  	valueType := reflect.TypeOf(value)
   431  	if valueType == nil {
   432  		return false
   433  	}
   434  
   435  	if valueType.Kind() != reflect.Pointer {
   436  		panic(fmt.Sprintf("value %#v (%T) is not a pointer", value, value))
   437  	}
   438  
   439  	pointedValueType := valueType.Elem()
   440  	if pointedValueType.Kind() != reflect.Struct {
   441  		panic(fmt.Sprintf("value %#v (%T) is not a pointer to a structure",
   442  			value, value))
   443  	}
   444  
   445  	return !reflect.ValueOf(value).IsZero()
   446  }