forked from timescale/tsbs
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
23ab8ea
commit 6f17b72
Showing
2 changed files
with
179 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package publicid | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
|
||
nanoid "github.com/matoous/go-nanoid/v2" | ||
) | ||
|
||
const ( | ||
Alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" // base58 | ||
Length = 12 | ||
) | ||
|
||
// New generates a unique public ID. | ||
func New() (string, error) { | ||
return nanoid.Generate(Alphabet, Length) | ||
} | ||
|
||
// NewWithPrefix generates a unique public ID with a given prefix. | ||
func NewWithPrefix(prefix string) (string, error) { | ||
id, err := New() | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
return fmt.Sprintf("%s_%s", prefix, id), nil | ||
} | ||
|
||
// Must is the same as New, but panics on error. | ||
func Must() string { | ||
return nanoid.MustGenerate(Alphabet, Length) | ||
} | ||
|
||
// MustWithPrefix is the same as NewWithPrefix, but panics on error. | ||
func MustWithPrefix(prefix string) string { | ||
id := Must() | ||
return fmt.Sprintf("%s_%s", prefix, id) | ||
} | ||
|
||
// Validate checks if a given field name’s public ID value is valid according to | ||
// the constraints defined by package publicid. | ||
func Validate(fieldName, id string) error { | ||
if id == "" { | ||
return fmt.Errorf("%s cannot be blank", fieldName) | ||
} | ||
|
||
if len(id) != Length { | ||
return fmt.Errorf("%s should be %d characters long", fieldName, Length) | ||
} | ||
|
||
if strings.Trim(id, Alphabet) != "" { | ||
return fmt.Errorf("%s has invalid characters", fieldName) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// ValidateWithPrefix checks if a given field name’s public ID value with a prefix is valid according to | ||
// the constraints defined by package publicid. | ||
func ValidateWithPrefix(fieldName, id, prefix string) error { | ||
if id == "" { | ||
return fmt.Errorf("%s cannot be blank", fieldName) | ||
} | ||
|
||
expectedPrefix := fmt.Sprintf("%s_", prefix) | ||
if !strings.HasPrefix(id, expectedPrefix) { | ||
return fmt.Errorf("%s must start with the prefix %s", fieldName, prefix) | ||
} | ||
|
||
// Remove the prefix and the underscore from the ID for further validation. | ||
trimmedID := strings.TrimPrefix(id, expectedPrefix) | ||
|
||
return Validate(fieldName, trimmedID) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package publicid | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNew(t *testing.T) { | ||
id, err := New() | ||
assert.NoError(t, err) | ||
assert.Len(t, id, Length, fmt.Sprintf("ID should be %d characters long", Length)) | ||
assert.Equal(t, "", strings.Trim(id, Alphabet), "ID should only contain characters from the Alphabet") | ||
} | ||
|
||
func TestNewWithPrefix(t *testing.T) { | ||
prefix := "test" | ||
id, err := NewWithPrefix(prefix) | ||
assert.NoError(t, err) | ||
assert.True(t, strings.HasPrefix(id, prefix+"_"), "ID should have the correct prefix") | ||
assert.Len(t, id, len(prefix)+1+Length, fmt.Sprintf("ID should be %d characters long", len(prefix)+1+Length)) | ||
assert.Equal(t, "", strings.Trim(id[len(prefix)+1:], Alphabet), "ID should only contain characters from the Alphabet after the prefix") | ||
} | ||
|
||
func TestMust(t *testing.T) { | ||
assert.NotPanics(t, func() { | ||
id := Must() | ||
assert.Len(t, id, Length, fmt.Sprintf("ID should be %d characters long", Length)) | ||
assert.Equal(t, "", strings.Trim(id, Alphabet), "ID should only contain characters from the Alphabet") | ||
}) | ||
} | ||
|
||
func TestMustWithPrefix(t *testing.T) { | ||
prefix := "must" | ||
assert.NotPanics(t, func() { | ||
id := MustWithPrefix(prefix) | ||
assert.True(t, strings.HasPrefix(id, prefix+"_"), "ID should have the correct prefix") | ||
assert.Len(t, id, len(prefix)+1+Length, fmt.Sprintf("ID should be %d characters long", len(prefix)+1+Length)) | ||
assert.Equal(t, "", strings.Trim(id[len(prefix)+1:], Alphabet), "ID should only contain characters from the Alphabet after the prefix") | ||
}) | ||
} | ||
|
||
func TestValidate(t *testing.T) { | ||
validId, _ := New() | ||
|
||
testCases := []struct { | ||
name string | ||
fieldName string | ||
id string | ||
wantErr bool | ||
errContains string | ||
}{ | ||
{"Valid ID", "field", validId, false, ""}, | ||
{"Empty ID", "field", "", true, "cannot be blank"}, | ||
{"Incorrect Length", "field", "123", true, "should be 12 characters long"}, | ||
{"Invalid Characters", "field", "invalidchar!", true, "has invalid characters"}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
err := Validate(tc.fieldName, tc.id) | ||
if tc.wantErr { | ||
assert.Error(t, err) | ||
assert.Contains(t, err.Error(), tc.errContains) | ||
} else { | ||
assert.NoError(t, err) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestValidateWithPrefix(t *testing.T) { | ||
prefix := "prefix" | ||
validIdWithPrefix, _ := NewWithPrefix(prefix) | ||
|
||
testCases := []struct { | ||
name string | ||
fieldName string | ||
id string | ||
prefix string | ||
wantErr bool | ||
errContains string | ||
}{ | ||
{"Valid ID with Prefix", "field", validIdWithPrefix, prefix, false, ""}, | ||
{"Empty ID", "field", "", prefix, true, "cannot be blank"}, | ||
{"No Prefix", "field", validIdWithPrefix[len(prefix)+1:], prefix, true, "must start with the prefix"}, | ||
{"Wrong Prefix", "field", "wrong_" + validIdWithPrefix, prefix, true, "must start with the prefix"}, | ||
{"Invalid Characters with Prefix", "field", prefix + "_invalidchar!", prefix, true, "has invalid characters"}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
err := ValidateWithPrefix(tc.fieldName, tc.id, tc.prefix) | ||
if tc.wantErr { | ||
assert.Error(t, err) | ||
assert.Contains(t, err.Error(), tc.errContains) | ||
} else { | ||
assert.NoError(t, err) | ||
} | ||
}) | ||
} | ||
} |