feat(domain): add validation for search shortcut keys, urls, and descriptions
- commit
- 899908d296e1aa336c66666cfd65759af3555686
- author
- bnema · · 2025-12-21 13:53 UTC
- parent
- 7831cd5
- signature
- unsigned
1 file · +55 -0
A internal/domain/validation/shortcuts.go +55 -0
| @@ -0,0 +1,55 @@ | |||
| 1 | + | package validation | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "net/url" | |
| 5 | + | "regexp" | |
| 6 | + | "strings" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | var shortcutKeyRE = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9]{0,19}$`) | |
| 10 | + | ||
| 11 | + | func ValidateShortcutKey(value string) []string { | |
| 12 | + | var errs []string | |
| 13 | + | value = strings.TrimSpace(value) | |
| 14 | + | if value == "" { | |
| 15 | + | errs = append(errs, "shortcut key cannot be empty") | |
| 16 | + | return errs | |
| 17 | + | } | |
| 18 | + | if !shortcutKeyRE.MatchString(value) { | |
| 19 | + | errs = append(errs, "shortcut key must start with a letter and be 1-20 alphanumeric characters") | |
| 20 | + | } | |
| 21 | + | return errs | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | func ValidateShortcutURL(value string) []string { | |
| 25 | + | var errs []string | |
| 26 | + | value = strings.TrimSpace(value) | |
| 27 | + | if value == "" { | |
| 28 | + | errs = append(errs, "shortcut url cannot be empty") | |
| 29 | + | return errs | |
| 30 | + | } | |
| 31 | + | if !strings.Contains(value, "%s") { | |
| 32 | + | errs = append(errs, "shortcut url must contain %s placeholder for the search query") | |
| 33 | + | return errs | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | candidate := strings.ReplaceAll(value, "%s", "query") | |
| 37 | + | parsed, err := url.Parse(candidate) | |
| 38 | + | if err != nil || parsed.Scheme == "" || parsed.Host == "" { | |
| 39 | + | errs = append(errs, "shortcut url must be a valid absolute URL") | |
| 40 | + | } | |
| 41 | + | ||
| 42 | + | return errs | |
| 43 | + | } | |
| 44 | + | ||
| 45 | + | func ValidateShortcutDescription(value string) []string { | |
| 46 | + | var errs []string | |
| 47 | + | value = strings.TrimSpace(value) | |
| 48 | + | if strings.ContainsAny(value, "\r\n") { | |
| 49 | + | errs = append(errs, "shortcut description must not contain newlines") | |
| 50 | + | } | |
| 51 | + | if len(value) > 200 { | |
| 52 | + | errs = append(errs, "shortcut description is too long") | |
| 53 | + | } | |
| 54 | + | return errs | |
| 55 | + | } | |
| @@ -0,0 +1,55 @@ | |||||
| 1 | + | package validation | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "net/url" | |||
| 5 | + | "regexp" | |||
| 6 | + | "strings" | |||
| 7 | + | ) | |||
| 8 | + | ||||
| 9 | + | var shortcutKeyRE = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9]{0,19}$`) | |||
| 10 | + | ||||
| 11 | + | func ValidateShortcutKey(value string) []string { | |||
| 12 | + | var errs []string | |||
| 13 | + | value = strings.TrimSpace(value) | |||
| 14 | + | if value == "" { | |||
| 15 | + | errs = append(errs, "shortcut key cannot be empty") | |||
| 16 | + | return errs | |||
| 17 | + | } | |||
| 18 | + | if !shortcutKeyRE.MatchString(value) { | |||
| 19 | + | errs = append(errs, "shortcut key must start with a letter and be 1-20 alphanumeric characters") | |||
| 20 | + | } | |||
| 21 | + | return errs | |||
| 22 | + | } | |||
| 23 | + | ||||
| 24 | + | func ValidateShortcutURL(value string) []string { | |||
| 25 | + | var errs []string | |||
| 26 | + | value = strings.TrimSpace(value) | |||
| 27 | + | if value == "" { | |||
| 28 | + | errs = append(errs, "shortcut url cannot be empty") | |||
| 29 | + | return errs | |||
| 30 | + | } | |||
| 31 | + | if !strings.Contains(value, "%s") { | |||
| 32 | + | errs = append(errs, "shortcut url must contain %s placeholder for the search query") | |||
| 33 | + | return errs | |||
| 34 | + | } | |||
| 35 | + | ||||
| 36 | + | candidate := strings.ReplaceAll(value, "%s", "query") | |||
| 37 | + | parsed, err := url.Parse(candidate) | |||
| 38 | + | if err != nil || parsed.Scheme == "" || parsed.Host == "" { | |||
| 39 | + | errs = append(errs, "shortcut url must be a valid absolute URL") | |||
| 40 | + | } | |||
| 41 | + | ||||
| 42 | + | return errs | |||
| 43 | + | } | |||
| 44 | + | ||||
| 45 | + | func ValidateShortcutDescription(value string) []string { | |||
| 46 | + | var errs []string | |||
| 47 | + | value = strings.TrimSpace(value) | |||
| 48 | + | if strings.ContainsAny(value, "\r\n") { | |||
| 49 | + | errs = append(errs, "shortcut description must not contain newlines") | |||
| 50 | + | } | |||
| 51 | + | if len(value) > 200 { | |||
| 52 | + | errs = append(errs, "shortcut description is too long") | |||
| 53 | + | } | |||
| 54 | + | return errs | |||
| 55 | + | } | |||