package mila import ( "errors" "fmt" "strings" "unicode/utf8" ) type Config struct { MaxRouteLen int MaxTextLen int MaxTemplateLen int } func DefaultConfig() Config { return Config{ MaxRouteLen: 3, MaxTextLen: 200, MaxTemplateLen: 200, } } type ValidationError struct { Field string `json:"field"` Msg string `json:"message"` } func (e ValidationError) Error() string { return fmt.Sprintf("%s: %s", e.Field, e.Msg) } func Validate(cfg Config, state ContentState) error { cfg = normalizeConfig(cfg) state.Route = strings.TrimSpace(state.Route) state.Template = strings.TrimSpace(state.Template) if state.Route == "" { return ValidationError{Field: "route", Msg: "is required"} } if runeLen(state.Route) > cfg.MaxRouteLen { return ValidationError{Field: "route", Msg: fmt.Sprintf("must be at most %d characters", cfg.MaxRouteLen)} } if !utf8.ValidString(state.Route) { return ValidationError{Field: "route", Msg: "must be valid UTF-8"} } if !utf8.ValidString(state.Text) { return ValidationError{Field: "str", Msg: "must be valid UTF-8"} } if runeLen(state.Text) > cfg.MaxTextLen { return ValidationError{Field: "str", Msg: fmt.Sprintf("must be at most %d characters", cfg.MaxTextLen)} } if state.Template == "" { return ValidationError{Field: "tpl_name", Msg: "is required"} } if !utf8.ValidString(state.Template) { return ValidationError{Field: "tpl_name", Msg: "must be valid UTF-8"} } if runeLen(state.Template) > cfg.MaxTemplateLen { return ValidationError{Field: "tpl_name", Msg: fmt.Sprintf("must be at most %d characters", cfg.MaxTemplateLen)} } return nil } func IsValidationError(err error) bool { var target ValidationError return errors.As(err, &target) } func normalizeConfig(cfg Config) Config { def := DefaultConfig() if cfg.MaxRouteLen <= 0 { cfg.MaxRouteLen = def.MaxRouteLen } if cfg.MaxTextLen <= 0 { cfg.MaxTextLen = def.MaxTextLen } if cfg.MaxTemplateLen <= 0 { cfg.MaxTemplateLen = def.MaxTemplateLen } return cfg } func runeLen(s string) int { return utf8.RuneCountInString(s) }