Spaces:
Runtime error
Runtime error
AzureAD\AdityaDevarshi
Phase 5: consistent error envelope + validation + leak fix + /api 404
00c2742 | package api | |
| import ( | |
| "net/http" | |
| "reflect" | |
| "strings" | |
| "time" | |
| "github.com/go-playground/validator/v10" | |
| ) | |
| // validate is the package-level validator singleton. It is configured once to | |
| // report the json tag name (not the Go field name) so envelope field errors | |
| // match the wire contract, and to recognise the custom `yyyymmdd` date rule. | |
| var validate = newValidator() | |
| func newValidator() *validator.Validate { | |
| v := validator.New(validator.WithRequiredStructEnabled()) | |
| // Report fields by their json tag so writeFieldError names match the API. | |
| v.RegisterTagNameFunc(func(fld reflect.StructField) string { | |
| name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] | |
| if name == "-" { | |
| return "" | |
| } | |
| return name | |
| }) | |
| // yyyymmdd validates a YYYY-MM-DD calendar date (mirrors validDate). | |
| _ = v.RegisterValidation("yyyymmdd", func(fl validator.FieldLevel) bool { | |
| s := fl.Field().String() | |
| _, err := time.Parse("2006-01-02", s) | |
| return err == nil | |
| }) | |
| return v | |
| } | |
| // validationMessage turns a single field error into a human-readable message | |
| // that preserves the intent of the old hand-written messages. | |
| func validationMessage(fe validator.FieldError) string { | |
| field := fe.Field() | |
| switch fe.Tag() { | |
| case "required": | |
| return field + " is required" | |
| case "oneof": | |
| return field + " must be one of: " + strings.ReplaceAll(fe.Param(), " ", ", ") | |
| case "gte": | |
| return field + " must be >= " + fe.Param() | |
| case "gt": | |
| return field + " must be > " + fe.Param() | |
| case "lte": | |
| return field + " must be <= " + fe.Param() | |
| case "min": | |
| return field + " must have at least " + fe.Param() + " item(s)" | |
| case "yyyymmdd": | |
| return field + " must be a YYYY-MM-DD date" | |
| default: | |
| return field + " is invalid" | |
| } | |
| } | |
| // validateStruct runs the struct validation rules. On the first violation it | |
| // writes a VALIDATION (400) field error naming the offending json field and | |
| // returns false; on success it returns true. dive/element errors fall back to | |
| // the struct-level field name so callers always get a usable field. | |
| func validateStruct(w http.ResponseWriter, v any) bool { | |
| err := validate.Struct(v) | |
| if err == nil { | |
| return true | |
| } | |
| if verrs, ok := err.(validator.ValidationErrors); ok && len(verrs) > 0 { | |
| fe := verrs[0] | |
| writeFieldError(w, http.StatusBadRequest, CodeValidation, validationMessage(fe), fe.Field()) | |
| return false | |
| } | |
| // Non-field validation error (e.g. invalid validation setup): generic 400. | |
| writeError(w, http.StatusBadRequest, "invalid request body") | |
| return false | |
| } | |