validate.go raw

   1  /*
   2  Copyright © LiquidWeb
   3  
   4  Licensed under the Apache License, Version 2.0 (the "License");
   5  you may not use this file except in compliance with the License.
   6  You may obtain a copy of the License at
   7  
   8      http://www.apache.org/licenses/LICENSE-2.0
   9  
  10  Unless required by applicable law or agreed to in writing, software
  11  distributed under the License is distributed on an "AS IS" BASIS,
  12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13  See the License for the specific language governing permissions and
  14  limitations under the License.
  15  */
  16  package validate
  17  
  18  import (
  19  	"fmt"
  20  	"reflect"
  21  
  22  	"github.com/spf13/cast"
  23  )
  24  
  25  func Validate(chk map[interface{}]interface{}) (err error) {
  26  	defer func() {
  27  		if paniced := recover(); paniced != nil {
  28  			err = fmt.Errorf("%w %s", ValidationFailure, paniced)
  29  		}
  30  	}()
  31  
  32  	for inputFieldValue, inputField := range chk {
  33  
  34  		inputFieldVal := reflect.ValueOf(inputField)
  35  
  36  		// by default, assume input field passed by user cannot be empty
  37  		inputFieldOptional := false
  38  		if inputFieldVal.Kind() == reflect.Map {
  39  			iface := inputFieldVal.Interface()
  40  			inputFieldType := iface.(map[string]string)["type"]
  41  			if iface.(map[string]string)["optional"] == "true" {
  42  				inputFieldOptional = true
  43  			}
  44  			inputFieldVal = reflect.ValueOf(inputFieldType)
  45  		}
  46  
  47  		inputFieldStr := cast.ToString(inputFieldVal)
  48  
  49  		// inputField must be defined in InputTypes struct
  50  		defined, shouldBeType, fieldVal := inputTypeDefined(inputFieldStr)
  51  		if !defined {
  52  			err = fmt.Errorf("%w for input field [%+v] type [%s] is not valid", ValidationFailure,
  53  				inputFieldValue, inputFieldStr)
  54  			return
  55  		}
  56  
  57  		// inputFieldValue must be of the correct type
  58  		reflectValue := reflect.TypeOf(inputFieldValue).Name()
  59  		if reflectValue != shouldBeType {
  60  			err = fmt.Errorf("%w for input field [%+v] type [%s] has an invalid type of [%s] wanted [%s]",
  61  				ValidationFailure, inputFieldValue, inputFieldStr, reflectValue, shouldBeType)
  62  			return
  63  		}
  64  
  65  		// if the input field wasn't passed, and allow optional is true, continue
  66  		if inputFieldOptional {
  67  			// if inputFieldValue is a zero value, return without error
  68  			inputFieldValueVal := reflect.ValueOf(inputFieldValue)
  69  
  70  			if inputFieldStr == "NonEmptyString" {
  71  				// since we check by going by the zero value for the type if the input field was passed,
  72  				// we can't enforce the string type to not be empty optionally for NonEmptyString. Since
  73  				// we can't differentiate between not passed and its zero value.
  74  				err = fmt.Errorf("NonEmptyString input fields cannot be optional")
  75  				return
  76  			}
  77  
  78  			if inputFieldValueVal.IsZero() {
  79  				continue
  80  			}
  81  		}
  82  
  83  		// if there's a Validate method call it
  84  		iface := fieldVal.Interface()
  85  		if interfaceHasMethod(iface, "Validate") {
  86  			if validateErr := interfaceInputTypeValidate(iface, inputFieldValue); validateErr != nil {
  87  				err = fmt.Errorf("%w for input field [%+v] %s", ValidationFailure, inputFieldValue,
  88  					validateErr)
  89  				return
  90  			}
  91  		}
  92  
  93  	}
  94  
  95  	return
  96  }
  97  
  98  func interfaceInputTypeValidate(iface, inputFieldValue interface{}) error {
  99  	switch iface.(type) {
 100  	case InputTypeUniqId:
 101  		var obj InputTypeUniqId
 102  		obj.UniqId = cast.ToString(inputFieldValue)
 103  		if err := obj.Validate(); err != nil {
 104  			return err
 105  		}
 106  	case InputTypeIP:
 107  		var obj InputTypeIP
 108  		obj.IP = cast.ToString(inputFieldValue)
 109  		if err := obj.Validate(); err != nil {
 110  			return err
 111  		}
 112  	case InputTypePositiveInt64:
 113  		var obj InputTypePositiveInt64
 114  		obj.PositiveInt64 = cast.ToInt64(inputFieldValue)
 115  		if err := obj.Validate(); err != nil {
 116  			return err
 117  		}
 118  	case InputTypePositiveInt:
 119  		var obj InputTypePositiveInt
 120  		obj.PositiveInt = cast.ToInt(inputFieldValue)
 121  		if err := obj.Validate(); err != nil {
 122  			return err
 123  		}
 124  	case InputTypeNonEmptyString:
 125  		var obj InputTypeNonEmptyString
 126  		obj.NonEmptyString = cast.ToString(inputFieldValue)
 127  		if err := obj.Validate(); err != nil {
 128  			return err
 129  		}
 130  	case InputTypeLoadBalancerStrategyString:
 131  		var obj InputTypeLoadBalancerStrategyString
 132  		obj.LoadBalancerStrategy = cast.ToString(inputFieldValue)
 133  		if err := obj.Validate(); err != nil {
 134  			return err
 135  		}
 136  	case InputTypeHttpsLiquidwebUrl:
 137  		var obj InputTypeHttpsLiquidwebUrl
 138  		obj.HttpsLiquidwebUrl = cast.ToString(inputFieldValue)
 139  		if err := obj.Validate(); err != nil {
 140  			return err
 141  		}
 142  	case InputTypeNetworkPortPair:
 143  		var obj InputTypeNetworkPortPair
 144  		obj.NetworkPortPair = cast.ToString(inputFieldValue)
 145  		if err := obj.Validate(); err != nil {
 146  			return err
 147  		}
 148  	case InputTypeNetworkPort:
 149  		var obj InputTypeNetworkPort
 150  		obj.NetworkPort = cast.ToInt(inputFieldValue)
 151  		if err := obj.Validate(); err != nil {
 152  			return err
 153  		}
 154  	case InputTypeLoadBalancerHealthCheckProtocol:
 155  		var obj InputTypeLoadBalancerHealthCheckProtocol
 156  		obj.LoadBalancerHealthCheckProtocol = cast.ToString(inputFieldValue)
 157  		if err := obj.Validate(); err != nil {
 158  			return err
 159  		}
 160  	case InputTypeLoadBalancerHttpCodeRange:
 161  		var obj InputTypeLoadBalancerHttpCodeRange
 162  		obj.LoadBalancerHttpCodeRange = cast.ToString(inputFieldValue)
 163  		if err := obj.Validate(); err != nil {
 164  			return err
 165  		}
 166  	default:
 167  		return fmt.Errorf("bug: validation missing entry for %s", inputFieldValue)
 168  	}
 169  
 170  	return nil
 171  }
 172  
 173  func interfaceHasMethod(iface interface{}, methodName string) bool {
 174  	ifaceVal := reflect.ValueOf(iface)
 175  
 176  	if !ifaceVal.IsValid() {
 177  		// not valid, so we already know its false
 178  		return false
 179  	}
 180  
 181  	if ifaceVal.Type().Kind() != reflect.Ptr {
 182  		ifaceVal = reflect.New(reflect.TypeOf(iface))
 183  	}
 184  
 185  	method := ifaceVal.MethodByName(methodName)
 186  
 187  	if method.IsValid() {
 188  		return true
 189  	}
 190  
 191  	return false
 192  }
 193  
 194  func inputTypeDefined(inputType string) (bool, string, reflect.Value) {
 195  	var validTypes InputTypes
 196  
 197  	err, fieldType, fieldVal := structHasField(validTypes, inputType)
 198  	if err != nil {
 199  		return false, fieldType, fieldVal
 200  	}
 201  
 202  	return true, fieldType, fieldVal
 203  }
 204  
 205  func structHasField(data interface{}, fieldName string) (error, string, reflect.Value) {
 206  	dataVal := reflect.ValueOf(data)
 207  
 208  	if !dataVal.IsValid() {
 209  		return fmt.Errorf("failed fetching value for fieldName [%s]", fieldName), "",
 210  			reflect.Value{}
 211  	}
 212  
 213  	if dataVal.Type().Kind() != reflect.Ptr {
 214  		dataVal = reflect.New(reflect.TypeOf(data))
 215  	}
 216  
 217  	fieldVal := dataVal.Elem().FieldByName(fieldName)
 218  	if !fieldVal.IsValid() {
 219  		return fmt.Errorf("[%s] has no field [%s]", dataVal.Type(), fieldName), "", fieldVal
 220  	}
 221  
 222  	fieldValKindStr := fieldVal.Kind().String()
 223  
 224  	if fieldValKindStr == "struct" {
 225  		fieldValKindStr = fieldVal.Field(0).Kind().String()
 226  	}
 227  
 228  	return nil, fieldValKindStr, fieldVal
 229  }
 230