From ae0f649474115c48603432ca8bdc4afb86efa9f5 Mon Sep 17 00:00:00 2001 From: Amolith Date: Sun, 21 Dec 2025 20:25:52 -0700 Subject: [PATCH] feat(validate): add validation for enum types Add validation functions that lowercase and match against known constants for TaskStatus, Motivation, and RelationshipStrength. Wire validation into task add/update/list to reject invalid input early with clear error messages instead of passing bogus values to the API. Assisted-by: Claude Opus 4 via Crush --- cmd/task/add.go | 15 ++++++++-- cmd/task/list.go | 21 ++++++++++++- cmd/task/update.go | 14 +++++++-- internal/validate/validate.go | 55 +++++++++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+), 5 deletions(-) diff --git a/cmd/task/add.go b/cmd/task/add.go index 017163f75bd20c20f8d156f4006ad58ef0859677..7c26c5c0c54f485f1cafb201f09a29e77f1fb4ca 100644 --- a/cmd/task/add.go +++ b/cmd/task/add.go @@ -17,6 +17,7 @@ import ( "git.secluded.site/lune/internal/config" "git.secluded.site/lune/internal/dateutil" "git.secluded.site/lune/internal/ui" + "git.secluded.site/lune/internal/validate" "github.com/spf13/cobra" ) @@ -152,7 +153,12 @@ func applyAreaAndGoal(cmd *cobra.Command, builder *lunatask.TaskBuilder) error { func applyOptionalFlags(cmd *cobra.Command, builder *lunatask.TaskBuilder) error { if status, _ := cmd.Flags().GetString("status"); status != "" { - builder.WithStatus(lunatask.TaskStatus(status)) + s, err := validate.TaskStatus(status) + if err != nil { + return err + } + + builder.WithStatus(s) } if note, _ := cmd.Flags().GetString("note"); note != "" { @@ -178,7 +184,12 @@ func applyOptionalFlags(cmd *cobra.Command, builder *lunatask.TaskBuilder) error } if motivation, _ := cmd.Flags().GetString("motivation"); motivation != "" { - builder.WithMotivation(lunatask.Motivation(motivation)) + m, err := validate.Motivation(motivation) + if err != nil { + return err + } + + builder.WithMotivation(m) } applyEisenhower(cmd, builder) diff --git a/cmd/task/list.go b/cmd/task/list.go index f09347f6efa409cd3a13e11b0cc277e79d2d5b22..65e14a2791c1b1ae21acde0a2a09a236c0d31998 100644 --- a/cmd/task/list.go +++ b/cmd/task/list.go @@ -15,6 +15,7 @@ import ( "git.secluded.site/lune/internal/completion" "git.secluded.site/lune/internal/config" "git.secluded.site/lune/internal/ui" + "git.secluded.site/lune/internal/validate" "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss/table" "github.com/spf13/cobra" @@ -64,7 +65,11 @@ func runList(cmd *cobra.Command, _ []string) error { return err } - statusFilter := mustGetStringFlag(cmd, "status") + statusFilter, err := resolveStatusFilter(cmd) + if err != nil { + return err + } + showAll := mustGetBoolFlag(cmd, "all") tasks = applyFilters(tasks, areaID, statusFilter, showAll) @@ -136,6 +141,20 @@ func resolveAreaFilter(cmd *cobra.Command) (string, error) { return area.ID, nil } +func resolveStatusFilter(cmd *cobra.Command) (string, error) { + status := mustGetStringFlag(cmd, "status") + if status == "" { + return "", nil + } + + s, err := validate.TaskStatus(status) + if err != nil { + return "", err + } + + return string(s), nil +} + func applyFilters(tasks []lunatask.Task, areaID, statusFilter string, showAll bool) []lunatask.Task { filtered := make([]lunatask.Task, 0, len(tasks)) today := time.Now().Truncate(24 * time.Hour) diff --git a/cmd/task/update.go b/cmd/task/update.go index 37da39a8b624379bb3870c34f2e635449cd67f9d..7ef34a92bd7444155a21a10d0172bc31e474ca67 100644 --- a/cmd/task/update.go +++ b/cmd/task/update.go @@ -147,7 +147,12 @@ func applyUpdateAreaAndGoal(cmd *cobra.Command, builder *lunatask.TaskUpdateBuil func applyUpdateFlags(cmd *cobra.Command, builder *lunatask.TaskUpdateBuilder) error { if status, _ := cmd.Flags().GetString("status"); status != "" { - builder.WithStatus(lunatask.TaskStatus(status)) + s, err := validate.TaskStatus(status) + if err != nil { + return err + } + + builder.WithStatus(s) } if note, _ := cmd.Flags().GetString("note"); note != "" { @@ -173,7 +178,12 @@ func applyUpdateFlags(cmd *cobra.Command, builder *lunatask.TaskUpdateBuilder) e } if motivation, _ := cmd.Flags().GetString("motivation"); motivation != "" { - builder.WithMotivation(lunatask.Motivation(motivation)) + m, err := validate.Motivation(motivation) + if err != nil { + return err + } + + builder.WithMotivation(m) } applyUpdateEisenhower(cmd, builder) diff --git a/internal/validate/validate.go b/internal/validate/validate.go index 52658779a90c672a5c6ab1da82d222f43bad13e2..d861c5252a237a3ee5f84fa7fc8665d324b958a7 100644 --- a/internal/validate/validate.go +++ b/internal/validate/validate.go @@ -8,7 +8,9 @@ package validate import ( "errors" "fmt" + "strings" + "git.secluded.site/go-lunatask" "github.com/google/uuid" "git.secluded.site/lune/internal/deeplink" @@ -41,3 +43,56 @@ func Reference(input string) (string, error) { return id, nil } + +// ErrInvalidStatus indicates the status value is not recognized. +var ErrInvalidStatus = errors.New("invalid status") + +// TaskStatus validates and normalizes a task status string. +// Returns the corresponding lunatask.TaskStatus or an error if invalid. +func TaskStatus(input string) (lunatask.TaskStatus, error) { + status := lunatask.TaskStatus(strings.ToLower(input)) + + switch status { + case lunatask.StatusLater, lunatask.StatusNext, lunatask.StatusStarted, + lunatask.StatusWaiting, lunatask.StatusCompleted: + return status, nil + default: + return "", fmt.Errorf("%w: %s", ErrInvalidStatus, input) + } +} + +// ErrInvalidMotivation indicates the motivation value is not recognized. +var ErrInvalidMotivation = errors.New("invalid motivation") + +// Motivation validates and normalizes a motivation string. +// Returns the corresponding lunatask.Motivation or an error if invalid. +func Motivation(input string) (lunatask.Motivation, error) { + motivation := lunatask.Motivation(strings.ToLower(input)) + + switch motivation { + case lunatask.MotivationUnknown, lunatask.MotivationMust, + lunatask.MotivationShould, lunatask.MotivationWant: + return motivation, nil + default: + return "", fmt.Errorf("%w: %s", ErrInvalidMotivation, input) + } +} + +// ErrInvalidRelationship indicates the relationship strength is not recognized. +var ErrInvalidRelationship = errors.New("invalid relationship strength") + +// RelationshipStrength validates and normalizes a relationship strength string. +// Returns the corresponding lunatask.RelationshipStrength or an error if invalid. +func RelationshipStrength(input string) (lunatask.RelationshipStrength, error) { + rel := lunatask.RelationshipStrength(strings.ToLower(input)) + + switch rel { + case lunatask.RelationshipFamily, lunatask.RelationshipIntimateFriend, + lunatask.RelationshipCloseFriend, lunatask.RelationshipCasualFriend, + lunatask.RelationshipAcquaintance, lunatask.RelationshipBusiness, + lunatask.RelationshipAlmostStranger: + return rel, nil + default: + return "", fmt.Errorf("%w: %s", ErrInvalidRelationship, input) + } +}