diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-02 13:28:46 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-02 13:28:46 +0200 |
| commit | 10406467650942b780e5de462d5103431c5a951e (patch) | |
| tree | 2ef1050fe60916915f39c3eea655fdafe8241d3a /internal/appconfig/config_validate.go | |
| parent | 8735394dae4266bea638b20b5d327ce366a608a1 (diff) | |
appconfig: split config module and decompose oversized funcs (task 406)
Diffstat (limited to 'internal/appconfig/config_validate.go')
| -rw-r--r-- | internal/appconfig/config_validate.go | 98 |
1 files changed, 98 insertions, 0 deletions
diff --git a/internal/appconfig/config_validate.go b/internal/appconfig/config_validate.go new file mode 100644 index 0000000..f5e698f --- /dev/null +++ b/internal/appconfig/config_validate.go @@ -0,0 +1,98 @@ +package appconfig + +import ( + "fmt" + "strings" +) + +// Validate checks custom actions and tmux settings for duplicates and consistency. +func (a App) Validate() error { + if err := validateCustomActions(a.CustomActions); err != nil { + return err + } + return validateTmuxCustomMenuHotkey(a.TmuxCustomMenuHotkey) +} + +func validateCustomActions(actions []CustomAction) error { + seenID := make(map[string]struct{}) + seenHK := make(map[string]struct{}) + for _, action := range actions { + if err := validateCustomAction(action, seenID, seenHK); err != nil { + return err + } + } + return nil +} + +func validateCustomAction(action CustomAction, seenID, seenHK map[string]struct{}) error { + id := strings.ToLower(strings.TrimSpace(action.ID)) + if id == "" { + return fmt.Errorf("config: custom action missing required field id") + } + if _, exists := seenID[id]; exists { + return fmt.Errorf("config: duplicate custom action id: %s", action.ID) + } + seenID[id] = struct{}{} + + if strings.TrimSpace(action.Title) == "" { + return fmt.Errorf("config: custom action %s missing required field title", action.ID) + } + if err := validateCustomScope(action); err != nil { + return err + } + if err := validateCustomInstructionOrUser(action); err != nil { + return err + } + return validateCustomHotkey(action, seenHK) +} + +func validateCustomScope(action CustomAction) error { + scope := strings.TrimSpace(action.Scope) + if scope == "" || scope == "selection" || scope == "diagnostics" { + return nil + } + return fmt.Errorf("config: custom action %s has invalid scope: %s", action.ID, action.Scope) +} + +func validateCustomInstructionOrUser(action CustomAction) error { + hasInstr := strings.TrimSpace(action.Instruction) != "" + hasUser := strings.TrimSpace(action.User) != "" + if hasInstr && hasUser { + return fmt.Errorf("config: custom action %s must set either instruction or user, not both", action.ID) + } + if !hasInstr && !hasUser { + return fmt.Errorf("config: custom action %s requires instruction or user", action.ID) + } + return nil +} + +func validateCustomHotkey(action CustomAction, seenHK map[string]struct{}) error { + hk := strings.TrimSpace(action.Hotkey) + if hk == "" { + return nil + } + if len([]rune(hk)) != 1 { + return fmt.Errorf("config: custom action %s hotkey must be a single character", action.ID) + } + lower := strings.ToLower(hk) + if _, exists := seenHK[lower]; exists { + return fmt.Errorf("config: duplicate custom action hotkey: %s", hk) + } + seenHK[lower] = struct{}{} + return nil +} + +func validateTmuxCustomMenuHotkey(hotkey string) error { + hk := strings.TrimSpace(hotkey) + if hk == "" { + return nil + } + if len([]rune(hk)) != 1 { + return fmt.Errorf("config: invalid tmux.custom_menu_hotkey: %s", hk) + } + switch strings.ToLower(hk) { + case "r", "i", "c", "t", "p", "s": + return fmt.Errorf("config: invalid tmux.custom_menu_hotkey: %s (clashes with built-in)", hk) + } + return nil +} |
