From 9ee7366e66c9ad96bab89139418a713dc584ae29 Mon Sep 17 00:00:00 2001 From: Lucas JAHIER Date: Tue, 12 Dec 2023 01:19:23 +0100 Subject: [PATCH] feat: Validate UUID without creating new UUID (#141) * feat: Validate UUID without creating new UUID * fix: update comment --- uuid.go | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++ uuid_test.go | 29 ++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/uuid.go b/uuid.go index dc75f7d..5232b48 100644 --- a/uuid.go +++ b/uuid.go @@ -186,6 +186,59 @@ func Must(uuid UUID, err error) UUID { return uuid } +// Validate returns an error if s is not a properly formatted UUID in one of the following formats: +// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} +// It returns an error if the format is invalid, otherwise nil. +func Validate(s string) error { + switch len(s) { + // Standard UUID format + case 36: + + // UUID with "urn:uuid:" prefix + case 36 + 9: + if !strings.EqualFold(s[:9], "urn:uuid:") { + return fmt.Errorf("invalid urn prefix: %q", s[:9]) + } + s = s[9:] + + // UUID enclosed in braces + case 36 + 2: + if s[0] != '{' || s[len(s)-1] != '}' { + return fmt.Errorf("invalid bracketed UUID format") + } + s = s[1 : len(s)-1] + + // UUID without hyphens + case 32: + for i := 0; i < len(s); i += 2 { + _, ok := xtob(s[i], s[i+1]) + if !ok { + return errors.New("invalid UUID format") + } + } + + default: + return invalidLengthError{len(s)} + } + + // Check for standard UUID format + if len(s) == 36 { + if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { + return errors.New("invalid UUID format") + } + for _, x := range []int{0, 2, 4, 6, 9, 11, 14, 16, 19, 21, 24, 26, 28, 30, 32, 34} { + if _, ok := xtob(s[x], s[x+1]); !ok { + return errors.New("invalid UUID format") + } + } + } + + return nil +} + // String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx // , or "" if uuid is invalid. func (uuid UUID) String() string { diff --git a/uuid_test.go b/uuid_test.go index fc97f2d..83cee2c 100644 --- a/uuid_test.go +++ b/uuid_test.go @@ -6,6 +6,7 @@ package uuid import ( "bytes" + "errors" "fmt" "os" "runtime" @@ -605,6 +606,34 @@ func FuzzFromBytes(f *testing.F) { }) } +// TestValidate checks various scenarios for the Validate function +func TestValidate(t *testing.T) { + testCases := []struct { + name string + input string + expect error + }{ + {"Valid UUID", "123e4567-e89b-12d3-a456-426655440000", nil}, + {"Valid UUID with URN", "urn:uuid:123e4567-e89b-12d3-a456-426655440000", nil}, + {"Valid UUID with Braces", "{123e4567-e89b-12d3-a456-426655440000}", nil}, + {"Valid UUID No Hyphens", "123e4567e89b12d3a456426655440000", nil}, + {"Invalid UUID", "invalid-uuid", errors.New("invalid UUID length: 12")}, + {"Invalid Length", "123", fmt.Errorf("invalid UUID length: %d", len("123"))}, + {"Invalid URN Prefix", "urn:test:123e4567-e89b-12d3-a456-426655440000", fmt.Errorf("invalid urn prefix: %q", "urn:test:")}, + {"Invalid Brackets", "[123e4567-e89b-12d3-a456-426655440000]", fmt.Errorf("invalid bracketed UUID format")}, + {"Invalid UUID Format", "12345678gabc1234abcd1234abcd1234", fmt.Errorf("invalid UUID format")}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := Validate(tc.input) + if (err != nil) != (tc.expect != nil) || (err != nil && err.Error() != tc.expect.Error()) { + t.Errorf("Validate(%q) = %v, want %v", tc.input, err, tc.expect) + } + }) + } +} + var asString = "f47ac10b-58cc-0372-8567-0e02b2c3d479" var asBytes = []byte(asString)