96 lines
2.3 KiB
Go
96 lines
2.3 KiB
Go
package validators
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/go-playground/validator/v10"
|
|
|
|
"wucher/internal/transport/http/jsonapi"
|
|
)
|
|
|
|
type Validator struct {
|
|
validate *validator.Validate
|
|
}
|
|
|
|
var hoursPattern = regexp.MustCompile(`^\d+(\.\d+)?$`)
|
|
|
|
// validateHoursOrDate accepts an hours value (e.g. "1200" or "1200.5") or a calendar
|
|
// date "YYYY-MM-DD". Anything else (e.g. "1200 FH") is rejected.
|
|
func validateHoursOrDate(fl validator.FieldLevel) bool {
|
|
s := strings.TrimSpace(fl.Field().String())
|
|
if s == "" {
|
|
return true
|
|
}
|
|
if hoursPattern.MatchString(s) {
|
|
return true
|
|
}
|
|
_, err := time.Parse("2006-01-02", s)
|
|
return err == nil
|
|
}
|
|
|
|
func New() *Validator {
|
|
v := validator.New()
|
|
v.RegisterTagNameFunc(func(fld reflect.StructField) string {
|
|
name := fld.Tag.Get("json")
|
|
if name == "" {
|
|
return fld.Name
|
|
}
|
|
if idx := strings.Index(name, ","); idx != -1 {
|
|
return name[:idx]
|
|
}
|
|
return name
|
|
})
|
|
_ = v.RegisterValidation("hours_or_date", validateHoursOrDate)
|
|
return &Validator{validate: v}
|
|
}
|
|
|
|
func (v *Validator) ValidateStruct(s any) []jsonapi.ErrorObject {
|
|
if err := v.validate.Struct(s); err != nil {
|
|
if ve, ok := err.(validator.ValidationErrors); ok {
|
|
return toJSONAPIErrors(ve)
|
|
}
|
|
return []jsonapi.ErrorObject{{
|
|
Status: "400",
|
|
Title: "Invalid request",
|
|
Detail: err.Error(),
|
|
}}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func toJSONAPIErrors(ve validator.ValidationErrors) []jsonapi.ErrorObject {
|
|
errs := make([]jsonapi.ErrorObject, 0, len(ve))
|
|
for _, fe := range ve {
|
|
field := fe.Field()
|
|
jsonPath := "/data/attributes/" + strings.ReplaceAll(field, ".", "/")
|
|
errs = append(errs, jsonapi.ErrorObject{
|
|
Status: "422",
|
|
Title: "Validation error",
|
|
Detail: validationMessage(field, fe),
|
|
Source: &jsonapi.ErrorSource{Pointer: jsonPath},
|
|
})
|
|
}
|
|
return errs
|
|
}
|
|
|
|
func validationMessage(field string, fe validator.FieldError) string {
|
|
switch fe.Tag() {
|
|
case "required":
|
|
return field + " is required"
|
|
case "uuid":
|
|
return field + " must be a valid UUID"
|
|
case "email":
|
|
return field + " must be a valid email address"
|
|
case "hours_or_date":
|
|
return field + " must be flight hours (e.g. 1200) or a date in YYYY-MM-DD format"
|
|
case "oneof":
|
|
return field + " must be one of: " + fe.Param()
|
|
default:
|
|
return fmt.Sprintf("%s is invalid (%s)", field, fe.Tag())
|
|
}
|
|
}
|