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