Skip to content

Commit 68b6683

Browse files
maxatomefredbi
authored andcommitted
feat(format): add uuid7 string format support
Signed-off-by: Maxime Soulé <[email protected]>
1 parent ad12a81 commit 68b6683

File tree

9 files changed

+204
-4
lines changed

9 files changed

+204
-4
lines changed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ It also provides convenient extensions to go-openapi users.
3535
- mac (e.g "01:02:03:04:05:06")
3636
- rgbcolor (e.g. "rgb(100,100,100)")
3737
- ssn
38-
- uuid, uuid3, uuid4, uuid5
38+
- uuid, uuid3, uuid4, uuid5, uuid7
3939
- cidr (e.g. "192.0.2.1/24", "2001:db8:a0b:12f0::1/32")
4040
- ulid (e.g. "00000PP9HGSBSSDZ1JTEXBJ0PW", [spec](https://github.com/ulid/spec))
4141

@@ -81,7 +81,8 @@ List of defined types:
8181
- SSN
8282
- URI
8383
- UUID
84-
- UUID3
85-
- UUID4
86-
- UUID5
84+
- [UUID3](https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-3)
85+
- [UUID4](https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-4)
86+
- [UUID5](https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-5)
87+
- [UUID7](https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-7)
8788
- [ULID](https://github.com/ulid/spec)

conv/default.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,21 @@ func UUID5Value(v *strfmt.UUID5) strfmt.UUID5 {
184184
return *v
185185
}
186186

187+
// UUID7 returns a pointer to of the UUID7 value passed in.
188+
func UUID7(v strfmt.UUID7) *strfmt.UUID7 {
189+
return &v
190+
}
191+
192+
// UUID7Value returns the value of the UUID7 pointer passed in or
193+
// the default value if the pointer is nil.
194+
func UUID7Value(v *strfmt.UUID7) strfmt.UUID7 {
195+
if v == nil {
196+
return strfmt.UUID7("")
197+
}
198+
199+
return *v
200+
}
201+
187202
// ISBN returns a pointer to of the ISBN value passed in.
188203
func ISBN(v strfmt.ISBN) *strfmt.ISBN {
189204
return &v

conv/default_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ func TestUUID5Value(t *testing.T) {
8080
assert.Equal(t, value, UUID5Value(&value))
8181
}
8282

83+
func TestUUID7Value(t *testing.T) {
84+
assert.Equal(t, strfmt.UUID7(""), UUID7Value(nil))
85+
value := strfmt.UUID7("foo")
86+
assert.Equal(t, value, UUID7Value(&value))
87+
}
88+
8389
func TestISBNValue(t *testing.T) {
8490
assert.Equal(t, strfmt.ISBN(""), ISBNValue(nil))
8591
value := strfmt.ISBN("foo")

default.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ const (
346346
uuidV3 = 3
347347
uuidV4 = 4
348348
uuidV5 = 5
349+
uuidV7 = 7
349350
)
350351

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

370+
// IsUUID7 returns true is the string matches a UUID v7, upper case is allowed
371+
func IsUUID7(str string) bool {
372+
id, err := uuid.Parse(str)
373+
return err == nil && id.Version() == uuid.Version(uuidV7)
374+
}
375+
369376
// IsEmail validates an email address.
370377
func IsEmail(str string) bool {
371378
addr, e := mail.ParseAddress(str)
@@ -394,6 +401,7 @@ func init() {
394401
// - uuid3
395402
// - uuid4
396403
// - uuid5
404+
// - uuid7
397405
u := URI("")
398406
Default.Add("uri", &u, isRequestURI)
399407

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

438+
uid7 := UUID7("")
439+
Default.Add("uuid7", &uid7, IsUUID7)
440+
430441
isbn := ISBN("")
431442
Default.Add("isbn", &isbn, func(str string) bool { return isISBN10(str) || isISBN13(str) })
432443

@@ -1320,6 +1331,78 @@ func (u *UUID5) DeepCopy() *UUID5 {
13201331
return out
13211332
}
13221333

1334+
// UUID7 represents a uuid7 string format
1335+
//
1336+
// swagger:strfmt uuid7
1337+
type UUID7 string
1338+
1339+
// MarshalText turns this instance into text
1340+
func (u UUID7) MarshalText() ([]byte, error) {
1341+
return []byte(string(u)), nil
1342+
}
1343+
1344+
// UnmarshalText hydrates this instance from text
1345+
func (u *UUID7) UnmarshalText(data []byte) error { // validation is performed later on
1346+
*u = UUID7(string(data))
1347+
return nil
1348+
}
1349+
1350+
// Scan read a value from a database driver
1351+
func (u *UUID7) Scan(raw any) error {
1352+
switch v := raw.(type) {
1353+
case []byte:
1354+
*u = UUID7(string(v))
1355+
case string:
1356+
*u = UUID7(v)
1357+
default:
1358+
return fmt.Errorf("cannot sql.Scan() strfmt.UUID7 from: %#v: %w", v, ErrFormat)
1359+
}
1360+
1361+
return nil
1362+
}
1363+
1364+
// Value converts a value to a database driver value
1365+
func (u UUID7) Value() (driver.Value, error) {
1366+
return driver.Value(string(u)), nil
1367+
}
1368+
1369+
func (u UUID7) String() string {
1370+
return string(u)
1371+
}
1372+
1373+
// MarshalJSON returns the UUID as JSON
1374+
func (u UUID7) MarshalJSON() ([]byte, error) {
1375+
return json.Marshal(string(u))
1376+
}
1377+
1378+
// UnmarshalJSON sets the UUID from JSON
1379+
func (u *UUID7) UnmarshalJSON(data []byte) error {
1380+
if string(data) == jsonNull {
1381+
return nil
1382+
}
1383+
var ustr string
1384+
if err := json.Unmarshal(data, &ustr); err != nil {
1385+
return err
1386+
}
1387+
*u = UUID7(ustr)
1388+
return nil
1389+
}
1390+
1391+
// DeepCopyInto copies the receiver and writes its value into out.
1392+
func (u *UUID7) DeepCopyInto(out *UUID7) {
1393+
*out = *u
1394+
}
1395+
1396+
// DeepCopy copies the receiver into a new UUID7.
1397+
func (u *UUID7) DeepCopy() *UUID7 {
1398+
if u == nil {
1399+
return nil
1400+
}
1401+
out := new(UUID7)
1402+
u.DeepCopyInto(out)
1403+
return out
1404+
}
1405+
13231406
// ISBN represents an isbn string format
13241407
//
13251408
// swagger:strfmt isbn

default_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,48 @@ func TestFormatUUID5(t *testing.T) {
372372
assert.Equal(t, UUID5(""), uuidZero)
373373
}
374374

375+
func validUUID7s() []string {
376+
other7 := uuid.Must(uuid.NewV7())
377+
378+
return []string{
379+
other7.String(),
380+
strings.ReplaceAll(other7.String(), "-", ""),
381+
}
382+
}
383+
384+
func invalidUUID7s() []string {
385+
other3 := uuid.NewMD5(uuid.NameSpaceURL, []byte("somewhere.com"))
386+
other4 := uuid.Must(uuid.NewRandom())
387+
other5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhereelse.com"))
388+
389+
return []string{
390+
"not-a-uuid",
391+
other3.String(),
392+
other4.String(),
393+
strings.ReplaceAll(other3.String(), "-", ""),
394+
strings.ReplaceAll(other4.String(), "-", ""),
395+
strings.Replace(other3.String(), "-", "", 2),
396+
strings.Replace(other4.String(), "-", "", 2),
397+
strings.Replace(other5.String(), "-", "", 2),
398+
}
399+
}
400+
401+
func TestFormatUUID7(t *testing.T) {
402+
first7 := uuid.Must(uuid.NewV7())
403+
str := first7.String()
404+
uuid7 := UUID7(str)
405+
testStringFormat(t, &uuid7, "uuid7", str,
406+
validUUID7s(),
407+
invalidUUID7s(),
408+
)
409+
410+
// special case for zero UUID
411+
var uuidZero UUID7
412+
err := uuidZero.UnmarshalJSON([]byte(jsonNull))
413+
require.NoError(t, err)
414+
assert.Equal(t, UUID7(""), uuidZero)
415+
}
416+
375417
func validUUIDs() []string {
376418
other3 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhereelse.com"))
377419
other4 := uuid.Must(uuid.NewRandom())
@@ -820,6 +862,23 @@ func TestDeepCopyUUID5(t *testing.T) {
820862
assert.Nil(t, out3)
821863
}
822864

865+
func TestDeepCopyUUID7(t *testing.T) {
866+
first7 := uuid.Must(uuid.NewV7())
867+
uuid7 := UUID7(first7.String())
868+
in := &uuid7
869+
870+
out := new(UUID7)
871+
in.DeepCopyInto(out)
872+
assert.Equal(t, in, out)
873+
874+
out2 := in.DeepCopy()
875+
assert.Equal(t, in, out2)
876+
877+
var inNil *UUID7
878+
out3 := inNil.DeepCopy()
879+
assert.Nil(t, out3)
880+
}
881+
823882
func TestDeepCopyISBN(t *testing.T) {
824883
isbn := ISBN("0321751043")
825884
in := &isbn

format.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ func (f *defaultFormats) MapStructureHookFunc() mapstructure.DecodeHookFunc {
118118
return UUID4(data), nil
119119
case "uuid5":
120120
return UUID5(data), nil
121+
case "uuid7":
122+
return UUID7(data), nil
121123
case "hostname":
122124
return Hostname(data), nil
123125
case "ipv4":

format_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ type testStruct struct {
138138
UUID3 UUID3 `json:"uuid3,omitempty"`
139139
UUID4 UUID4 `json:"uuid4,omitempty"`
140140
UUID5 UUID5 `json:"uuid5,omitempty"`
141+
UUID7 UUID7 `json:"uuid7,omitempty"`
141142
Hn Hostname `json:"hn,omitempty"`
142143
Ipv4 IPv4 `json:"ipv4,omitempty"`
143144
Ipv6 IPv6 `json:"ipv6,omitempty"`
@@ -167,6 +168,7 @@ func TestDecodeHook(t *testing.T) {
167168
"uuid3": "bcd02e22-68f0-3046-a512-327cca9def8f",
168169
"uuid4": "025b0d74-00a2-4048-bf57-227c5111bb34",
169170
"uuid5": "886313e1-3b8a-5372-9b90-0c9aee199e5d",
171+
"uuid7": "019a15e6-cd5e-7204-b11b-12075f4c8a25",
170172
"hn": "somewhere.com",
171173
"ipv4": "192.168.254.1",
172174
"ipv6": "::1",
@@ -199,6 +201,7 @@ func TestDecodeHook(t *testing.T) {
199201
UUID3: UUID3("bcd02e22-68f0-3046-a512-327cca9def8f"),
200202
UUID4: UUID4("025b0d74-00a2-4048-bf57-227c5111bb34"),
201203
UUID5: UUID5("886313e1-3b8a-5372-9b90-0c9aee199e5d"),
204+
UUID7: UUID7("019a15e6-cd5e-7204-b11b-12075f4c8a25"),
202205
Hn: Hostname("somewhere.com"),
203206
Ipv4: IPv4("192.168.254.1"),
204207
Ipv6: IPv6("::1"),

mongo.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ var (
4747
_ bson.Unmarshaler = (*UUID4)(nil)
4848
_ bson.Marshaler = UUID5("")
4949
_ bson.Unmarshaler = (*UUID5)(nil)
50+
_ bson.Marshaler = UUID7("")
51+
_ bson.Unmarshaler = (*UUID7)(nil)
5052
_ bson.Marshaler = ISBN("")
5153
_ bson.Unmarshaler = (*ISBN)(nil)
5254
_ bson.Marshaler = ISBN10("")
@@ -452,6 +454,25 @@ func (u *UUID5) UnmarshalBSON(data []byte) error {
452454
return fmt.Errorf("couldn't unmarshal bson bytes as UUID5: %w", ErrFormat)
453455
}
454456

457+
// MarshalBSON document from this value
458+
func (u UUID7) MarshalBSON() ([]byte, error) {
459+
return bson.Marshal(bson.M{"data": u.String()})
460+
}
461+
462+
// UnmarshalBSON document into this value
463+
func (u *UUID7) UnmarshalBSON(data []byte) error {
464+
var m bson.M
465+
if err := bson.Unmarshal(data, &m); err != nil {
466+
return err
467+
}
468+
469+
if ud, ok := m["data"].(string); ok {
470+
*u = UUID7(ud)
471+
return nil
472+
}
473+
return fmt.Errorf("couldn't unmarshal bson bytes as UUID7: %w", ErrFormat)
474+
}
475+
455476
// MarshalBSON document from this value
456477
func (u ISBN) MarshalBSON() ([]byte, error) {
457478
return bson.Marshal(bson.M{"data": u.String()})

mongo_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,16 @@ func TestFormatBSON(t *testing.T) {
203203
)
204204
})
205205

206+
t.Run("with UUID7", func(t *testing.T) {
207+
first7 := uuid.Must(uuid.NewV7())
208+
str := first7.String()
209+
uuid7 := UUID7(str)
210+
testBSONStringFormat(t, &uuid7, "uuid7", str,
211+
validUUID7s(),
212+
invalidUUID7s(),
213+
)
214+
})
215+
206216
t.Run("with UUID", func(t *testing.T) {
207217
first5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhere.com"))
208218
other5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhereelse.com"))

0 commit comments

Comments
 (0)