github.com/opentelekomcloud/gophertelekomcloud@v0.9.3/internal/build/tags.go (about)

     1  package build
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  
     7  	"github.com/opentelekomcloud/gophertelekomcloud/internal/multierr"
     8  )
     9  
    10  type fieldValue struct {
    11  	// Name of the field in the type.
    12  	Name string
    13  	// Value of the field in the structure.
    14  	Value reflect.Value
    15  	// `required` tag value.
    16  	TagRequired bool
    17  	// `xor` tag value.
    18  	TagXOR string
    19  	// `or` tag value.
    20  	TagOR string
    21  }
    22  
    23  // ValidateTags validating structure by tags.
    24  //
    25  // Supported validations:
    26  //
    27  //	required (`required:"true"`)  - mark field required, returns error if it is empty.
    28  //	or:      (`or:"OtherField"`)  - requires at least one field to be not empty.
    29  //	xor:     (`xor:"OtherField"`) - requires exactly of this and the other field to be set.
    30  func ValidateTags(opts interface{}) error {
    31  	if opts == nil {
    32  		return nil // nil is an ideal value
    33  	}
    34  
    35  	optsValue := reflect.ValueOf(opts)
    36  	if optsValue.Kind() == reflect.Ptr {
    37  		optsValue = optsValue.Elem()
    38  	}
    39  
    40  	optsType := reflect.TypeOf(opts)
    41  	if optsType.Kind() == reflect.Ptr {
    42  		optsType = optsType.Elem()
    43  	}
    44  
    45  	fields := make(map[string]fieldValue)
    46  
    47  	if optsValue.Kind() != reflect.Struct {
    48  		return nil // no need to go deep
    49  	}
    50  
    51  	// fill the structure fields map
    52  	for i := 0; i < optsValue.NumField(); i++ {
    53  		value := optsValue.Field(i)
    54  		field := optsType.Field(i)
    55  
    56  		fields[field.Name] = fieldValue{
    57  			Name:        field.Name,
    58  			Value:       value,
    59  			TagRequired: structFieldRequired(field),
    60  			TagXOR:      field.Tag.Get("xor"),
    61  			TagOR:       field.Tag.Get("or"),
    62  		}
    63  	}
    64  
    65  	errors := multierr.MultiError{}
    66  
    67  	for name, field := range fields {
    68  		fieldErrors := make([]error, 0)
    69  
    70  		if field.TagRequired && field.Value.IsZero() {
    71  			fieldErrors = append(fieldErrors,
    72  				fmt.Errorf("missing input for argument [%s]", name),
    73  			)
    74  		}
    75  
    76  		orField := field.TagOR
    77  		if orField != "" && field.Value.IsZero() && fields[orField].Value.IsZero() {
    78  			fieldErrors = append(fieldErrors,
    79  				fmt.Errorf("at least one of %s and %s must be provided", name, orField),
    80  			)
    81  		}
    82  
    83  		xorField := field.TagXOR
    84  		if xorField != "" && (field.Value.IsZero() == fields[xorField].Value.IsZero()) {
    85  			fieldErrors = append(fieldErrors,
    86  				fmt.Errorf("exactly one of %s and %s must be provided", name, xorField),
    87  			)
    88  		}
    89  
    90  		errors = append(errors, fieldErrors...)
    91  	}
    92  
    93  	return errors.ErrorOrNil()
    94  }
    95  
    96  func structFieldRequired(field reflect.StructField) bool {
    97  	return field.Tag.Get("required") == "true"
    98  }