cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm leaflet readability golang
29
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 186 lines 4.4 kB view raw
1package services 2 3import ( 4 "fmt" 5 "net/url" 6 "regexp" 7 "slices" 8 "strings" 9 "time" 10) 11 12var ( 13 emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`) 14 dateFormats = []string{ 15 "2006-01-02", 16 "2006-01-02T15:04:05Z", 17 "2006-01-02T15:04:05-07:00", 18 "2006-01-02 15:04:05", 19 } 20) 21 22// ValidationError represents a validation error 23type ValidationError struct { 24 Field string 25 Message string 26} 27 28func (e ValidationError) Error() string { 29 return fmt.Sprintf("validation error for field '%s': %s", e.Field, e.Message) 30} 31 32func NewValidationError(f, m string) ValidationError { 33 return ValidationError{Field: f, Message: m} 34} 35 36// ValidationErrors represents multiple validation errors 37type ValidationErrors []ValidationError 38 39func (e ValidationErrors) Error() string { 40 if len(e) == 0 { 41 return "no validation errors" 42 } 43 44 if len(e) == 1 { 45 return e[0].Error() 46 } 47 48 var messages []string 49 for _, err := range e { 50 messages = append(messages, err.Error()) 51 } 52 return fmt.Sprintf("multiple validation errors: %s", strings.Join(messages, "; ")) 53} 54 55// RequiredString validates that a string field is not empty 56func RequiredString(name, value string) error { 57 if strings.TrimSpace(value) == "" { 58 return NewValidationError(name, "is required and cannot be empty") 59 } 60 return nil 61} 62 63// ValidURL validates that a string is a valid URL 64func ValidURL(name, value string) error { 65 if value == "" { 66 return nil 67 } 68 69 parsed, err := url.Parse(value) 70 if err != nil { 71 return ValidationError{Field: name, Message: "must be a valid URL"} 72 } 73 74 if parsed.Scheme != "http" && parsed.Scheme != "https" { 75 return NewValidationError(name, "must use http or https scheme") 76 } 77 78 return nil 79} 80 81// ValidEmail validates that a string is a valid email address 82func ValidEmail(name, value string) error { 83 if value == "" { 84 return nil 85 } 86 87 if !emailRegex.MatchString(value) { 88 return NewValidationError(name, "must be a valid email address") 89 } 90 91 return nil 92} 93 94// StringLength validates string length constraints 95func StringLength(name, value string, min, max int) error { 96 length := len(strings.TrimSpace(value)) 97 if min > 0 && length < min { 98 return NewValidationError(name, fmt.Sprintf("must be at least %d characters long", min)) 99 } 100 if max > 0 && length > max { 101 return NewValidationError(name, fmt.Sprintf("must not exceed %d characters", max)) 102 } 103 return nil 104} 105 106// ValidDate validates that a string can be parsed as a date in supported formats 107func ValidDate(name, value string) error { 108 if value == "" { 109 return nil 110 } 111 112 for _, format := range dateFormats { 113 if _, err := time.Parse(format, value); err == nil { 114 return nil 115 } 116 } 117 return NewValidationError(name, "must be a valid date (YYYY-MM-DD, YYYY-MM-DDTHH:MM:SSZ, etc.)") 118} 119 120// PositiveID validates that an ID is positive 121func PositiveID(name string, value int64) error { 122 if value <= 0 { 123 return NewValidationError(name, "must be a positive integer") 124 } 125 return nil 126} 127 128// ValidEnum validates that a value is one of the allowed enum values 129func ValidEnum(name, value string, allowedValues []string) error { 130 if value == "" { 131 return nil 132 } 133 if slices.Contains(allowedValues, value) { 134 return nil 135 } 136 return NewValidationError(name, fmt.Sprintf("must be one of: %s", strings.Join(allowedValues, ", "))) 137} 138 139// ValidFilePath validates that a string looks like a valid file path 140func ValidFilePath(name, value string) error { 141 if value == "" { 142 return nil 143 } 144 if strings.Contains(value, "..") { 145 return NewValidationError(name, "cannot contain '..' path traversal") 146 } 147 if strings.ContainsAny(value, "<>:\"|?*") { 148 return NewValidationError(name, "contains invalid characters") 149 } 150 return nil 151} 152 153// Validator provides a fluent interface for validation 154type Validator struct { 155 errors ValidationErrors 156} 157 158// NewValidator creates a new validator instance 159func NewValidator() *Validator { 160 return &Validator{} 161} 162 163// Check adds a validation check 164func (v *Validator) Check(err error) *Validator { 165 if err != nil { 166 if valErr, ok := err.(ValidationError); ok { 167 v.errors = append(v.errors, valErr) 168 } else { 169 v.errors = append(v.errors, NewValidationError("unknown", err.Error())) 170 } 171 } 172 return v 173} 174 175// IsValid returns true if no validation errors occurred 176func (v *Validator) IsValid() bool { 177 return len(v.errors) == 0 178} 179 180// Errors returns all validation errors 181func (v *Validator) Errors() error { 182 if len(v.errors) == 0 { 183 return nil 184 } 185 return v.errors 186}