changes in preparation for package rename to validator

update code for more idiomatic code
pull/28/head
Dean Karn 10 years ago
parent 47a6634ff8
commit 7ac98be692
  1. 13
      README.md
  2. 2
      baked_in.go
  3. 46
      doc.go
  4. 122
      validator.go
  5. 560
      validator_test.go

@ -1,29 +1,30 @@
Package go-validate-yourself Package go-validate-yourself
================ ================
[![Build Status](https://travis-ci.org/bluesuncorp/go-validate-yourself.svg?branch=v4)](https://travis-ci.org/bluesuncorp/go-validate-yourself) [![Build Status](https://travis-ci.org/bluesuncorp/go-validate-yourself.svg?branch=v5)](https://travis-ci.org/bluesuncorp/go-validate-yourself)
[![GoDoc](https://godoc.org/gopkg.in/bluesuncorp/go-validate-yourself.v4?status.svg)](https://godoc.org/gopkg.in/bluesuncorp/go-validate-yourself.v4) [![GoDoc](https://godoc.org/gopkg.in/bluesuncorp/go-validate-yourself.v5?status.svg)](https://godoc.org/gopkg.in/bluesuncorp/go-validate-yourself.v5)
Package validator implements value validations for structs and individual fields based on tags. Package validator implements value validations for structs and individual fields based on tags.
It is even capable of Cross Field and even Cross Field Cross Struct validation.
Installation Installation
============ ============
Just use go get. Just use go get.
go get gopkg.in/bluesuncorp/go-validate-yourself.v4 go get gopkg.in/bluesuncorp/go-validate-yourself.v5
or to update or to update
go get -u gopkg.in/bluesuncorp/go-validate-yourself.v4 go get -u gopkg.in/bluesuncorp/go-validate-yourself.v5
And then just import the package into your own code. And then just import the package into your own code.
import "gopkg.in/bluesuncorp/go-validate-yourself.v4" import "gopkg.in/bluesuncorp/go-validate-yourself.v5"
Usage Usage
===== =====
Please see http://godoc.org/gopkg.in/bluesuncorp/go-validate-yourself.v4 for detailed usage docs. Please see http://godoc.org/gopkg.in/bluesuncorp/go-validate-yourself.v5 for detailed usage docs.
Contributing Contributing
============ ============

@ -11,7 +11,7 @@ import (
// BakedInValidators is the default map of ValidationFunc // BakedInValidators is the default map of ValidationFunc
// you can add, remove or even replace items to suite your needs, // you can add, remove or even replace items to suite your needs,
// or even disregard and use your own map if so desired. // or even disregard and use your own map if so desired.
var BakedInValidators = map[string]ValidationFunc{ var BakedInValidators = map[string]Func{
"required": hasValue, "required": hasValue,
"len": hasLengthOf, "len": hasLengthOf,
"min": hasMinOf, "min": hasMinOf,

@ -1,12 +1,12 @@
/* /*
Package validator implements value validations for structs and individual fields based on tags. It can also handle Cross Field validation and even Cross Field Cross Struct validation for nested structs. Package validator implements value validations for structs and individual fields based on tags. It can also handle Cross Field validation and even Cross Field Cross Struct validation for nested structs.
Built In Validator Validate
myValidator = validator.NewValidator("validate", validator.BakedInValidators) validate := validator.New("validate", validator.BakedInValidators)
errs := myValidator.ValidateStruct(//your struct) errs := validate.Struct(//your struct)
valErr := myValidator.ValidateFieldByTag(field, "omitempty,min=1,max=10") valErr := validate.Field(field, "omitempty,min=1,max=10")
A simple example usage: A simple example usage:
@ -25,17 +25,17 @@ A simple example usage:
} }
// errs will contain a hierarchical list of errors // errs will contain a hierarchical list of errors
// using the StructValidationErrors struct // using the StructErrors struct
// or nil if no errors exist // or nil if no errors exist
errs := myValidator.ValidateStruct(user) errs := validate.Struct(user)
// in this case 1 error Name is required // in this case 1 error Name is required
errs.Struct will be "User" errs.Struct will be "User"
errs.StructErrors will be empty <-- fields that were structs errs.StructErrors will be empty <-- fields that were structs
errs.Errors will have 1 error of type FieldValidationError errs.Errors will have 1 error of type FieldError
NOTE: Anonymous Structs - they don't have names so expect the Struct name NOTE: Anonymous Structs - they don't have names so expect the Struct name
within StructValidationErrors to be blank. within StructErrors to be blank.
Error Handling Error Handling
@ -45,7 +45,7 @@ The error can be used like so
fieldErr.Field // "Name" fieldErr.Field // "Name"
fieldErr.ErrorTag // "required" fieldErr.ErrorTag // "required"
Both StructValidationErrors and FieldValidationError implement the Error interface but it's Both StructErrors and FieldError implement the Error interface but it's
intended use is for development + debugging, not a production error message. intended use is for development + debugging, not a production error message.
fieldErr.Error() // Field validation for "Name" failed on the "required" tag fieldErr.Error() // Field validation for "Name" failed on the "required" tag
@ -67,7 +67,7 @@ I needed to know the field and what validation failed so that I could provide an
} }
The hierarchical error structure is hard to work with sometimes.. Agreed Flatten function to the rescue! The hierarchical error structure is hard to work with sometimes.. Agreed Flatten function to the rescue!
Flatten will return a map of FieldValidationError's but the field name will be namespaced. Flatten will return a map of FieldError's but the field name will be namespaced.
// if UserDetail Details field failed validation // if UserDetail Details field failed validation
Field will be "Sub.Details" Field will be "Sub.Details"
@ -89,7 +89,7 @@ Custom functions can be added
return true return true
} }
myValidator.AddFunction("custom tag name", customFunc) validate.AddFunction("custom tag name", customFunc)
// NOTES: using the same tag name as an existing function // NOTES: using the same tag name as an existing function
// will overwrite the existing one // will overwrite the existing one
@ -97,11 +97,11 @@ Cross Field Validation
Cross Field Validation can be implemented, for example Start & End Date range validation Cross Field Validation can be implemented, for example Start & End Date range validation
// NOTE: when calling myValidator.validateStruct(val) val will be the top level struct passed // NOTE: when calling validate.Struct(val) val will be the top level struct passed
// into the function // into the function
// when calling myValidator.ValidateFieldByTagAndValue(val, field, tag) val will be // when calling validate.FieldWithValue(val, field, tag) val will be
// whatever you pass, struct, field... // whatever you pass, struct, field...
// when calling myValidator.ValidateFieldByTag(field, tag) val will be nil // when calling validate.Field(field, tag) val will be nil
// //
// Because of the specific requirements and field names within each persons project that // Because of the specific requirements and field names within each persons project that
// uses this library it is likely that custom functions will need to be created for your // uses this library it is likely that custom functions will need to be created for your
@ -225,29 +225,29 @@ Here is a list of the current built in validators:
Only valid for Numbers and time.Time types, this will validate the field value Only valid for Numbers and time.Time types, this will validate the field value
against another fields value either within a struct or passed in field. against another fields value either within a struct or passed in field.
usage examples are for validation of a Start and End date: usage examples are for validation of a Start and End date:
Validation on End field using ValidateByStruct Usage(gtfield=Start) Validation on End field using validate.Struct Usage(gtfield=Start)
Validating by field ValidateFieldByTagAndValue(start, end, "gtfield") Validating by field validate.FieldWithValue(start, end, "gtfield")
gtefield gtefield
Only valid for Numbers and time.Time types, this will validate the field value Only valid for Numbers and time.Time types, this will validate the field value
against another fields value either within a struct or passed in field. against another fields value either within a struct or passed in field.
usage examples are for validation of a Start and End date: usage examples are for validation of a Start and End date:
Validation on End field using ValidateByStruct Usage(gtefield=Start) Validation on End field using validate.Struct Usage(gtefield=Start)
Validating by field ValidateFieldByTagAndValue(start, end, "gtefield") Validating by field validate.FieldWithValue(start, end, "gtefield")
ltfield ltfield
Only valid for Numbers and time.Time types, this will validate the field value Only valid for Numbers and time.Time types, this will validate the field value
against another fields value either within a struct or passed in field. against another fields value either within a struct or passed in field.
usage examples are for validation of a Start and End date: usage examples are for validation of a Start and End date:
Validation on End field using ValidateByStruct Usage(ltfield=Start) Validation on End field using validate.Struct Usage(ltfield=Start)
Validating by field ValidateFieldByTagAndValue(start, end, "ltfield") Validating by field validate.FieldWithValue(start, end, "ltfield")
ltefield ltefield
Only valid for Numbers and time.Time types, this will validate the field value Only valid for Numbers and time.Time types, this will validate the field value
against another fields value either within a struct or passed in field. against another fields value either within a struct or passed in field.
usage examples are for validation of a Start and End date: usage examples are for validation of a Start and End date:
Validation on End field using ValidateByStruct Usage(ltefield=Start) Validation on End field using validate.Struct Usage(ltefield=Start)
Validating by field ValidateFieldByTagAndValue(start, end, "ltefield") Validating by field validate.FieldWithValue(start, end, "ltefield")
alpha alpha
This validates that a strings value contains alpha characters only This validates that a strings value contains alpha characters only
@ -329,6 +329,6 @@ This package panics when bad input is provided, this is by design, bad code like
TestField: "Test" TestField: "Test"
} }
myValidator.ValidateStruct(t) // this will panic validate.Struct(t) // this will panic
*/ */
package validator package validator

@ -2,7 +2,7 @@
* Package validator * Package validator
* *
* MISC: * MISC:
* - anonymous structs - they don't have names so expect the Struct name within StructValidationErrors to be blank * - anonymous structs - they don't have names so expect the Struct name within StructErrors to be blank
* *
*/ */
@ -25,15 +25,15 @@ const (
tagKeySeparator = "=" tagKeySeparator = "="
structOnlyTag = "structonly" structOnlyTag = "structonly"
omitempty = "omitempty" omitempty = "omitempty"
validationFieldErrMsg = "Field validation for \"%s\" failed on the \"%s\" tag\n" fieldErrMsg = "Field validation for \"%s\" failed on the \"%s\" tag\n"
validationStructErrMsg = "Struct:%s\n" structErrMsg = "Struct:%s\n"
) )
// FieldValidationError contains a single field's validation error along // FieldError contains a single field's validation error along
// with other properties that may be needed for error message creation // with other properties that may be needed for error message creation
type FieldValidationError struct { type FieldError struct {
Field string Field string
ErrorTag string Tag string
Kind reflect.Kind Kind reflect.Kind
Type reflect.Type Type reflect.Type
Param string Param string
@ -41,27 +41,27 @@ type FieldValidationError struct {
} }
// This is intended for use in development + debugging and not intended to be a production error message. // This is intended for use in development + debugging and not intended to be a production error message.
// it also allows FieldValidationError to be used as an Error interface // it also allows FieldError to be used as an Error interface
func (e *FieldValidationError) Error() string { func (e *FieldError) Error() string {
return fmt.Sprintf(validationFieldErrMsg, e.Field, e.ErrorTag) return fmt.Sprintf(fieldErrMsg, e.Field, e.Tag)
} }
// StructValidationErrors is hierarchical list of field and struct validation errors // StructErrors is hierarchical list of field and struct validation errors
// for a non hierarchical representation please see the Flatten method for StructValidationErrors // for a non hierarchical representation please see the Flatten method for StructErrors
type StructValidationErrors struct { type StructErrors struct {
// Name of the Struct // Name of the Struct
Struct string Struct string
// Struct Field Errors // Struct Field Errors
Errors map[string]*FieldValidationError Errors map[string]*FieldError
// Struct Fields of type struct and their errors // Struct Fields of type struct and their errors
// key = Field Name of current struct, but internally Struct will be the actual struct name unless anonymous struct, it will be blank // key = Field Name of current struct, but internally Struct will be the actual struct name unless anonymous struct, it will be blank
StructErrors map[string]*StructValidationErrors StructErrors map[string]*StructErrors
} }
// This is intended for use in development + debugging and not intended to be a production error message. // This is intended for use in development + debugging and not intended to be a production error message.
// it also allows StructValidationErrors to be used as an Error interface // it also allows StructErrors to be used as an Error interface
func (e *StructValidationErrors) Error() string { func (e *StructErrors) Error() string {
buff := bytes.NewBufferString(fmt.Sprintf(validationStructErrMsg, e.Struct)) buff := bytes.NewBufferString(fmt.Sprintf(structErrMsg, e.Struct))
for _, err := range e.Errors { for _, err := range e.Errors {
buff.WriteString(err.Error()) buff.WriteString(err.Error())
@ -74,15 +74,15 @@ func (e *StructValidationErrors) Error() string {
return buff.String() return buff.String()
} }
// Flatten flattens the StructValidationErrors hierarchical structure into a flat namespace style field name // Flatten flattens the StructErrors hierarchical structure into a flat namespace style field name
// for those that want/need it // for those that want/need it
func (e *StructValidationErrors) Flatten() map[string]*FieldValidationError { func (e *StructErrors) Flatten() map[string]*FieldError {
if e == nil { if e == nil {
return nil return nil
} }
errs := map[string]*FieldValidationError{} errs := map[string]*FieldError{}
for _, f := range e.Errors { for _, f := range e.Errors {
@ -103,27 +103,27 @@ func (e *StructValidationErrors) Flatten() map[string]*FieldValidationError {
return errs return errs
} }
// ValidationFunc accepts all values needed for file and cross field validation // Func accepts all values needed for file and cross field validation
// top = top level struct when validating by struct otherwise nil // top = top level struct when validating by struct otherwise nil
// current = current level struct when validating by struct otherwise optional comparison value // current = current level struct when validating by struct otherwise optional comparison value
// f = field value for validation // f = field value for validation
// param = parameter used in validation i.e. gt=0 param would be 0 // param = parameter used in validation i.e. gt=0 param would be 0
type ValidationFunc func(top interface{}, current interface{}, f interface{}, param string) bool type Func func(top interface{}, current interface{}, f interface{}, param string) bool
// Validator implements the Validator Struct // Validate implements the Validate Struct
// NOTE: Fields within are not thread safe and that is on purpose // NOTE: Fields within are not thread safe and that is on purpose
// Functions and Tags should all be predifined before use, so subscribe to the philosiphy // Functions and Tags should all be predifined before use, so subscribe to the philosiphy
// or make it thread safe on your end // or make it thread safe on your end
type Validator struct { type Validate struct {
// tagName being used. // tagName being used.
tagName string tagName string
// validationFuncs is a map of validation functions and the tag keys // validateFuncs is a map of validation functions and the tag keys
validationFuncs map[string]ValidationFunc validationFuncs map[string]Func
} }
// NewValidator creates a new Validator instance for use. // New creates a new Validate instance for use.
func NewValidator(tagName string, funcs map[string]ValidationFunc) *Validator { func New(tagName string, funcs map[string]Func) *Validate {
return &Validator{ return &Validate{
tagName: tagName, tagName: tagName,
validationFuncs: funcs, validationFuncs: funcs,
} }
@ -131,13 +131,13 @@ func NewValidator(tagName string, funcs map[string]ValidationFunc) *Validator {
// SetTag sets tagName of the Validator to one of your choosing after creation // SetTag sets tagName of the Validator to one of your choosing after creation
// perhaps to dodge a tag name conflict in a specific section of code // perhaps to dodge a tag name conflict in a specific section of code
func (v *Validator) SetTag(tagName string) { func (v *Validate) SetTag(tagName string) {
v.tagName = tagName v.tagName = tagName
} }
// AddFunction adds a ValidationFunc to a Validator's map of validators denoted by the key // AddFunction adds a validation Func to a Validate's map of validators denoted by the key
// NOTE: if the key already exists, it will get replaced. // NOTE: if the key already exists, it will get replaced.
func (v *Validator) AddFunction(key string, f ValidationFunc) error { func (v *Validate) AddFunction(key string, f Func) error {
if len(key) == 0 { if len(key) == 0 {
return errors.New("Function Key cannot be empty") return errors.New("Function Key cannot be empty")
@ -152,34 +152,34 @@ func (v *Validator) AddFunction(key string, f ValidationFunc) error {
return nil return nil
} }
// ValidateStruct validates a struct, even it's nested structs, and returns a struct containing the errors // Struct validates a struct, even it's nested structs, and returns a struct containing the errors
// NOTE: Nested Arrays, or Maps of structs do not get validated only the Array or Map itself; the reason is that there is no good // NOTE: Nested Arrays, or Maps of structs do not get validated only the Array or Map itself; the reason is that there is no good
// way to represent or report which struct within the array has the error, besides can validate the struct prior to adding it to // way to represent or report which struct within the array has the error, besides can validate the struct prior to adding it to
// the Array or Map. // the Array or Map.
func (v *Validator) ValidateStruct(s interface{}) *StructValidationErrors { func (v *Validate) Struct(s interface{}) *StructErrors {
return v.validateStructRecursive(s, s, s) return v.structRecursive(s, s, s)
} }
// validateStructRecursive validates a struct recursivly and passes the top level and current struct around for use in validator functions and returns a struct containing the errors // structRecursive validates a struct recursivly and passes the top level and current struct around for use in validator functions and returns a struct containing the errors
func (v *Validator) validateStructRecursive(top interface{}, current interface{}, s interface{}) *StructValidationErrors { func (v *Validate) structRecursive(top interface{}, current interface{}, s interface{}) *StructErrors {
structValue := reflect.ValueOf(s) structValue := reflect.ValueOf(s)
structType := reflect.TypeOf(s) structType := reflect.TypeOf(s)
structName := structType.Name() structName := structType.Name()
if structValue.Kind() == reflect.Ptr && !structValue.IsNil() { if structValue.Kind() == reflect.Ptr && !structValue.IsNil() {
return v.validateStructRecursive(top, current, structValue.Elem().Interface()) return v.structRecursive(top, current, structValue.Elem().Interface())
} }
if structValue.Kind() != reflect.Struct && structValue.Kind() != reflect.Interface { if structValue.Kind() != reflect.Struct && structValue.Kind() != reflect.Interface {
panic("interface passed for validation is not a struct") panic("interface passed for validation is not a struct")
} }
validationErrors := &StructValidationErrors{ validationErrors := &StructErrors{
Struct: structName, Struct: structName,
Errors: map[string]*FieldValidationError{}, Errors: map[string]*FieldError{},
StructErrors: map[string]*StructValidationErrors{}, StructErrors: map[string]*StructErrors{},
} }
var numFields = structValue.NumField() var numFields = structValue.NumField()
@ -214,7 +214,7 @@ func (v *Validator) validateStructRecursive(top interface{}, current interface{}
if valueField.Type() == reflect.TypeOf(time.Time{}) { if valueField.Type() == reflect.TypeOf(time.Time{}) {
if fieldError := v.validateFieldByNameAndTagAndValue(top, current, valueField.Interface(), typeField.Name, tag); fieldError != nil { if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), typeField.Name, tag); fieldError != nil {
validationErrors.Errors[fieldError.Field] = fieldError validationErrors.Errors[fieldError.Field] = fieldError
// free up memory reference // free up memory reference
fieldError = nil fieldError = nil
@ -226,7 +226,7 @@ func (v *Validator) validateStructRecursive(top interface{}, current interface{}
continue continue
} }
if structErrors := v.validateStructRecursive(top, valueField.Interface(), valueField.Interface()); structErrors != nil { if structErrors := v.structRecursive(top, valueField.Interface(), valueField.Interface()); structErrors != nil {
validationErrors.StructErrors[typeField.Name] = structErrors validationErrors.StructErrors[typeField.Name] = structErrors
// free up memory map no longer needed // free up memory map no longer needed
structErrors = nil structErrors = nil
@ -235,7 +235,7 @@ func (v *Validator) validateStructRecursive(top interface{}, current interface{}
default: default:
if fieldError := v.validateFieldByNameAndTagAndValue(top, current, valueField.Interface(), typeField.Name, tag); fieldError != nil { if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), typeField.Name, tag); fieldError != nil {
validationErrors.Errors[fieldError.Field] = fieldError validationErrors.Errors[fieldError.Field] = fieldError
// free up memory reference // free up memory reference
fieldError = nil fieldError = nil
@ -250,21 +250,21 @@ func (v *Validator) validateStructRecursive(top interface{}, current interface{}
return validationErrors return validationErrors
} }
// ValidateFieldByTag allows validation of a single field, still using tag style validation to check multiple errors // Field allows validation of a single field, still using tag style validation to check multiple errors
func (v *Validator) ValidateFieldByTag(f interface{}, tag string) *FieldValidationError { func (v *Validate) Field(f interface{}, tag string) *FieldError {
return v.ValidateFieldByTagAndValue(nil, f, tag) return v.FieldWithValue(nil, f, tag)
} }
// ValidateFieldByTagAndValue allows validation of a single field, possibly even against another fields value, still using tag style validation to check multiple errors // FieldWithValue allows validation of a single field, possibly even against another fields value, still using tag style validation to check multiple errors
func (v *Validator) ValidateFieldByTagAndValue(val interface{}, f interface{}, tag string) *FieldValidationError { func (v *Validate) FieldWithValue(val interface{}, f interface{}, tag string) *FieldError {
return v.validateFieldByNameAndTagAndValue(nil, val, f, "", tag) return v.fieldWithNameAndValue(nil, val, f, "", tag)
} }
func (v *Validator) validateFieldByNameAndTagAndValue(val interface{}, current interface{}, f interface{}, name string, tag string) *FieldValidationError { func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f interface{}, name string, tag string) *FieldError {
// This is a double check if coming from ValidateStruct but need to be here in case function is called directly // This is a double check if coming from validate.Struct but need to be here in case function is called directly
if tag == noValidationTag { if tag == noValidationTag {
return nil return nil
} }
@ -277,7 +277,7 @@ func (v *Validator) validateFieldByNameAndTagAndValue(val interface{}, current i
fieldKind := valueField.Kind() fieldKind := valueField.Kind()
if fieldKind == reflect.Ptr && !valueField.IsNil() { if fieldKind == reflect.Ptr && !valueField.IsNil() {
return v.validateFieldByNameAndTagAndValue(val, current, valueField.Elem().Interface(), name, tag) return v.fieldWithNameAndValue(val, current, valueField.Elem().Interface(), name, tag)
} }
fieldType := valueField.Type() fieldType := valueField.Type()
@ -291,7 +291,7 @@ func (v *Validator) validateFieldByNameAndTagAndValue(val interface{}, current i
} }
} }
var valErr *FieldValidationError var valErr *FieldError
var err error var err error
valTags := strings.Split(tag, tagSeparator) valTags := strings.Split(tag, tagSeparator)
@ -305,25 +305,25 @@ func (v *Validator) validateFieldByNameAndTagAndValue(val interface{}, current i
for _, val := range orVals { for _, val := range orVals {
valErr, err = v.validateFieldByNameAndSingleTag(val, current, f, name, val) valErr, err = v.fieldWithNameAndSingleTag(val, current, f, name, val)
if err == nil { if err == nil {
return nil return nil
} }
errTag += orSeparator + valErr.ErrorTag errTag += orSeparator + valErr.Tag
} }
errTag = strings.TrimLeft(errTag, orSeparator) errTag = strings.TrimLeft(errTag, orSeparator)
valErr.ErrorTag = errTag valErr.Tag = errTag
valErr.Kind = fieldKind valErr.Kind = fieldKind
return valErr return valErr
} }
if valErr, err = v.validateFieldByNameAndSingleTag(val, current, f, name, valTag); err != nil { if valErr, err = v.fieldWithNameAndSingleTag(val, current, f, name, valTag); err != nil {
valErr.Kind = valueField.Kind() valErr.Kind = valueField.Kind()
valErr.Type = fieldType valErr.Type = fieldType
@ -335,7 +335,7 @@ func (v *Validator) validateFieldByNameAndTagAndValue(val interface{}, current i
return nil return nil
} }
func (v *Validator) validateFieldByNameAndSingleTag(val interface{}, current interface{}, f interface{}, name string, valTag string) (*FieldValidationError, error) { func (v *Validate) fieldWithNameAndSingleTag(val interface{}, current interface{}, f interface{}, name string, valTag string) (*FieldError, error) {
vals := strings.Split(valTag, tagKeySeparator) vals := strings.Split(valTag, tagKeySeparator)
key := strings.Trim(vals[0], " ") key := strings.Trim(vals[0], " ")
@ -344,9 +344,9 @@ func (v *Validator) validateFieldByNameAndSingleTag(val interface{}, current int
panic(fmt.Sprintf("Invalid validation tag on field %s", name)) panic(fmt.Sprintf("Invalid validation tag on field %s", name))
} }
valErr := &FieldValidationError{ valErr := &FieldError{
Field: name, Field: name,
ErrorTag: key, Tag: key,
Value: f, Value: f,
Param: "", Param: "",
} }

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save