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