@@ -29,6 +29,10 @@ func validateFieldWithPresence(fieldValue reflect.Value, fieldPath string, tags
2929 })
3030 }
3131
32+ // Validate min/max tag parseability upfront so malformed constraints
33+ // are reported even when value validation is skipped (e.g., zero/missing values).
34+ errors = append (errors , validateMinMaxParseable (fieldValue , fieldPath , tags )... )
35+
3236 // Check required constraint
3337 if tags .required {
3438 if presentFields != nil {
@@ -196,20 +200,78 @@ func isZeroValue(v reflect.Value) bool {
196200 }
197201}
198202
203+ // validateMinMaxParseable checks that min/max tag values can be parsed for the given field kind.
204+ // Called before early returns so malformed constraints are always reported.
205+ func validateMinMaxParseable (fieldValue reflect.Value , fieldPath string , tags tagConfig ) []FieldError {
206+ var errors []FieldError
207+
208+ kind := fieldValue .Kind ()
209+ switch kind {
210+ case reflect .Int , reflect .Int8 , reflect .Int16 , reflect .Int32 , reflect .Int64 :
211+ if tags .min != "" {
212+ if _ , err := strconv .ParseInt (tags .min , 10 , 64 ); err != nil {
213+ errors = append (errors , FieldError {FieldPath : fieldPath , Code : ErrCodeInvalidTag ,
214+ Message : fmt .Sprintf ("invalid min constraint %q: must be a valid integer" , tags .min )})
215+ }
216+ }
217+ if tags .max != "" {
218+ if _ , err := strconv .ParseInt (tags .max , 10 , 64 ); err != nil {
219+ errors = append (errors , FieldError {FieldPath : fieldPath , Code : ErrCodeInvalidTag ,
220+ Message : fmt .Sprintf ("invalid max constraint %q: must be a valid integer" , tags .max )})
221+ }
222+ }
223+ case reflect .Uint , reflect .Uint8 , reflect .Uint16 , reflect .Uint32 , reflect .Uint64 :
224+ if tags .min != "" {
225+ if _ , err := strconv .ParseUint (tags .min , 10 , 64 ); err != nil {
226+ errors = append (errors , FieldError {FieldPath : fieldPath , Code : ErrCodeInvalidTag ,
227+ Message : fmt .Sprintf ("invalid min constraint %q: must be a valid unsigned integer" , tags .min )})
228+ }
229+ }
230+ if tags .max != "" {
231+ if _ , err := strconv .ParseUint (tags .max , 10 , 64 ); err != nil {
232+ errors = append (errors , FieldError {FieldPath : fieldPath , Code : ErrCodeInvalidTag ,
233+ Message : fmt .Sprintf ("invalid max constraint %q: must be a valid unsigned integer" , tags .max )})
234+ }
235+ }
236+ case reflect .Float32 , reflect .Float64 :
237+ if tags .min != "" {
238+ if _ , err := strconv .ParseFloat (tags .min , 64 ); err != nil {
239+ errors = append (errors , FieldError {FieldPath : fieldPath , Code : ErrCodeInvalidTag ,
240+ Message : fmt .Sprintf ("invalid min constraint %q: must be a valid number" , tags .min )})
241+ }
242+ }
243+ if tags .max != "" {
244+ if _ , err := strconv .ParseFloat (tags .max , 64 ); err != nil {
245+ errors = append (errors , FieldError {FieldPath : fieldPath , Code : ErrCodeInvalidTag ,
246+ Message : fmt .Sprintf ("invalid max constraint %q: must be a valid number" , tags .max )})
247+ }
248+ }
249+ case reflect .String :
250+ if tags .min != "" {
251+ if _ , err := strconv .Atoi (tags .min ); err != nil {
252+ errors = append (errors , FieldError {FieldPath : fieldPath , Code : ErrCodeInvalidTag ,
253+ Message : fmt .Sprintf ("invalid min constraint %q: must be a valid integer" , tags .min )})
254+ }
255+ }
256+ if tags .max != "" {
257+ if _ , err := strconv .Atoi (tags .max ); err != nil {
258+ errors = append (errors , FieldError {FieldPath : fieldPath , Code : ErrCodeInvalidTag ,
259+ Message : fmt .Sprintf ("invalid max constraint %q: must be a valid integer" , tags .max )})
260+ }
261+ }
262+ }
263+
264+ return errors
265+ }
266+
199267// validateIntMinMax validates min/max constraints for signed integer types.
200268func validateIntMinMax (fieldValue reflect.Value , fieldPath string , tags tagConfig ) []FieldError {
201269 var errors []FieldError
202270 value := fieldValue .Int ()
203271
204272 if tags .min != "" {
205273 minVal , err := strconv .ParseInt (tags .min , 10 , 64 )
206- if err != nil {
207- errors = append (errors , FieldError {
208- FieldPath : fieldPath ,
209- Code : ErrCodeInvalidTag ,
210- Message : fmt .Sprintf ("invalid min constraint %q: must be a valid integer" , tags .min ),
211- })
212- } else if value < minVal {
274+ if err == nil && value < minVal {
213275 errors = append (errors , FieldError {
214276 FieldPath : fieldPath ,
215277 Code : ErrCodeMin ,
@@ -220,13 +282,7 @@ func validateIntMinMax(fieldValue reflect.Value, fieldPath string, tags tagConfi
220282
221283 if tags .max != "" {
222284 maxVal , err := strconv .ParseInt (tags .max , 10 , 64 )
223- if err != nil {
224- errors = append (errors , FieldError {
225- FieldPath : fieldPath ,
226- Code : ErrCodeInvalidTag ,
227- Message : fmt .Sprintf ("invalid max constraint %q: must be a valid integer" , tags .max ),
228- })
229- } else if value > maxVal {
285+ if err == nil && value > maxVal {
230286 errors = append (errors , FieldError {
231287 FieldPath : fieldPath ,
232288 Code : ErrCodeMax ,
@@ -245,13 +301,7 @@ func validateUintMinMax(fieldValue reflect.Value, fieldPath string, tags tagConf
245301
246302 if tags .min != "" {
247303 minVal , err := strconv .ParseUint (tags .min , 10 , 64 )
248- if err != nil {
249- errors = append (errors , FieldError {
250- FieldPath : fieldPath ,
251- Code : ErrCodeInvalidTag ,
252- Message : fmt .Sprintf ("invalid min constraint %q: must be a valid unsigned integer" , tags .min ),
253- })
254- } else if value < minVal {
304+ if err == nil && value < minVal {
255305 errors = append (errors , FieldError {
256306 FieldPath : fieldPath ,
257307 Code : ErrCodeMin ,
@@ -262,13 +312,7 @@ func validateUintMinMax(fieldValue reflect.Value, fieldPath string, tags tagConf
262312
263313 if tags .max != "" {
264314 maxVal , err := strconv .ParseUint (tags .max , 10 , 64 )
265- if err != nil {
266- errors = append (errors , FieldError {
267- FieldPath : fieldPath ,
268- Code : ErrCodeInvalidTag ,
269- Message : fmt .Sprintf ("invalid max constraint %q: must be a valid unsigned integer" , tags .max ),
270- })
271- } else if value > maxVal {
315+ if err == nil && value > maxVal {
272316 errors = append (errors , FieldError {
273317 FieldPath : fieldPath ,
274318 Code : ErrCodeMax ,
@@ -287,13 +331,7 @@ func validateFloatMinMax(fieldValue reflect.Value, fieldPath string, tags tagCon
287331
288332 if tags .min != "" {
289333 minVal , err := strconv .ParseFloat (tags .min , 64 )
290- if err != nil {
291- errors = append (errors , FieldError {
292- FieldPath : fieldPath ,
293- Code : ErrCodeInvalidTag ,
294- Message : fmt .Sprintf ("invalid min constraint %q: must be a valid number" , tags .min ),
295- })
296- } else if value < minVal {
334+ if err == nil && value < minVal {
297335 errors = append (errors , FieldError {
298336 FieldPath : fieldPath ,
299337 Code : ErrCodeMin ,
@@ -304,13 +342,7 @@ func validateFloatMinMax(fieldValue reflect.Value, fieldPath string, tags tagCon
304342
305343 if tags .max != "" {
306344 maxVal , err := strconv .ParseFloat (tags .max , 64 )
307- if err != nil {
308- errors = append (errors , FieldError {
309- FieldPath : fieldPath ,
310- Code : ErrCodeInvalidTag ,
311- Message : fmt .Sprintf ("invalid max constraint %q: must be a valid number" , tags .max ),
312- })
313- } else if value > maxVal {
345+ if err == nil && value > maxVal {
314346 errors = append (errors , FieldError {
315347 FieldPath : fieldPath ,
316348 Code : ErrCodeMax ,
@@ -330,13 +362,7 @@ func validateStringMinMax(fieldValue reflect.Value, fieldPath string, tags tagCo
330362
331363 if tags .min != "" {
332364 minLen , err := strconv .Atoi (tags .min )
333- if err != nil {
334- errors = append (errors , FieldError {
335- FieldPath : fieldPath ,
336- Code : ErrCodeInvalidTag ,
337- Message : fmt .Sprintf ("invalid min constraint %q: must be a valid integer" , tags .min ),
338- })
339- } else if length < minLen {
365+ if err == nil && length < minLen {
340366 errors = append (errors , FieldError {
341367 FieldPath : fieldPath ,
342368 Code : ErrCodeMin ,
@@ -347,13 +373,7 @@ func validateStringMinMax(fieldValue reflect.Value, fieldPath string, tags tagCo
347373
348374 if tags .max != "" {
349375 maxLen , err := strconv .Atoi (tags .max )
350- if err != nil {
351- errors = append (errors , FieldError {
352- FieldPath : fieldPath ,
353- Code : ErrCodeInvalidTag ,
354- Message : fmt .Sprintf ("invalid max constraint %q: must be a valid integer" , tags .max ),
355- })
356- } else if length > maxLen {
376+ if err == nil && length > maxLen {
357377 errors = append (errors , FieldError {
358378 FieldPath : fieldPath ,
359379 Code : ErrCodeMax ,
0 commit comments