deeplink.go

 1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
 2//
 3// SPDX-License-Identifier: AGPL-3.0-or-later
 4
 5package lunatask
 6
 7import (
 8	"errors"
 9	"fmt"
10	"strings"
11)
12
13// Errors returned by deep link operations.
14var (
15	// ErrInvalidDeepLink is returned when parsing a malformed deep link.
16	ErrInvalidDeepLink = errors.New("invalid deep link")
17	// ErrInvalidResource is returned when a resource type is unknown.
18	ErrInvalidResource = errors.New("invalid resource")
19	// ErrInvalidUUID is returned when the UUID portion is empty.
20	ErrInvalidUUID = errors.New("invalid UUID")
21)
22
23// Resource represents a Lunatask resource type for deep links.
24type Resource string
25
26// Valid resource types for deep links.
27const (
28	ResourceArea     Resource = "areas"
29	ResourceGoal     Resource = "goals"
30	ResourceTask     Resource = "tasks"
31	ResourceNote     Resource = "notes"
32	ResourcePerson   Resource = "people"
33	ResourceNotebook Resource = "notebooks"
34)
35
36// ParseDeepLink extracts resource type and UUID from a Lunatask deep link
37// or plain UUID. Accepts "lunatask://tasks/uuid" or plain UUID strings.
38// When a plain UUID is provided, the resource type will be empty.
39func ParseDeepLink(input string) (Resource, string, error) {
40	if input == "" {
41		return "", "", fmt.Errorf("%w: empty input", ErrInvalidDeepLink)
42	}
43
44	// Check for lunatask:// prefix
45	const prefix = "lunatask://"
46	if !strings.HasPrefix(input, prefix) {
47		// Treat as plain UUID
48		return "", input, nil
49	}
50
51	// Remove prefix and split
52	remainder := strings.TrimPrefix(input, prefix)
53	parts := strings.SplitN(remainder, "/", 2) //nolint:mnd // split resource/uuid
54
55	if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
56		return "", "", fmt.Errorf("%w: %q", ErrInvalidDeepLink, input)
57	}
58
59	resource := Resource(parts[0])
60	uuid := parts[1]
61
62	// Validate resource type
63	switch resource {
64	case ResourceArea, ResourceGoal, ResourceTask, ResourceNote, ResourcePerson, ResourceNotebook:
65		// valid
66	default:
67		return "", "", fmt.Errorf("%w: %q", ErrInvalidResource, resource)
68	}
69
70	return resource, uuid, nil
71}
72
73// BuildDeepLink constructs a Lunatask deep link from resource type and ID.
74// Returns "lunatask://resource/uuid".
75func BuildDeepLink(resource Resource, id string) (string, error) {
76	if id == "" {
77		return "", ErrInvalidUUID
78	}
79
80	// Validate resource type
81	switch resource {
82	case ResourceArea, ResourceGoal, ResourceTask, ResourceNote, ResourcePerson, ResourceNotebook:
83		// valid
84	default:
85		return "", fmt.Errorf("%w: %q", ErrInvalidResource, resource)
86	}
87
88	return fmt.Sprintf("lunatask://%s/%s", resource, id), nil
89}