diff --git a/deeplink.go b/deeplink.go index 1fed2d63e2f6b80d261418ecac29eebf73638b8d..f9b2021069c19566ba0e61beb05e19997efc56c4 100644 --- a/deeplink.go +++ b/deeplink.go @@ -16,6 +16,8 @@ import ( var ( // ErrInvalidDeepLink is returned when parsing a malformed deep link. ErrInvalidDeepLink = errors.New("invalid deep link") + // ErrInvalidReference is returned when input is neither a valid deep link nor UUID. + ErrInvalidReference = errors.New("invalid reference") // ErrInvalidResource is returned when a resource type is unknown. ErrInvalidResource = errors.New("invalid resource") // ErrInvalidUUID is returned when the UUID portion is empty or malformed. @@ -35,11 +37,16 @@ const ( ResourceNotebook Resource = "notebooks" ) +// ResourceUnknown is returned when parsing a raw UUID without resource context. +const ResourceUnknown Resource = "" + // Valid reports whether r is a known resource type. func (r Resource) Valid() bool { switch r { case ResourceArea, ResourceGoal, ResourceTask, ResourceNote, ResourcePerson, ResourceNotebook: return true + case ResourceUnknown: + return false } return false @@ -63,13 +70,13 @@ func ValidateUUID(id string) error { return nil } -// ParseDeepLink extracts resource type and UUID from a Lunatask deep link +// ParseReference extracts resource type and UUID from a Lunatask deep link // or plain UUID. Accepts "lunatask://tasks/uuid" or plain UUID strings. -// When a plain UUID is provided, the resource type will be empty. +// When a plain UUID is provided, the resource type will be ResourceUnknown. // The UUID is validated to be a well-formed UUID string. -func ParseDeepLink(input string) (Resource, string, error) { +func ParseReference(input string) (Resource, string, error) { if input == "" { - return "", "", fmt.Errorf("%w: empty input", ErrInvalidDeepLink) + return "", "", fmt.Errorf("%w: empty input", ErrInvalidReference) } // Check for lunatask:// prefix @@ -77,10 +84,10 @@ func ParseDeepLink(input string) (Resource, string, error) { if !strings.HasPrefix(input, prefix) { // Treat as plain UUID, validate format if err := ValidateUUID(input); err != nil { - return "", "", err + return "", "", fmt.Errorf("%w %q: expected UUID or lunatask:// deep link", ErrInvalidReference, input) } - return "", input, nil + return ResourceUnknown, input, nil } // Remove prefix and split @@ -105,6 +112,13 @@ func ParseDeepLink(input string) (Resource, string, error) { return resource, id, nil } +// ParseDeepLink extracts resource type and UUID from input. +// +// Deprecated: Use ParseReference instead. +func ParseDeepLink(input string) (Resource, string, error) { + return ParseReference(input) +} + // BuildDeepLink constructs a Lunatask deep link from resource type and ID. // Returns "lunatask://resource/uuid". The ID is validated to be a well-formed UUID. func BuildDeepLink(resource Resource, id string) (string, error) { diff --git a/deeplink_test.go b/deeplink_test.go index 80e52c3ab601071f9ae976bccef20c53b3185568..d51ecbe56d471e5b0c4374a6adae8c6a56c66b00 100644 --- a/deeplink_test.go +++ b/deeplink_test.go @@ -11,7 +11,7 @@ import ( lunatask "git.secluded.site/go-lunatask" ) -func TestParseDeepLink(t *testing.T) { +func TestParseReference(t *testing.T) { t.Parallel() validUUID := "12345678-1234-1234-1234-123456789012" @@ -32,51 +32,70 @@ func TestParseDeepLink(t *testing.T) { {"person_link", "lunatask://people/" + validUUID, lunatask.ResourcePerson, validUUID, nil}, {"notebook_link", "lunatask://notebooks/" + validUUID, lunatask.ResourceNotebook, validUUID, nil}, - // Plain UUIDs - {"plain_uuid", validUUID2, "", validUUID2, nil}, - {"uuid_only", validUUID, "", validUUID, nil}, + // Plain UUIDs return ResourceUnknown + {"plain_uuid", validUUID2, lunatask.ResourceUnknown, validUUID2, nil}, + {"uuid_only", validUUID, lunatask.ResourceUnknown, validUUID, nil}, // Invalid inputs - {"empty", "", "", "", lunatask.ErrInvalidDeepLink}, + {"empty", "", "", "", lunatask.ErrInvalidReference}, {"invalid_resource", "lunatask://invalid/" + validUUID, "", "", lunatask.ErrInvalidResource}, {"missing_uuid", "lunatask://tasks/", "", "", lunatask.ErrInvalidDeepLink}, {"missing_resource", "lunatask:///" + validUUID, "", "", lunatask.ErrInvalidDeepLink}, {"malformed", "lunatask://tasks", "", "", lunatask.ErrInvalidDeepLink}, {"invalid_uuid_in_link", "lunatask://tasks/not-a-uuid", "", "", lunatask.ErrInvalidUUID}, - {"invalid_plain_uuid", "not-a-valid-uuid", "", "", lunatask.ErrInvalidUUID}, + {"invalid_plain_uuid", "not-a-valid-uuid", "", "", lunatask.ErrInvalidReference}, } for _, testCase := range tests { t.Run(testCase.name, func(t *testing.T) { t.Parallel() - resource, uuid, err := lunatask.ParseDeepLink(testCase.input) + resource, uuid, err := lunatask.ParseReference(testCase.input) if testCase.wantErr != nil { if !errors.Is(err, testCase.wantErr) { - t.Errorf("ParseDeepLink(%q) error = %v, want %v", testCase.input, err, testCase.wantErr) + t.Errorf("ParseReference(%q) error = %v, want %v", testCase.input, err, testCase.wantErr) } return } if err != nil { - t.Errorf("ParseDeepLink(%q) unexpected error = %v", testCase.input, err) + t.Errorf("ParseReference(%q) unexpected error = %v", testCase.input, err) return } if resource != testCase.wantResource { - t.Errorf("ParseDeepLink(%q) resource = %q, want %q", testCase.input, resource, testCase.wantResource) + t.Errorf("ParseReference(%q) resource = %q, want %q", testCase.input, resource, testCase.wantResource) } if uuid != testCase.wantUUID { - t.Errorf("ParseDeepLink(%q) uuid = %q, want %q", testCase.input, uuid, testCase.wantUUID) + t.Errorf("ParseReference(%q) uuid = %q, want %q", testCase.input, uuid, testCase.wantUUID) } }) } } +func TestParseDeepLink_Deprecated(t *testing.T) { + t.Parallel() + + validUUID := "12345678-1234-1234-1234-123456789012" + + resource, uuid, err := lunatask.ParseDeepLink("lunatask://tasks/" + validUUID) + if err != nil { + t.Errorf("ParseDeepLink() unexpected error = %v", err) + } + + if resource != lunatask.ResourceTask { + t.Errorf("ParseDeepLink() resource = %q, want %q", resource, lunatask.ResourceTask) + } + + if uuid != validUUID { + t.Errorf("ParseDeepLink() uuid = %q, want %q", uuid, validUUID) + } +} + func TestBuildDeepLink(t *testing.T) { t.Parallel()