package api import ( "errors" "net/http" "github.com/orchard9/rdev/internal/validate" ) // Bind decodes JSON from the request body and validates it. // This combines DecodeJSON and validation in a single call for convenience. // // Usage: // // func CreateUser(w http.ResponseWriter, r *http.Request) error { // var req CreateUserRequest // if err := api.Bind(r, &req); err != nil { // return err // Returns typed HTTPError // } // // req is now decoded and validated // } // // The struct should have validation tags: // // type CreateUserRequest struct { // Name string `json:"name" validate:"required,min=1,max=100"` // Email string `json:"email" validate:"required,email"` // } // // Bind uses the internal/validate package for validation. For struct validation // with go-playground/validator tags, see BindWithValidator. func Bind(r *http.Request, v any) error { // Decode JSON if err := DecodeJSON(r, v); err != nil { if errors.Is(err, ErrEmptyBody) { return BadRequest("request body is required") } return BadRequest("invalid request body") } return nil } // BindStrict decodes JSON from the request body with strict field checking. // Unknown fields in the JSON will cause an error. func BindStrict(r *http.Request, v any) error { if err := DecodeJSONStrict(r, v); err != nil { if errors.Is(err, ErrEmptyBody) { return BadRequest("request body is required") } return BadRequest("invalid request body") } return nil } // BindAndValidate decodes JSON and validates using the validate package. // Returns a validation error with field-level details if validation fails. // // Usage: // // func CreateUser(w http.ResponseWriter, r *http.Request) error { // var req CreateUserRequest // if err := api.BindAndValidate(r, &req, func(v *validate.Validator, req *CreateUserRequest) { // v.Required(req.Name, "name") // v.Required(req.Email, "email") // v.StringLength(req.Name, "name", 1, 100) // }); err != nil { // return err // } // } func BindAndValidate[T any](r *http.Request, v *T, validateFn func(*validate.Validator, *T)) error { // Decode JSON if err := DecodeJSON(r, v); err != nil { if errors.Is(err, ErrEmptyBody) { return BadRequest("request body is required") } return BadRequest("invalid request body") } // Run validation validator := validate.New() validateFn(validator, v) if validator.HasErrors() { return WithDetails(Validation("validation failed"), formatValidateErrors(validator.Errors())) } return nil } // ValidationDetail is the structure for field-level validation errors. type ValidationDetail struct { Field string `json:"field"` Message string `json:"message"` } // formatValidateErrors converts validation errors to API-friendly details. func formatValidateErrors(errs validate.ValidationErrors) []ValidationDetail { details := make([]ValidationDetail, 0, len(errs)) for _, e := range errs { details = append(details, ValidationDetail{ Field: e.Field, Message: e.Message, }) } return details }