Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ It also provides convenient extensions to go-openapi users.
- mac (e.g "01:02:03:04:05:06")
- rgbcolor (e.g. "rgb(100,100,100)")
- ssn
- uuid, uuid3, uuid4, uuid5
- uuid, uuid3, uuid4, uuid5, uuid7
- cidr (e.g. "192.0.2.1/24", "2001:db8:a0b:12f0::1/32")
- ulid (e.g. "00000PP9HGSBSSDZ1JTEXBJ0PW", [spec](https://github.com/ulid/spec))

Expand Down Expand Up @@ -81,7 +81,8 @@ List of defined types:
- SSN
- URI
- UUID
- UUID3
- UUID4
- UUID5
- [UUID3](https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-3)
- [UUID4](https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-4)
- [UUID5](https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-5)
- [UUID7](https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-7)
- [ULID](https://github.com/ulid/spec)
15 changes: 15 additions & 0 deletions conv/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,21 @@ func UUID5Value(v *strfmt.UUID5) strfmt.UUID5 {
return *v
}

// UUID7 returns a pointer to of the UUID7 value passed in.
func UUID7(v strfmt.UUID7) *strfmt.UUID7 {
return &v
}

// UUID7Value returns the value of the UUID7 pointer passed in or
// the default value if the pointer is nil.
func UUID7Value(v *strfmt.UUID7) strfmt.UUID7 {
if v == nil {
return strfmt.UUID7("")
}

return *v
}

// ISBN returns a pointer to of the ISBN value passed in.
func ISBN(v strfmt.ISBN) *strfmt.ISBN {
return &v
Expand Down
6 changes: 6 additions & 0 deletions conv/default_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ func TestUUID5Value(t *testing.T) {
assert.Equal(t, value, UUID5Value(&value))
}

func TestUUID7Value(t *testing.T) {
assert.Equal(t, strfmt.UUID7(""), UUID7Value(nil))
value := strfmt.UUID7("foo")
assert.Equal(t, value, UUID7Value(&value))
}

func TestISBNValue(t *testing.T) {
assert.Equal(t, strfmt.ISBN(""), ISBNValue(nil))
value := strfmt.ISBN("foo")
Expand Down
83 changes: 83 additions & 0 deletions default.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ const (
uuidV3 = 3
uuidV4 = 4
uuidV5 = 5
uuidV7 = 7
)

// IsUUID3 returns true is the string matches a UUID v3, upper case is allowed
Expand All @@ -366,6 +367,12 @@ func IsUUID5(str string) bool {
return err == nil && id.Version() == uuid.Version(uuidV5)
}

// IsUUID7 returns true is the string matches a UUID v7, upper case is allowed
func IsUUID7(str string) bool {
id, err := uuid.Parse(str)
return err == nil && id.Version() == uuid.Version(uuidV7)
}

// IsEmail validates an email address.
func IsEmail(str string) bool {
addr, e := mail.ParseAddress(str)
Expand Down Expand Up @@ -394,6 +401,7 @@ func init() {
// - uuid3
// - uuid4
// - uuid5
// - uuid7
u := URI("")
Default.Add("uri", &u, isRequestURI)

Expand Down Expand Up @@ -427,6 +435,9 @@ func init() {
uid5 := UUID5("")
Default.Add("uuid5", &uid5, IsUUID5)

uid7 := UUID7("")
Default.Add("uuid7", &uid7, IsUUID7)

isbn := ISBN("")
Default.Add("isbn", &isbn, func(str string) bool { return isISBN10(str) || isISBN13(str) })

Expand Down Expand Up @@ -1320,6 +1331,78 @@ func (u *UUID5) DeepCopy() *UUID5 {
return out
}

// UUID7 represents a uuid7 string format
//
// swagger:strfmt uuid7
type UUID7 string

// MarshalText turns this instance into text
func (u UUID7) MarshalText() ([]byte, error) {
return []byte(string(u)), nil
}

// UnmarshalText hydrates this instance from text
func (u *UUID7) UnmarshalText(data []byte) error { // validation is performed later on
*u = UUID7(string(data))
return nil
}

// Scan read a value from a database driver
func (u *UUID7) Scan(raw any) error {
switch v := raw.(type) {
case []byte:
*u = UUID7(string(v))
case string:
*u = UUID7(v)
default:
return fmt.Errorf("cannot sql.Scan() strfmt.UUID7 from: %#v: %w", v, ErrFormat)
}

return nil
}

// Value converts a value to a database driver value
func (u UUID7) Value() (driver.Value, error) {
return driver.Value(string(u)), nil
}

func (u UUID7) String() string {
return string(u)
}

// MarshalJSON returns the UUID as JSON
func (u UUID7) MarshalJSON() ([]byte, error) {
return json.Marshal(string(u))
}

// UnmarshalJSON sets the UUID from JSON
func (u *UUID7) UnmarshalJSON(data []byte) error {
if string(data) == jsonNull {
return nil
}
var ustr string
if err := json.Unmarshal(data, &ustr); err != nil {
return err
}
*u = UUID7(ustr)
return nil
}

// DeepCopyInto copies the receiver and writes its value into out.
func (u *UUID7) DeepCopyInto(out *UUID7) {
*out = *u
}

// DeepCopy copies the receiver into a new UUID7.
func (u *UUID7) DeepCopy() *UUID7 {
if u == nil {
return nil
}
out := new(UUID7)
u.DeepCopyInto(out)
return out
}

// ISBN represents an isbn string format
//
// swagger:strfmt isbn
Expand Down
59 changes: 59 additions & 0 deletions default_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,48 @@ func TestFormatUUID5(t *testing.T) {
assert.Equal(t, UUID5(""), uuidZero)
}

func validUUID7s() []string {
other7 := uuid.Must(uuid.NewV7())

return []string{
other7.String(),
strings.ReplaceAll(other7.String(), "-", ""),
}
}

func invalidUUID7s() []string {
other3 := uuid.NewMD5(uuid.NameSpaceURL, []byte("somewhere.com"))
other4 := uuid.Must(uuid.NewRandom())
other5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhereelse.com"))

return []string{
"not-a-uuid",
other3.String(),
other4.String(),
strings.ReplaceAll(other3.String(), "-", ""),
strings.ReplaceAll(other4.String(), "-", ""),
strings.Replace(other3.String(), "-", "", 2),
strings.Replace(other4.String(), "-", "", 2),
strings.Replace(other5.String(), "-", "", 2),
}
}

func TestFormatUUID7(t *testing.T) {
first7 := uuid.Must(uuid.NewV7())
str := first7.String()
uuid7 := UUID7(str)
testStringFormat(t, &uuid7, "uuid7", str,
validUUID7s(),
invalidUUID7s(),
)

// special case for zero UUID
var uuidZero UUID7
err := uuidZero.UnmarshalJSON([]byte(jsonNull))
require.NoError(t, err)
assert.Equal(t, UUID7(""), uuidZero)
}

func validUUIDs() []string {
other3 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhereelse.com"))
other4 := uuid.Must(uuid.NewRandom())
Expand Down Expand Up @@ -820,6 +862,23 @@ func TestDeepCopyUUID5(t *testing.T) {
assert.Nil(t, out3)
}

func TestDeepCopyUUID7(t *testing.T) {
first7 := uuid.Must(uuid.NewV7())
uuid7 := UUID7(first7.String())
in := &uuid7

out := new(UUID7)
in.DeepCopyInto(out)
assert.Equal(t, in, out)

out2 := in.DeepCopy()
assert.Equal(t, in, out2)

var inNil *UUID7
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}

func TestDeepCopyISBN(t *testing.T) {
isbn := ISBN("0321751043")
in := &isbn
Expand Down
2 changes: 2 additions & 0 deletions format.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ func (f *defaultFormats) MapStructureHookFunc() mapstructure.DecodeHookFunc {
return UUID4(data), nil
case "uuid5":
return UUID5(data), nil
case "uuid7":
return UUID7(data), nil
case "hostname":
return Hostname(data), nil
case "ipv4":
Expand Down
3 changes: 3 additions & 0 deletions format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ type testStruct struct {
UUID3 UUID3 `json:"uuid3,omitempty"`
UUID4 UUID4 `json:"uuid4,omitempty"`
UUID5 UUID5 `json:"uuid5,omitempty"`
UUID7 UUID7 `json:"uuid7,omitempty"`
Hn Hostname `json:"hn,omitempty"`
Ipv4 IPv4 `json:"ipv4,omitempty"`
Ipv6 IPv6 `json:"ipv6,omitempty"`
Expand Down Expand Up @@ -167,6 +168,7 @@ func TestDecodeHook(t *testing.T) {
"uuid3": "bcd02e22-68f0-3046-a512-327cca9def8f",
"uuid4": "025b0d74-00a2-4048-bf57-227c5111bb34",
"uuid5": "886313e1-3b8a-5372-9b90-0c9aee199e5d",
"uuid7": "019a15e6-cd5e-7204-b11b-12075f4c8a25",
"hn": "somewhere.com",
"ipv4": "192.168.254.1",
"ipv6": "::1",
Expand Down Expand Up @@ -199,6 +201,7 @@ func TestDecodeHook(t *testing.T) {
UUID3: UUID3("bcd02e22-68f0-3046-a512-327cca9def8f"),
UUID4: UUID4("025b0d74-00a2-4048-bf57-227c5111bb34"),
UUID5: UUID5("886313e1-3b8a-5372-9b90-0c9aee199e5d"),
UUID7: UUID7("019a15e6-cd5e-7204-b11b-12075f4c8a25"),
Hn: Hostname("somewhere.com"),
Ipv4: IPv4("192.168.254.1"),
Ipv6: IPv6("::1"),
Expand Down
21 changes: 21 additions & 0 deletions mongo.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ var (
_ bson.Unmarshaler = (*UUID4)(nil)
_ bson.Marshaler = UUID5("")
_ bson.Unmarshaler = (*UUID5)(nil)
_ bson.Marshaler = UUID7("")
_ bson.Unmarshaler = (*UUID7)(nil)
_ bson.Marshaler = ISBN("")
_ bson.Unmarshaler = (*ISBN)(nil)
_ bson.Marshaler = ISBN10("")
Expand Down Expand Up @@ -452,6 +454,25 @@ func (u *UUID5) UnmarshalBSON(data []byte) error {
return fmt.Errorf("couldn't unmarshal bson bytes as UUID5: %w", ErrFormat)
}

// MarshalBSON document from this value
func (u UUID7) MarshalBSON() ([]byte, error) {
return bson.Marshal(bson.M{"data": u.String()})
}

// UnmarshalBSON document into this value
func (u *UUID7) UnmarshalBSON(data []byte) error {
var m bson.M
if err := bson.Unmarshal(data, &m); err != nil {
return err
}

if ud, ok := m["data"].(string); ok {
*u = UUID7(ud)
return nil
}
return fmt.Errorf("couldn't unmarshal bson bytes as UUID7: %w", ErrFormat)
}

// MarshalBSON document from this value
func (u ISBN) MarshalBSON() ([]byte, error) {
return bson.Marshal(bson.M{"data": u.String()})
Expand Down
10 changes: 10 additions & 0 deletions mongo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,16 @@ func TestFormatBSON(t *testing.T) {
)
})

t.Run("with UUID7", func(t *testing.T) {
first7 := uuid.Must(uuid.NewV7())
str := first7.String()
uuid7 := UUID7(str)
testBSONStringFormat(t, &uuid7, "uuid7", str,
validUUID7s(),
invalidUUID7s(),
)
})

t.Run("with UUID", func(t *testing.T) {
first5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhere.com"))
other5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhereelse.com"))
Expand Down
Loading