Files
Mazyar eeafe2a625
continuous-integration/drone/push Build is passing
Add inquiry status transition error handling and validation improvements
- Introduced InvalidStatusTransitionError to handle invalid status transitions for inquiries, providing clearer error messages.
- Updated UpdateInquiryStatusForm to make the Reason field optional and added logic to set a default reason if not provided.
- Enhanced form validation tests to cover new status transition error scenarios and validation messages.
- Refactored handler methods to utilize new error handling functions for improved response management.

This update improves the robustness of inquiry status management by ensuring proper error handling and validation, enhancing user experience during status updates.
2026-07-01 22:50:44 +03:30

121 lines
3.1 KiB
Go

package inquiry
import (
"fmt"
"regexp"
"strings"
"github.com/asaskevich/govalidator"
)
var (
lengthRulePattern = regexp.MustCompile(`length\((\d+)\|(\d+)\)`)
inRulePattern = regexp.MustCompile(`in\(([^)]+)\)`)
)
// 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 := inquiryFieldLabel(field)
lowerRule := strings.ToLower(rule)
switch {
case strings.Contains(lowerRule, "required"):
return label + " is required"
case strings.Contains(lowerRule, "email"):
return label + " must be a valid email address"
case strings.Contains(lowerRule, "length("):
if min, max, ok := parseLengthRule(rule); ok {
if min == max {
return fmt.Sprintf("%s must be exactly %d characters", label, min)
}
return fmt.Sprintf("%s must be between %d and %d characters", label, min, max)
}
return label + " has an invalid length"
case strings.Contains(lowerRule, "in("):
if values, ok := parseInRule(rule); ok {
return fmt.Sprintf("%s must be one of: %s", label, strings.Join(values, ", "))
}
return label + " has an invalid value"
case strings.Contains(lowerRule, "range("):
return label + " is out of the allowed range"
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 parseLengthRule(rule string) (min, max int, ok bool) {
matches := lengthRulePattern.FindStringSubmatch(rule)
if len(matches) != 3 {
return 0, 0, false
}
_, err := fmt.Sscanf(matches[1]+" "+matches[2], "%d %d", &min, &max)
return min, max, err == nil
}
func parseInRule(rule string) (values []string, ok bool) {
matches := inRulePattern.FindStringSubmatch(rule)
if len(matches) != 2 {
return nil, false
}
for _, value := range strings.Split(matches[1], "|") {
values = append(values, value)
}
return values, len(values) > 0
}
func inquiryFieldLabel(field string) string {
field = strings.TrimPrefix(field, "CreateInquiryForm.")
field = strings.TrimPrefix(field, "UpdateInquiryStatusForm.")
field = strings.TrimPrefix(field, "SearchInquiriesForm.")
labels := map[string]string{
"FullName": "Full name",
"CompanyName": "Company name",
"WorkEmail": "Work email",
"PhoneNumber": "Phone number",
"HCaptchaToken": "hCaptcha token",
"Status": "Status",
"Reason": "Reason",
"Description": "Description",
"Search": "Search query",
"Limit": "Limit",
"Offset": "Offset",
"SortBy": "Sort field",
"SortOrder": "Sort order",
}
if label, exists := labels[field]; exists {
return label
}
return field
}