Repository: go-openapi/strfmt
Pull request #80 (2021-02-20 21:43:39)

Add support for ULID string format
More details about format:
  https://github.com/ulid/spec

It uses binary underlying implementation:
  https://github.com/oklog/ulid

Resolves: #77 


elipavlov: @casualjim @fredbi , could you please look at this PR.

This PR uses binary implementation of ULID as the underlying (embedded) type.
It works much faster and I decided that it would be more helpful to have this implementation out-of-the-box after unmarshalling a request.
But tradeoff is to have one new dependency.


There is another implementation, which relies only on regexp and string representation: #79 

Maybe it would be enough to leave regexp version and leave the rest to developers themselves.

Benchmark comparison for binary and regexp implementation was:
```sh
goos: darwin
goarch: amd64
pkg: github.com/go-openapi/strfmt
BenchmarkParseStrict-4   	46144773	        25.7 ns/op
BenchmarkRegexpIsULID-4      	 2117212	         585 ns/op
PASS
ok  	github.com/go-openapi/strfmt	4.021s
PASS
ok  	github.com/go-openapi/strfmt/conv	0.127s
```

I'm going to add more tests to this PR if we choose it instead of #79.
casualjim: it's fine to add a dependency. My vote goes to this one too
codecov[bot]: # [Codecov](https://codecov.io/gh/go-openapi/strfmt/pull/80?src=pr&el=h1) Report
> Merging [#80](https://codecov.io/gh/go-openapi/strfmt/pull/80?src=pr&el=desc) (3815ce4) into [master](https://codecov.io/gh/go-openapi/strfmt/commit/a4e46eae40fd7f77151fc130d3b9cb2dc9ebb548?el=desc) (a4e46ea) will **increase** coverage by `0.37%`.
> The diff coverage is `87.95%`.

[![Impacted file tree graph](https://codecov.io/gh/go-openapi/strfmt/pull/80/graphs/tree.svg?width=650&height=150&src=pr&token=1NsRXHf1sd)](https://codecov.io/gh/go-openapi/strfmt/pull/80?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master      #80      +/-   ##
==========================================
+ Coverage   81.67%   82.05%   +0.37%     
==========================================
  Files          10       12       +2     
  Lines        1288     1371      +83     
==========================================
+ Hits         1052     1125      +73     
- Misses        164      171       +7     
- Partials       72       75       +3     
```


| [Impacted Files](https://codecov.io/gh/go-openapi/strfmt/pull/80?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [format.go](https://codecov.io/gh/go-openapi/strfmt/pull/80/diff?src=pr&el=tree#diff-Zm9ybWF0Lmdv) | `73.79% <60.00%> (-0.50%)` | :arrow_down: |
| [ulid.go](https://codecov.io/gh/go-openapi/strfmt/pull/80/diff?src=pr&el=tree#diff-dWxpZC5nbw==) | `89.18% <89.18%> (ø)` | |
| [conv/ulid.go](https://codecov.io/gh/go-openapi/strfmt/pull/80/diff?src=pr&el=tree#diff-Y29udi91bGlkLmdv) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/go-openapi/strfmt/pull/80?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/go-openapi/strfmt/pull/80?src=pr&el=footer). Last update [a4e46ea...3815ce4](https://codecov.io/gh/go-openapi/strfmt/pull/80?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/README.md b/README.md
index b2645ce..0cf89d7 100644
--- a/README.md
+++ b/README.md
@@ -38,6 +38,7 @@ It also provides convenient extensions to go-openapi users.
   - ssn
   - uuid, uuid3, uuid4, uuid5
   - cidr (e.g. "192.0.2.1/24", "2001:db8:a0b:12f0::1/32")
+  - ulid (e.g. "00000PP9HGSBSSDZ1JTEXBJ0PW", [spec](https://github.com/ulid/spec))
 
 > NOTE: as the name stands for, this package is intended to support string formatting only.
 > It does not provide validation for numerical values with swagger format extension for JSON types "number" or
@@ -84,3 +85,4 @@ List of defined types:
 - UUID3
 - UUID4
 - UUID5
+- [ULID](https://github.com/ulid/spec)
diff --git a/conv/ulid.go b/conv/ulid.go
new file mode 100644
index 0000000..112bec3
--- /dev/null
+++ b/conv/ulid.go
@@ -0,0 +1,18 @@
+package conv
+
+import "github.com/go-openapi/strfmt"
+
+// ULID returns a pointer to of the ULID value passed in.
+func ULID(v strfmt.ULID) *strfmt.ULID {
+	return &v
+}
+
+// ULIDValue returns the value of the ULID pointer passed in or
+// the default value if the pointer is nil.
+func ULIDValue(v *strfmt.ULID) strfmt.ULID {
+	if v == nil {
+		return strfmt.ULID{}
+	}
+
+	return *v
+}
diff --git a/format.go b/format.go
index 3f93a72..13a1626 100644
--- a/format.go
+++ b/format.go
@@ -160,6 +160,12 @@ func (f *defaultFormats) MapStructureHookFunc() mapstructure.DecodeHookFunc {
 					return Base64(data.(string)), nil
 				case "password":
 					return Password(data.(string)), nil
+				case "ulid":
+					ulid, err := ParseULID(data.(string))
+					if err != nil {
+						return nil, err
+					}
+					return ulid, nil
 				default:
 					return nil, errors.InvalidTypeName(v.Name)
 				}
diff --git a/go.mod b/go.mod
index 6eaa6d7..9a6e874 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,7 @@ require (
 	github.com/go-openapi/errors v0.19.8
 	github.com/google/uuid v1.1.1
 	github.com/mitchellh/mapstructure v1.3.3
+	github.com/oklog/ulid v1.3.1
 	github.com/stretchr/testify v1.6.1
 	go.mongodb.org/mongo-driver v1.4.3
 )
diff --git a/go.sum b/go.sum
index a2d3603..c1a3678 100644
--- a/go.sum
+++ b/go.sum
@@ -63,6 +63,8 @@ github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
 github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
diff --git a/ulid.go b/ulid.go
new file mode 100644
index 0000000..6ea2e35
--- /dev/null
+++ b/ulid.go
@@ -0,0 +1,221 @@
+package strfmt
+
+import (
+	cryptorand "crypto/rand"
+	"database/sql/driver"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"sync"
+
+	"github.com/oklog/ulid"
+	"go.mongodb.org/mongo-driver/bson"
+)
+
+// ULID represents a ulid string format
+// ref:
+//   https://github.com/ulid/spec
+// impl:
+//   https://github.com/oklog/ulid
+//
+// swagger:strfmt ulid
+type ULID struct {
+	ulid.ULID
+}
+
+var (
+	ulidEntropyPool = sync.Pool{
+		New: func() interface{} {
+			return cryptorand.Reader
+		},
+	}
+
+	ULIDScanDefaultFunc = func(raw interface{}) (ULID, error) {
+		var u ULID = NewULIDZero()
+		switch x := raw.(type) {
+		case nil:
+			// zerp ulid
+			return u, nil
+		case string:
+			if x == "" {
+				// zero ulid
+				return u, nil
+			}
+			return u, u.UnmarshalText([]byte(x))
+		case []byte:
+			return u, u.UnmarshalText(x)
+		}
+
+		return u, fmt.Errorf("cannot sql.Scan() strfmt.ULID from: %#v: %w", raw, ulid.ErrScanValue)
+	}
+
+	// ULIDScanOverrideFunc allows you to override the Scan method of the ULID type
+	ULIDScanOverrideFunc = ULIDScanDefaultFunc
+
+	ULIDValueDefaultFunc = func(u ULID) (driver.Value, error) {
+		return driver.Value(u.String()), nil
+	}
+
+	// ULIDValueOverrideFunc allows you to override the Value method of the ULID type
+	ULIDValueOverrideFunc = ULIDValueDefaultFunc
+)
+
+func init() {
+	// register formats in the default registry:
+	//   - ulid
+	ulid := ULID{}
+	Default.Add("ulid", &ulid, IsULID)
+}
+
+// IsULID checks if provided string is ULID format
+// Be noticed that this function considers overflowed ULID as non-ulid.
+// For more details see https://github.com/ulid/spec
+func IsULID(str string) bool {
+	_, err := ulid.ParseStrict(str)
+	return err == nil
+}
+
+// ParseULID parses a string that represents an valid ULID
+func ParseULID(str string) (ULID, error) {
+	var u ULID
+
+	return u, u.UnmarshalText([]byte(str))
+}
+
+// NewULIDZero returns a zero valued ULID type
+func NewULIDZero() ULID {
+	return ULID{}
+}
+
+// NewULID generates new unique ULID value and a error if any
+func NewULID() (u ULID, err error) {
+	entropy := ulidEntropyPool.Get().(io.Reader)
+
+	id, err := ulid.New(ulid.Now(), entropy)
+	if err != nil {
+		return u, err
+	}
+	ulidEntropyPool.Put(entropy)
+
+	u.ULID = id
+	return u, nil
+}
+
+// GetULID returns underlying instance of ULID
+func (u *ULID) GetULID() interface{} {
+	return u.ULID
+}
+
+// MarshalText returns this instance into text
+func (u ULID) MarshalText() ([]byte, error) {
+	return u.ULID.MarshalText()
+}
+
+// UnmarshalText hydrates this instance from text
+func (u *ULID) UnmarshalText(data []byte) error { // validation is performed later on
+	return u.ULID.UnmarshalText(data)
+}
+
+// Scan reads a value from a database driver
+func (u *ULID) Scan(raw interface{}) error {
+	ul, err := ULIDScanOverrideFunc(raw)
+	if err == nil {
+		*u = ul
+	}
+	return err
+}
+
+// Value converts a value to a database driver value
+func (u ULID) Value() (driver.Value, error) {
+	return ULIDValueOverrideFunc(u)
+}
+
+func (u ULID) String() string {
+	return u.ULID.String()
+}
+
+// MarshalJSON returns the ULID as JSON
+func (u ULID) MarshalJSON() ([]byte, error) {
+	return json.Marshal(u.String())
+}
+
+// UnmarshalJSON sets the ULID from JSON
+func (u *ULID) UnmarshalJSON(data []byte) error {
+	if string(data) == jsonNull {
+		return nil
+	}
+	var ustr string
+	if err := json.Unmarshal(data, &ustr); err != nil {
+		return err
+	}
+	id, err := ulid.ParseStrict(ustr)
+	if err != nil {
+		return fmt.Errorf("couldn't parse JSON value as ULID: %w", err)
+	}
+	u.ULID = id
+	return nil
+}
+
+// MarshalBSON document from this value
+func (u ULID) MarshalBSON() ([]byte, error) {
+	return bson.Marshal(bson.M{"data": u.String()})
+}
+
+// UnmarshalBSON document into this value
+func (u *ULID) 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 {
+		id, err := ulid.ParseStrict(ud)
+		if err != nil {
+			return fmt.Errorf("couldn't parse bson bytes as ULID: %w", err)
+		}
+		u.ULID = id
+		return nil
+	}
+	return errors.New("couldn't unmarshal bson bytes as ULID")
+}
+
+// DeepCopyInto copies the receiver and writes its value into out.
+func (u *ULID) DeepCopyInto(out *ULID) {
+	*out = *u
+}
+
+// DeepCopy copies the receiver into a new ULID.
+func (u *ULID) DeepCopy() *ULID {
+	if u == nil {
+		return nil
+	}
+	out := new(ULID)
+	u.DeepCopyInto(out)
+	return out
+}
+
+// GobEncode implements the gob.GobEncoder interface.
+func (u ULID) GobEncode() ([]byte, error) {
+	return u.ULID.MarshalBinary()
+}
+
+// GobDecode implements the gob.GobDecoder interface.
+func (u *ULID) GobDecode(data []byte) error {
+	return u.ULID.UnmarshalBinary(data)
+}
+
+// MarshalBinary implements the encoding.BinaryMarshaler interface.
+func (u ULID) MarshalBinary() ([]byte, error) {
+	return u.ULID.MarshalBinary()
+}
+
+// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
+func (u *ULID) UnmarshalBinary(data []byte) error {
+	return u.ULID.UnmarshalBinary(data)
+}
+
+// Equal checks if two ULID instances are equal by their underlying type
+func (u ULID) Equal(other ULID) bool {
+	return u.ULID == other.ULID
+}


Test patch:
diff --git a/conv/ulid_test.go b/conv/ulid_test.go
new file mode 100644
index 0000000..62525f9
--- /dev/null
+++ b/conv/ulid_test.go
@@ -0,0 +1,22 @@
+package conv
+
+import (
+	"testing"
+
+	"github.com/go-openapi/strfmt"
+	"github.com/stretchr/testify/assert"
+)
+
+const testUlid = string("01EYXZVGBHG26MFTG4JWR4K558")
+
+func TestULIDValue(t *testing.T) {
+	assert.Equal(t, strfmt.ULID{}, ULIDValue(nil))
+
+	value := strfmt.ULID{}
+	err := value.UnmarshalText([]byte(testUlid))
+	assert.NoError(t, err)
+	assert.Equal(t, value, ULIDValue(&value))
+
+	ulidRef := ULID(value)
+	assert.Equal(t, &value, ulidRef)
+}
diff --git a/format_test.go b/format_test.go
index dc9afed..5e06b47 100644
--- a/format_test.go
+++ b/format_test.go
@@ -151,6 +151,7 @@ type testStruct struct {
 	Rgbcolor   RGBColor   `json:"rgbcolor,omitempty"`
 	B64        Base64     `json:"b64,omitempty"`
 	Pw         Password   `json:"pw,omitempty"`
+	ULID       ULID       `json:"ulid,omitempty"`
 }
 
 func TestDecodeHook(t *testing.T) {
@@ -179,11 +180,13 @@ func TestDecodeHook(t *testing.T) {
 		"ssn":        "111-11-1111",
 		"creditcard": "4111-1111-1111-1111",
 		"b64":        "ZWxpemFiZXRocG9zZXk=",
+		"ulid":       "7ZZZZZZZZZZZZZZZZZZZZZZZZZ",
 	}
 
 	date, _ := time.Parse(RFC3339FullDate, "2014-12-15")
 	dur, _ := ParseDuration("5s")
 	dt, _ := ParseDateTime("2012-03-02T15:06:05.999999999Z")
+	ulid, _ := ParseULID("7ZZZZZZZZZZZZZZZZZZZZZZZZZ")
 
 	exp := &testStruct{
 		D:          Date(date),
@@ -209,6 +212,7 @@ func TestDecodeHook(t *testing.T) {
 		Rgbcolor:   RGBColor("rgb(255,255,255)"),
 		B64:        Base64("ZWxpemFiZXRocG9zZXk="),
 		Pw:         Password("super secret stuff here"),
+		ULID:       ulid,
 	}
 
 	test := new(testStruct)
@@ -261,3 +265,42 @@ func TestDecodeDateTimeHook(t *testing.T) {
 		})
 	}
 }
+
+func TestDecode_ULID_Hook_Negative(t *testing.T) {
+	t.Parallel()
+	testCases := []struct {
+		Name  string
+		Input string
+	}{
+		{
+			"empty string for ulid",
+			"",
+		},
+		{
+			"invalid non empty ulid",
+			"8000000000YYYYYYYYYYYYYYYY",
+		},
+	}
+	registry := NewFormats()
+	type layout struct {
+		ULID *ULID `json:"ulid,omitempty"`
+	}
+	for i := range testCases {
+		tc := testCases[i]
+		t.Run(tc.Name, func(t *testing.T) {
+			t.Parallel()
+			test := new(layout)
+			cfg := &mapstructure.DecoderConfig{
+				DecodeHook:       registry.MapStructureHookFunc(),
+				WeaklyTypedInput: false,
+				Result:           test,
+			}
+			d, err := mapstructure.NewDecoder(cfg)
+			assert.NoError(t, err)
+			input := make(map[string]interface{})
+			input["ulid"] = tc.Input
+			err = d.Decode(input)
+			assert.Error(t, err, "error expected got none")
+		})
+	}
+}
diff --git a/ulid_test.go b/ulid_test.go
new file mode 100644
index 0000000..71eda74
--- /dev/null
+++ b/ulid_test.go
@@ -0,0 +1,342 @@
+package strfmt
+
+import (
+	"bytes"
+	"database/sql/driver"
+	"encoding/gob"
+	"fmt"
+	"sync"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"go.mongodb.org/mongo-driver/bson"
+)
+
+const testUlid = string("01EYXZVGBHG26MFTG4JWR4K558")
+const testUlidAlt = string("01EYXZW663G7PYHVSQ8WTMDA67")
+
+var testUlidOverrideMtx sync.Mutex
+var testUlidOverrideValMtx sync.Mutex
+
+func TestFormatULID_Text(t *testing.T) {
+	t.Parallel()
+
+	t.Run("positive", func(t *testing.T) {
+		t.Parallel()
+		ulid, err := ParseULID(testUlid)
+		assert.NoError(t, err)
+
+		res, err := ulid.MarshalText()
+		assert.NoError(t, err)
+		assert.Equal(t, testUlid, string(res))
+
+		ulid2, _ := ParseULID(testUlidAlt)
+		assert.NoError(t, err)
+
+		what := []byte(testUlid)
+		err = ulid2.UnmarshalText(what)
+		assert.NoError(t, err)
+		assert.Equal(t, testUlid, ulid2.String())
+	})
+	t.Run("negative", func(t *testing.T) {
+		t.Parallel()
+		ulid, err := ParseULID(testUlid)
+		assert.NoError(t, err)
+
+		what := []byte("00000000-0000-0000-0000-000000000000")
+
+		err = ulid.UnmarshalText(what)
+		assert.Error(t, err)
+	})
+}
+
+func TestFormatULID_BSON(t *testing.T) {
+	t.Parallel()
+	t.Run("positive", func(t *testing.T) {
+		t.Parallel()
+		ulid, _ := ParseULID(testUlid)
+
+		bsonData, err := bson.Marshal(&ulid)
+		assert.NoError(t, err)
+
+		var ulidUnmarshaled ULID
+		err = bson.Unmarshal(bsonData, &ulidUnmarshaled)
+		assert.NoError(t, err)
+		assert.Equal(t, ulid, ulidUnmarshaled)
+
+		// Check value marshaling explicitly
+		m := bson.M{"data": ulid}
+		bsonData, err = bson.Marshal(&m)
+		assert.NoError(t, err)
+
+		var mUnmarshaled bson.M
+		err = bson.Unmarshal(bsonData, &mUnmarshaled)
+		assert.NoError(t, err)
+
+		data, ok := m["data"].(ULID)
+		assert.Equal(t, true, ok)
+		assert.Equal(t, ulid, data)
+	})
+	t.Run("negative", func(t *testing.T) {
+		t.Parallel()
+		uuid := UUID("00000000-0000-0000-0000-000000000000")
+		bsonData, err := bson.Marshal(&uuid)
+		assert.NoError(t, err)
+
+		var ulidUnmarshaled ULID
+		err = bson.Unmarshal(bsonData, &ulidUnmarshaled)
+		assert.Error(t, err)
+	})
+}
+
+func TestFormatULID_JSON(t *testing.T) {
+	t.Parallel()
+	t.Run("positive", func(t *testing.T) {
+		t.Parallel()
+		ulid, err := ParseULID(testUlid)
+		assert.NoError(t, err)
+
+		whatStr := fmt.Sprintf(`"%s"`, testUlidAlt)
+		what := []byte(whatStr)
+		err = ulid.UnmarshalJSON(what)
+		assert.NoError(t, err)
+		assert.Equal(t, testUlidAlt, ulid.String())
+
+		data, err := ulid.MarshalJSON()
+		assert.NoError(t, err)
+		assert.Equal(t, whatStr, string(data))
+	})
+	t.Run("null", func(t *testing.T) {
+		t.Parallel()
+		ulid, err := ParseULID(testUlid)
+		assert.NoError(t, err)
+
+		err = ulid.UnmarshalJSON([]byte("null"))
+		assert.Nil(t, err)
+	})
+	t.Run("negative", func(t *testing.T) {
+		t.Parallel()
+		// Check UnmarshalJSON failure with no lexed items
+		ulid := NewULIDZero()
+		err := ulid.UnmarshalJSON([]byte("zorg emperor"))
+		assert.Error(t, err)
+
+		// Check lexer failure
+		err = ulid.UnmarshalJSON([]byte(`"zorg emperor"`))
+		assert.Error(t, err)
+	})
+}
+
+func TestFormatULID_Scan(t *testing.T) {
+	t.Parallel()
+	t.Run("db.Scan", func(t *testing.T) {
+		t.Parallel()
+		testUlidOverrideMtx.Lock()
+		defer testUlidOverrideMtx.Unlock()
+
+		srcUlid := testUlidAlt
+
+		ulid, err := ParseULID(testUlid)
+		assert.NoError(t, err)
+
+		err = ulid.Scan(srcUlid)
+		assert.NoError(t, err)
+		assert.Equal(t, srcUlid, ulid.String())
+
+		ulid, _ = ParseULID(testUlid)
+		err = ulid.Scan([]byte(srcUlid))
+		assert.NoError(t, err)
+		assert.Equal(t, srcUlid, ulid.String())
+	})
+	t.Run("db.Scan_Failed", func(t *testing.T) {
+		t.Parallel()
+		testUlidOverrideMtx.Lock()
+		defer testUlidOverrideMtx.Unlock()
+
+		ulid, err := ParseULID(testUlid)
+		zero := NewULIDZero()
+		assert.NoError(t, err)
+
+		err = ulid.Scan(nil)
+		assert.NoError(t, err)
+		assert.Equal(t, zero, ulid)
+
+		err = ulid.Scan("")
+		assert.NoError(t, err)
+		assert.Equal(t, zero, ulid)
+
+		err = ulid.Scan(int64(0))
+		assert.Error(t, err)
+
+		err = ulid.Scan(float64(0))
+		assert.Error(t, err)
+	})
+	t.Run("db.Value", func(t *testing.T) {
+		t.Parallel()
+		testUlidOverrideValMtx.Lock()
+		defer testUlidOverrideValMtx.Unlock()
+
+		ulid, err := ParseULID(testUlid)
+		assert.NoError(t, err)
+
+		val, err := ulid.Value()
+		assert.NoError(t, err)
+
+		assert.EqualValues(t, testUlid, val)
+	})
+	t.Run("override.Scan", func(t *testing.T) {
+		t.Parallel()
+		testUlidOverrideMtx.Lock()
+		defer testUlidOverrideMtx.Unlock()
+
+		ulid, err := ParseULID(testUlid)
+		assert.NoError(t, err)
+		ulid2, err := ParseULID(testUlidAlt)
+		assert.NoError(t, err)
+
+		ULIDScanOverrideFunc = func(raw interface{}) (ULID, error) {
+			var u ULID = NewULIDZero()
+			switch x := raw.(type) {
+			case [16]byte:
+				return u, u.ULID.UnmarshalBinary(x[:])
+			case int: // just for linter
+				return u, fmt.Errorf("cannot sql.Scan() strfmt.ULID from: %#v", raw)
+			}
+			return u, fmt.Errorf("cannot sql.Scan() strfmt.ULID from: %#v", raw)
+		}
+
+		// get underlying binary implementation which is actually [16]byte
+		bytes := [16]byte(ulid.ULID)
+
+		err = ulid2.Scan(bytes)
+		assert.NoError(t, err)
+		assert.Equal(t, ulid2, ulid)
+		assert.Equal(t, ulid2.String(), ulid.String())
+
+		// check other default cases became unreachable
+		err = ulid2.Scan(testUlid)
+		assert.Error(t, err)
+
+		// return default Scan method
+		ULIDScanOverrideFunc = ULIDScanDefaultFunc
+		err = ulid2.Scan(testUlid)
+		assert.NoError(t, err)
+		assert.Equal(t, ulid2.String(), testUlid)
+	})
+	t.Run("override.Value", func(t *testing.T) {
+		t.Parallel()
+		testUlidOverrideValMtx.Lock()
+		defer testUlidOverrideValMtx.Unlock()
+
+		ulid, err := ParseULID(testUlid)
+		assert.NoError(t, err)
+		ulid2, err := ParseULID(testUlid)
+		assert.NoError(t, err)
+
+		ULIDValueOverrideFunc = func(u ULID) (driver.Value, error) {
+			bytes := [16]byte(u.ULID)
+			return driver.Value(bytes), nil
+		}
+
+		exp := [16]byte(ulid2.ULID)
+		val, err := ulid.Value()
+		assert.NoError(t, err)
+
+		assert.EqualValues(t, exp, val)
+
+		// return default Value method
+		ULIDValueOverrideFunc = ULIDValueDefaultFunc
+
+		val, err = ulid.Value()
+		assert.NoError(t, err)
+
+		assert.EqualValues(t, testUlid, val)
+	})
+}
+
+func TestFormatULID_DeepCopy(t *testing.T) {
+	ulid, err := ParseULID(testUlid)
+	assert.NoError(t, err)
+	in := &ulid
+
+	out := new(ULID)
+	in.DeepCopyInto(out)
+	assert.Equal(t, in, out)
+
+	out2 := in.DeepCopy()
+	assert.Equal(t, in, out2)
+
+	var inNil *ULID
+	out3 := inNil.DeepCopy()
+	assert.Nil(t, out3)
+}
+
+func TestFormatULID_GobEncoding(t *testing.T) {
+	ulid, err := ParseULID(testUlid)
+	assert.NoError(t, err)
+
+	b := bytes.Buffer{}
+	enc := gob.NewEncoder(&b)
+	err = enc.Encode(ulid)
+	assert.NoError(t, err)
+	assert.NotEmpty(t, b.Bytes())
+
+	var result ULID
+
+	dec := gob.NewDecoder(&b)
+	err = dec.Decode(&result)
+	assert.NoError(t, err)
+	assert.Equal(t, ulid, result)
+	assert.Equal(t, ulid.String(), result.String())
+}
+
+func TestFormatULID_NewULID_and_Equal(t *testing.T) {
+	t.Parallel()
+
+	ulid1, err := NewULID()
+	assert.NoError(t, err)
+
+	ulid2, err := NewULID()
+	assert.NoError(t, err)
+
+	assert.True(t, ulid1.Equal(ulid1), "ULID instances should be equal")
+	assert.False(t, ulid1.Equal(ulid2), "ULID instances should not be equal")
+
+	ulidZero := NewULIDZero()
+	ulidZero2 := NewULIDZero()
+	assert.True(t, ulidZero.Equal(ulidZero2), "ULID instances should be equal")
+}
+
+func TestIsULID(t *testing.T) {
+	t.Parallel()
+
+	tcases := []struct {
+		ulid   string
+		expect bool
+	}{
+		{ulid: "01EYXZVGBHG26MFTG4JWR4K558", expect: true},
+		{ulid: "01EYXZW663G7PYHVSQ8WTMDA67", expect: true},
+		{ulid: "7ZZZZZZZZZ0000000000000000", expect: true},
+		{ulid: "00000000000000000000000000", expect: true},
+		{ulid: "7ZZZZZZZZZZZZZZZZZZZZZZZZZ", expect: true},
+		{ulid: "not-a-ulid", expect: false},
+		{ulid: "8000000000FJ2MMFJ3ATV3XB2C", expect: false},
+		{ulid: "81EYY0NEYJZZZZZZZZZZZZZZZZ", expect: false},
+		{ulid: "7ZZZZZZZZZ000000000000000U", expect: false},
+		{ulid: "7ZZZZZZZZZ000000000000000L", expect: false},
+		{ulid: "7ZZZZZZZZZ000000000000000O", expect: false},
+		{ulid: "7ZZZZZZZZZ000000000000000I", expect: false},
+	}
+	for _, tcase := range tcases {
+		tc := tcase
+		t.Run(fmt.Sprintf("%s:%t", tc.ulid, tc.expect), func(t *testing.T) {
+			t.Parallel()
+			if tc.expect {
+				assert.True(t, IsULID(tc.ulid))
+			} else {
+				assert.False(t, IsULID(tc.ulid))
+			}
+		})
+	}
+
+}