1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5// Package validate provides input validation helpers.
6package validate
7
8import (
9 "errors"
10 "fmt"
11
12 "git.secluded.site/go-lunatask"
13 "git.secluded.site/lune/internal/config"
14)
15
16// ErrInvalidReference indicates the input is not a valid UUID or deep link.
17var ErrInvalidReference = errors.New("invalid reference: expected UUID or lunatask:// deep link")
18
19// Reference parses a UUID or Lunatask deep link and returns the normalised UUID.
20// Accepts formats:
21// - UUID: "3bbf1923-64ae-4bcf-96a9-9bb86c799dab"
22// - Deep link: "lunatask://areas/3bbf1923-64ae-4bcf-96a9-9bb86c799dab"
23func Reference(input string) (string, error) {
24 _, id, err := lunatask.ParseReference(input)
25 if err != nil {
26 return "", fmt.Errorf("%w: %s", ErrInvalidReference, input)
27 }
28
29 return id, nil
30}
31
32// ErrInvalidStatus indicates the status value is not recognized.
33var ErrInvalidStatus = errors.New("invalid status")
34
35// TaskStatus validates and normalizes a task status string.
36// Returns the corresponding lunatask.TaskStatus or an error if invalid.
37func TaskStatus(input string) (lunatask.TaskStatus, error) {
38 status, err := lunatask.ParseTaskStatus(input)
39 if err != nil {
40 return "", fmt.Errorf("%w: %s", ErrInvalidStatus, input)
41 }
42
43 return status, nil
44}
45
46// ErrInvalidMotivation indicates the motivation value is not recognized.
47var ErrInvalidMotivation = errors.New("invalid motivation")
48
49// Motivation validates and normalizes a motivation string.
50// Returns the corresponding lunatask.Motivation or an error if invalid.
51func Motivation(input string) (lunatask.Motivation, error) {
52 motivation, err := lunatask.ParseMotivation(input)
53 if err != nil {
54 return "", fmt.Errorf("%w: %s", ErrInvalidMotivation, input)
55 }
56
57 return motivation, nil
58}
59
60// ErrInvalidRelationship indicates the relationship strength is not recognized.
61var ErrInvalidRelationship = errors.New("invalid relationship strength")
62
63// RelationshipStrength validates and normalizes a relationship strength string.
64// Returns the corresponding lunatask.RelationshipStrength or an error if invalid.
65func RelationshipStrength(input string) (lunatask.RelationshipStrength, error) {
66 rel, err := lunatask.ParseRelationshipStrength(input)
67 if err != nil {
68 return "", fmt.Errorf("%w: %s", ErrInvalidRelationship, input)
69 }
70
71 return rel, nil
72}
73
74// ErrInvalidArea indicates the area reference is not a valid UUID, deep link, or config key.
75var ErrInvalidArea = errors.New("invalid area: expected UUID, lunatask:// deep link, or config key")
76
77// AreaRef resolves an area reference to a UUID.
78// Accepts formats:
79// - UUID: "527a2b42-99fd-490d-8b21-c55451368f4c"
80// - Deep link: "lunatask://areas/527a2b42-..."
81// - Config key: "projects"
82func AreaRef(cfg *config.Config, input string) (string, error) {
83 // Try UUID or deep link first
84 if _, id, err := lunatask.ParseReference(input); err == nil {
85 return id, nil
86 }
87
88 // Try config key lookup
89 if area := cfg.AreaByKey(input); area != nil {
90 return area.ID, nil
91 }
92
93 return "", fmt.Errorf("%w: %s", ErrInvalidArea, input)
94}
95
96// ErrInvalidGoal indicates the goal reference is not a valid UUID, deep link, or config key.
97var ErrInvalidGoal = errors.New("invalid goal: expected UUID, lunatask:// deep link, or config key")
98
99// GoalRef resolves a goal reference to a UUID.
100// Requires a valid area ID to look up goals by key (goals are scoped to areas).
101// Accepts formats:
102// - UUID: "53ca909e-887d-4ed2-9943-d1212adf8ad8"
103// - Deep link: "lunatask://goals/53ca909e-..."
104// - Config key: "lunatask" (requires valid areaID for lookup)
105func GoalRef(cfg *config.Config, areaID, input string) (string, error) {
106 // Try UUID or deep link first
107 if _, id, err := lunatask.ParseReference(input); err == nil {
108 return id, nil
109 }
110
111 // Try config key lookup (requires area context)
112 if area := cfg.AreaByID(areaID); area != nil {
113 if goal := area.GoalByKey(input); goal != nil {
114 return goal.ID, nil
115 }
116 }
117
118 return "", fmt.Errorf("%w: %s", ErrInvalidGoal, input)
119}