validate.go

  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}