137 lines
3.5 KiB
Go
137 lines
3.5 KiB
Go
package cms
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/asaskevich/govalidator"
|
|
"github.com/labstack/echo/v4"
|
|
)
|
|
|
|
var (
|
|
objectIDPattern = regexp.MustCompile(`^[a-fA-F0-9]{24}$`)
|
|
filePathPattern = regexp.MustCompile(`^/[^/]*/v\d+/files/[a-fA-F0-9]{24}(/download)?$`)
|
|
)
|
|
|
|
// ValidationService registers CMS-specific govalidator rules.
|
|
type ValidationService struct{}
|
|
|
|
// NewValidationService registers custom validators used by CMS forms.
|
|
func NewValidationService() ValidationService {
|
|
govalidator.CustomTypeTagMap.Set("mediaRef", govalidator.CustomTypeValidator(func(i interface{}, _ interface{}) bool {
|
|
value, ok := i.(string)
|
|
if !ok {
|
|
return false
|
|
}
|
|
return isMediaReference(value)
|
|
}))
|
|
|
|
return ValidationService{}
|
|
}
|
|
|
|
func isMediaReference(value string) bool {
|
|
if value == "" {
|
|
return true
|
|
}
|
|
if govalidator.IsURL(value) {
|
|
return true
|
|
}
|
|
if objectIDPattern.MatchString(value) {
|
|
return true
|
|
}
|
|
return filePathPattern.MatchString(value)
|
|
}
|
|
|
|
// ValidateCMSForm validates a CMS create/update payload.
|
|
func ValidateCMSForm(form *CMSForm) error {
|
|
_, err := govalidator.ValidateStruct(form)
|
|
return err
|
|
}
|
|
|
|
// ParseForm binds and validates a CMS form from the request.
|
|
func ParseForm(c echo.Context) (*CMSForm, error) {
|
|
var form CMSForm
|
|
if err := c.Bind(&form); err != nil {
|
|
return nil, fmt.Errorf("invalid request format: %w", err)
|
|
}
|
|
if err := ValidateCMSForm(&form); err != nil {
|
|
return nil, err
|
|
}
|
|
return &form, nil
|
|
}
|
|
|
|
// FormatValidationErrors converts govalidator errors into user-facing messages.
|
|
func FormatValidationErrors(err error) string {
|
|
if err == nil {
|
|
return ""
|
|
}
|
|
|
|
errs, ok := err.(govalidator.Errors)
|
|
if !ok {
|
|
return humanizeValidationError(err.Error())
|
|
}
|
|
|
|
messages := make([]string, 0, len(errs))
|
|
for _, item := range errs {
|
|
messages = append(messages, humanizeValidationError(item.Error()))
|
|
}
|
|
return strings.Join(messages, "; ")
|
|
}
|
|
|
|
func humanizeValidationError(raw string) string {
|
|
field, rule, ok := parseGovalidatorError(raw)
|
|
if !ok {
|
|
return raw
|
|
}
|
|
|
|
label := fieldLabel(field)
|
|
lowerRule := strings.ToLower(rule)
|
|
|
|
switch {
|
|
case strings.Contains(lowerRule, "mediaref"):
|
|
return label + " must be a valid URL or uploaded file"
|
|
case strings.Contains(lowerRule, "url"):
|
|
return label + " must be a valid URL"
|
|
case strings.Contains(lowerRule, "email"):
|
|
return label + " must be a valid email address"
|
|
case strings.Contains(lowerRule, "length"):
|
|
return label + " has an invalid length"
|
|
case strings.Contains(lowerRule, "range"):
|
|
return label + " is out of the allowed range"
|
|
case strings.Contains(lowerRule, "in("):
|
|
return label + " has an invalid value"
|
|
default:
|
|
return label + " is invalid"
|
|
}
|
|
}
|
|
|
|
func parseGovalidatorError(raw string) (field, rule string, ok bool) {
|
|
parts := strings.SplitN(raw, ": ", 2)
|
|
if len(parts) != 2 {
|
|
return "", "", false
|
|
}
|
|
return parts[0], parts[1], true
|
|
}
|
|
|
|
func fieldLabel(field string) string {
|
|
field = strings.TrimPrefix(field, "CMSForm.")
|
|
replacer := strings.NewReplacer(
|
|
"Hero.", "Hero section: ",
|
|
"Chart.", "Chart section: ",
|
|
"Features.", "Features section: ",
|
|
"Challenges.", "Challenges section: ",
|
|
"Advantages.", "Advantages section: ",
|
|
"Contact.", "Contact section: ",
|
|
"Footer.", "Footer section: ",
|
|
".", " ",
|
|
)
|
|
label := replacer.Replace(field)
|
|
label = strings.ReplaceAll(label, "Cards 0", "card 1")
|
|
label = strings.ReplaceAll(label, "Cards 1", "card 2")
|
|
label = strings.ReplaceAll(label, "Cards 2", "card 3")
|
|
label = strings.ReplaceAll(label, "Cards 3", "card 4")
|
|
label = strings.ReplaceAll(label, "Cards 4", "card 5")
|
|
return label
|
|
}
|