1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5// Package deeplink parses and builds Lunatask deep links.
6package deeplink
7
8import (
9 "errors"
10 "fmt"
11 "strings"
12
13 "github.com/google/uuid"
14)
15
16// Resource represents a Lunatask resource type that supports deep linking.
17type Resource string
18
19// Supported Lunatask resource types for deep linking.
20const (
21 Area Resource = "areas"
22 Goal Resource = "goals"
23 Task Resource = "tasks"
24 Note Resource = "notes"
25 Person Resource = "people"
26 Notebook Resource = "notebooks"
27)
28
29const (
30 scheme = "lunatask://"
31 deepLinkPartCount = 2
32)
33
34// ErrInvalidReference indicates the input is neither a valid UUID nor deep link.
35var ErrInvalidReference = errors.New("invalid reference: expected UUID or lunatask:// deep link")
36
37// ErrUnsupportedResource indicates the deep link resource type is not recognised.
38var ErrUnsupportedResource = errors.New("unsupported resource type in deep link")
39
40// ParseID extracts a UUID from either a raw UUID string or a Lunatask deep link.
41// Accepts formats:
42// - UUID: "3bbf1923-64ae-4bcf-96a9-9bb86c799dab"
43// - Deep link: "lunatask://areas/3bbf1923-64ae-4bcf-96a9-9bb86c799dab"
44func ParseID(input string) (string, error) {
45 input = strings.TrimSpace(input)
46
47 // Try parsing as UUID first
48 if parsed, err := uuid.Parse(input); err == nil {
49 return parsed.String(), nil
50 }
51
52 // Try parsing as deep link
53 if !strings.HasPrefix(input, scheme) {
54 return "", fmt.Errorf("%w: %s", ErrInvalidReference, input)
55 }
56
57 path := strings.TrimPrefix(input, scheme)
58 parts := strings.Split(path, "/")
59
60 if len(parts) != deepLinkPartCount {
61 return "", fmt.Errorf("%w: %s", ErrInvalidReference, input)
62 }
63
64 resource := parts[0]
65 id := parts[1]
66
67 // Validate resource type
68 switch Resource(resource) {
69 case Area, Goal, Task, Note, Person, Notebook:
70 // Valid resource
71 default:
72 return "", fmt.Errorf("%w: %s", ErrUnsupportedResource, resource)
73 }
74
75 // Validate and normalise the UUID
76 parsed, err := uuid.Parse(id)
77 if err != nil {
78 return "", fmt.Errorf("%w: %s", ErrInvalidReference, input)
79 }
80
81 return parsed.String(), nil
82}
83
84// Build constructs a Lunatask deep link for the given resource and ID.
85func Build(resource Resource, id string) (string, error) {
86 parsed, err := uuid.Parse(id)
87 if err != nil {
88 return "", fmt.Errorf("%w: %s", ErrInvalidReference, id)
89 }
90
91 return fmt.Sprintf("%s%s/%s", scheme, resource, parsed.String()), nil
92}