package validate import ( "encoding/json" "net/http" "reflect" "strconv" "strings" apierrors "git.yuntuo.hk/k2safe/go-omni/errors" "github.com/go-playground/validator/v10" ) var structValidator = newValidator() func newValidator() *validator.Validate { instance := validator.New() instance.SetTagName("binding") return instance } // JSON binds and validates a JSON request body using standard-library HTTP // types. It intentionally does not depend on a network framework. func JSON(request *http.Request, value any) error { if request == nil || request.Body == nil { return apierrors.BadRequest("INVALID_BODY", "request body is invalid") } if err := json.NewDecoder(request.Body).Decode(value); err != nil { return apierrors.BadRequest("INVALID_BODY", "request body is invalid").WithCause(err) } return validateValue(value, "INVALID_BODY", "request body is invalid") } // Query binds query parameters using form tags and validates the result. func Query(request *http.Request, value any) error { if request == nil { return apierrors.BadRequest("INVALID_QUERY", "query parameters are invalid") } if err := bindValues(request.URL.Query(), value, "form"); err != nil { return apierrors.BadRequest("INVALID_QUERY", "query parameters are invalid").WithCause(err) } return validateValue(value, "INVALID_QUERY", "query parameters are invalid") } // URI binds path parameters supplied by a router adapter and validates them. func URI(params map[string]string, value any) error { if err := bindValues(singleValues(params), value, "uri"); err != nil { return apierrors.BadRequest("INVALID_PATH", "path parameters are invalid").WithCause(err) } return validateValue(value, "INVALID_PATH", "path parameters are invalid") } func validateValue(value any, code, message string) error { if err := structValidator.Struct(value); err != nil { return apierrors.BadRequest(code, message).WithCause(err) } return nil } func bindValues(values map[string][]string, target any, tagName string) error { value := reflect.ValueOf(target) if value.Kind() != reflect.Pointer || value.IsNil() || value.Elem().Kind() != reflect.Struct { return &bindError{"target must be a non-nil pointer to a struct"} } return bindStruct(values, value.Elem(), tagName) } func bindStruct(values map[string][]string, target reflect.Value, tagName string) error { typeOfTarget := target.Type() for index := 0; index < target.NumField(); index++ { field := target.Field(index) if !field.CanSet() { continue } fieldInfo := typeOfTarget.Field(index) tag := fieldInfo.Tag.Get(tagName) if tag == "-" { continue } name, defaultValue := parseTag(tag, fieldInfo.Name) items, ok := values[name] if !ok || len(items) == 0 || items[0] == "" { if defaultValue != "" { items = []string{defaultValue} } else { continue } } if err := setValue(field, items[0]); err != nil { return &bindError{fieldInfo.Name + ": " + err.Error()} } } return nil } func parseTag(tag, fallback string) (string, string) { if tag == "" { return fallback, "" } parts := strings.Split(tag, ",") name := parts[0] if name == "" { name = fallback } var defaultValue string for _, part := range parts[1:] { if strings.HasPrefix(part, "default=") { defaultValue = strings.TrimPrefix(part, "default=") } } return name, defaultValue } func setValue(target reflect.Value, value string) error { switch target.Kind() { case reflect.String: target.SetString(value) case reflect.Bool: parsed, err := strconv.ParseBool(value) if err != nil { return err } target.SetBool(parsed) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: parsed, err := strconv.ParseInt(value, 10, target.Type().Bits()) if err != nil { return err } target.SetInt(parsed) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: parsed, err := strconv.ParseUint(value, 10, target.Type().Bits()) if err != nil { return err } target.SetUint(parsed) case reflect.Float32, reflect.Float64: parsed, err := strconv.ParseFloat(value, target.Type().Bits()) if err != nil { return err } target.SetFloat(parsed) default: return &bindError{"unsupported field type " + target.Type().String()} } return nil } func singleValues(values map[string]string) map[string][]string { result := make(map[string][]string, len(values)) for key, value := range values { result[key] = []string{value} } return result } type bindError struct{ message string } func (e *bindError) Error() string { return e.message }