diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml
index 9ce426e..918b484 100644
--- a/.github/dependabot.yaml
+++ b/.github/dependabot.yaml
@@ -1,7 +1,8 @@
version: 2
updates:
- package-ecosystem: "github-actions"
- directory: "/"
+ directories:
+ - "**/*"
schedule:
interval: "weekly"
day: "friday"
@@ -27,7 +28,8 @@ updates:
# 2. golang.org-dependencies are auto-merged
# 3. go-openapi patch updates are auto-merged. Minor/major version updates require a manual merge.
# 4. other dependencies require a manual merge
- directory: "/"
+ directories:
+ - "**/*"
schedule:
interval: "weekly"
day: "friday"
diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml
index 48e5a57..3b108fe 100644
--- a/.github/workflows/auto-merge.yml
+++ b/.github/workflows/auto-merge.yml
@@ -11,5 +11,5 @@ jobs:
permissions:
contents: write
pull-requests: write
- uses: go-openapi/ci-workflows/.github/workflows/auto-merge.yml@435746a4b72b06b6b6989c309fd2ad8150dbae5a # v0.2.11
+ uses: go-openapi/ci-workflows/.github/workflows/auto-merge.yml@34a5baa33361844b1d2c70cd4548e3cea83529d9 # v0.2.13
secrets: inherit
diff --git a/.github/workflows/bump-release.yml b/.github/workflows/bump-release.yml
index 57f7f75..0805a80 100644
--- a/.github/workflows/bump-release.yml
+++ b/.github/workflows/bump-release.yml
@@ -6,21 +6,15 @@ permissions:
on:
workflow_dispatch:
inputs:
- bump-patch:
- description: Bump a patch version release
- type: boolean
+ bump-type:
+ description: Type of bump (patch, minor, major)
+ type: choice
+ options:
+ - patch
+ - minor
+ - major
+ default: patch
required: false
- default: true
- bump-minor:
- description: Bump a minor version release
- type: boolean
- required: false
- default: false
- bump-major:
- description: Bump a major version release
- type: boolean
- required: false
- default: false
tag-message-title:
description: Tag message title to prepend to the release notes
required: false
@@ -36,11 +30,10 @@ jobs:
bump-release:
permissions:
contents: write
- uses: go-openapi/ci-workflows/.github/workflows/bump-release.yml@435746a4b72b06b6b6989c309fd2ad8150dbae5a # v0.2.11
+ pull-requests: write
+ uses: go-openapi/ci-workflows/.github/workflows/bump-release-monorepo.yml@34a5baa33361844b1d2c70cd4548e3cea83529d9 # v0.2.13
with:
- bump-patch: ${{ inputs.bump-patch }}
- bump-minor: ${{ inputs.bump-minor }}
- bump-major: ${{ inputs.bump-major }}
+ bump-type: ${{ inputs.bump-type }}
tag-message-title: ${{ inputs.tag-message-title }}
tag-message-body: ${{ inputs.tag-message-body }}
secrets: inherit
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 32fb5e3..12b2c72 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -18,5 +18,5 @@ jobs:
permissions:
contents: read
security-events: write
- uses: go-openapi/ci-workflows/.github/workflows/codeql.yml@435746a4b72b06b6b6989c309fd2ad8150dbae5a # v0.2.11
+ uses: go-openapi/ci-workflows/.github/workflows/codeql.yml@34a5baa33361844b1d2c70cd4548e3cea83529d9 # v0.2.13
secrets: inherit
diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml
index 33a469f..e2dd5e1 100644
--- a/.github/workflows/contributors.yml
+++ b/.github/workflows/contributors.yml
@@ -14,5 +14,5 @@ jobs:
permissions:
pull-requests: write
contents: write
- uses: go-openapi/ci-workflows/.github/workflows/contributors.yml@435746a4b72b06b6b6989c309fd2ad8150dbae5a # v0.2.11
+ uses: go-openapi/ci-workflows/.github/workflows/contributors.yml@34a5baa33361844b1d2c70cd4548e3cea83529d9 # v0.2.13
secrets: inherit
diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml
index 514d891..9ba00d6 100644
--- a/.github/workflows/go-test.yml
+++ b/.github/workflows/go-test.yml
@@ -13,5 +13,5 @@ on:
jobs:
test:
- uses: go-openapi/ci-workflows/.github/workflows/go-test.yml@435746a4b72b06b6b6989c309fd2ad8150dbae5a # v0.2.11
+ uses: go-openapi/ci-workflows/.github/workflows/go-test-monorepo.yml@34a5baa33361844b1d2c70cd4548e3cea83529d9 # v0.2.13
secrets: inherit
diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml
index 0634152..f39cf85 100644
--- a/.github/workflows/integration-test.yml
+++ b/.github/workflows/integration-test.yml
@@ -53,7 +53,7 @@ jobs:
- uses: actions/setup-go@v5
with:
- go-version-file: go.mod
+ go-version: stable
- name: Run integration tests
working-directory: internal/testintegration
diff --git a/.github/workflows/scanner.yml b/.github/workflows/scanner.yml
index b936bc1..f2f53df 100644
--- a/.github/workflows/scanner.yml
+++ b/.github/workflows/scanner.yml
@@ -15,5 +15,5 @@ jobs:
permissions:
contents: read
security-events: write
- uses: go-openapi/ci-workflows/.github/workflows/scanner.yml@435746a4b72b06b6b6989c309fd2ad8150dbae5a # V0.2.11
+ uses: go-openapi/ci-workflows/.github/workflows/scanner.yml@34a5baa33361844b1d2c70cd4548e3cea83529d9 # v0.2.13
secrets: inherit
diff --git a/.github/workflows/tag-release.yml b/.github/workflows/tag-release.yml
index b834743..dddab0a 100644
--- a/.github/workflows/tag-release.yml
+++ b/.github/workflows/tag-release.yml
@@ -13,7 +13,8 @@ jobs:
name: Create release
permissions:
contents: write
- uses: go-openapi/ci-workflows/.github/workflows/release.yml@435746a4b72b06b6b6989c309fd2ad8150dbae5a # v0.2.11
+ uses: go-openapi/ci-workflows/.github/workflows/release.yml@34a5baa33361844b1d2c70cd4548e3cea83529d9 # v0.2.13
with:
tag: ${{ github.ref_name }}
+ is-monorepo: true
secrets: inherit
diff --git a/README.md b/README.md
index 20935fb..0cab77b 100644
--- a/README.md
+++ b/README.md
@@ -24,6 +24,13 @@ You may join the discord community by clicking the invite link on the discord ba
Or join our Slack channel: [![Slack Channel][slack-logo]![slack-badge]][slack-url]
+* **2026-03-07** : v0.26.0 **dropped dependency to the mongodb driver**
+ * mongodb users can still use this package without any change
+ * however, we have frozen the back-compatible support for mongodb driver at v2.5.0
+ * users who want to keep-up with future evolutions (possibly incompatible) of this driver
+ can do so by adding a blank import in their program: `import _ "github.com/go-openapi/strfmt/enable/mongodb"`.
+ This will switch the behavior to the actual driver, which remains regularly updated as an independent module.
+
## Status
API is stable.
@@ -132,8 +139,9 @@ List of defined types:
All format types implement the `database/sql` interfaces `sql.Scanner` and `driver.Valuer`,
so they work out of the box with Go's standard `database/sql` package and any SQL driver.
-All format types also implement BSON marshaling/unmarshaling for use with MongoDB
-(via [`go.mongodb.org/mongo-driver/v2`](https://pkg.go.dev/go.mongodb.org/mongo-driver/v2)).
+All format types also implement BSON marshaling/unmarshaling for use with MongoDB.
+By default, a built-in minimal codec is used (compatible with mongo-driver v2.5.0).
+For full driver support, add `import _ "github.com/go-openapi/strfmt/enable/mongodb"`.
> **MySQL / MariaDB caveat for `DateTime`:**
> The `go-sql-driver/mysql` driver has hard-coded handling for `time.Time` but does not
diff --git a/bson.go b/bson.go
index ee07c64..16a83f6 100644
--- a/bson.go
+++ b/bson.go
@@ -5,9 +5,9 @@ package strfmt
import (
"database/sql/driver"
+ "encoding/hex"
+ "encoding/json"
"fmt"
-
- bsonprim "go.mongodb.org/mongo-driver/v2/bson"
)
func init() { //nolint:gochecknoinits // registers bsonobjectid format in the default registry
@@ -17,45 +17,46 @@ func init() { //nolint:gochecknoinits // registers bsonobjectid format in the de
// IsBSONObjectID returns true when the string is a valid BSON [ObjectId].
func IsBSONObjectID(str string) bool {
- _, err := bsonprim.ObjectIDFromHex(str)
+ _, err := objectIDFromHex(str)
return err == nil
}
-// ObjectId represents a BSON object ID (alias to [primitive.ObjectID]).
+// ObjectId represents a BSON object ID (a 12-byte unique identifier).
//
-// swagger:[strfmt] bsonobjectid.
-type ObjectId bsonprim.ObjectID //nolint:revive
+// swagger:strfmt bsonobjectid.
+type ObjectId [12]byte //nolint:revive
+
+// nilObjectID is the zero-value ObjectId.
+var nilObjectID ObjectId //nolint:gochecknoglobals // package-level sentinel
-// NewObjectId creates a [ObjectId] from a Hex String.
+// NewObjectId creates a [ObjectId] from a hexadecimal String.
func NewObjectId(hex string) ObjectId { //nolint:revive
- oid, err := bsonprim.ObjectIDFromHex(hex)
+ oid, err := objectIDFromHex(hex)
if err != nil {
panic(err)
}
- return ObjectId(oid)
+ return oid
}
// MarshalText turns this instance into text.
func (id ObjectId) MarshalText() ([]byte, error) {
- oid := bsonprim.ObjectID(id)
- if oid == bsonprim.NilObjectID {
+ if id == nilObjectID {
return nil, nil
}
- return []byte(oid.Hex()), nil
+ return []byte(id.Hex()), nil
}
// UnmarshalText hydrates this instance from text.
func (id *ObjectId) UnmarshalText(data []byte) error { // validation is performed later on
if len(data) == 0 {
- *id = ObjectId(bsonprim.NilObjectID)
+ *id = nilObjectID
return nil
}
- oidstr := string(data)
- oid, err := bsonprim.ObjectIDFromHex(oidstr)
+ oid, err := objectIDFromHex(string(data))
if err != nil {
return err
}
- *id = ObjectId(oid)
+ *id = oid
return nil
}
@@ -76,25 +77,34 @@ func (id *ObjectId) Scan(raw any) error {
// Value converts a value to a database driver value.
func (id ObjectId) Value() (driver.Value, error) {
- return driver.Value(bsonprim.ObjectID(id).Hex()), nil
+ return driver.Value(id.Hex()), nil
+}
+
+// Hex returns the hex string representation of the [ObjectId].
+func (id ObjectId) Hex() string {
+ return hex.EncodeToString(id[:])
}
func (id ObjectId) String() string {
- return bsonprim.ObjectID(id).Hex()
+ return id.Hex()
}
// MarshalJSON returns the [ObjectId] as JSON.
func (id ObjectId) MarshalJSON() ([]byte, error) {
- return bsonprim.ObjectID(id).MarshalJSON()
+ return json.Marshal(id.Hex())
}
// UnmarshalJSON sets the [ObjectId] from JSON.
func (id *ObjectId) UnmarshalJSON(data []byte) error {
- var obj bsonprim.ObjectID
- if err := obj.UnmarshalJSON(data); err != nil {
+ var hexStr string
+ if err := json.Unmarshal(data, &hexStr); err != nil {
+ return err
+ }
+ oid, err := objectIDFromHex(hexStr)
+ if err != nil {
return err
}
- *id = ObjectId(obj)
+ *id = oid
return nil
}
@@ -112,3 +122,18 @@ func (id *ObjectId) DeepCopy() *ObjectId {
id.DeepCopyInto(out)
return out
}
+
+// objectIDFromHex parses a 24-character hex string into an [ObjectId].
+func objectIDFromHex(s string) (ObjectId, error) {
+ const objectIDHexLen = 24
+ if len(s) != objectIDHexLen {
+ return nilObjectID, fmt.Errorf("the provided hex string %q is not a valid ObjectID: %w", s, ErrFormat)
+ }
+ b, err := hex.DecodeString(s)
+ if err != nil {
+ return nilObjectID, fmt.Errorf("the provided hex string %q is not a valid ObjectID: %w", s, err)
+ }
+ var oid ObjectId
+ copy(oid[:], b)
+ return oid, nil
+}
diff --git a/bson_test.go b/bson_test.go
index 874c685..924f646 100644
--- a/bson_test.go
+++ b/bson_test.go
@@ -8,7 +8,6 @@ import (
"github.com/go-openapi/testify/v2/assert"
"github.com/go-openapi/testify/v2/require"
- "go.mongodb.org/mongo-driver/v2/bson"
)
func TestBSONObjectId_fullCycle(t *testing.T) {
@@ -39,10 +38,10 @@ func TestBSONObjectId_fullCycle(t *testing.T) {
require.NoError(t, err)
assert.EqualT(t, id, idCopy)
- bsonBytes, err := bson.Marshal(&id)
+ bsonBytes, err := id.MarshalBSON()
require.NoError(t, err)
- err = bson.Unmarshal(bsonBytes, &idCopy)
+ err = idCopy.UnmarshalBSON(bsonBytes)
require.NoError(t, err)
assert.EqualT(t, id, idCopy)
}
diff --git a/default_test.go b/default_test.go
index ac5db5b..ec6ef5e 100644
--- a/default_test.go
+++ b/default_test.go
@@ -218,7 +218,7 @@ func TestFormatIPv4(t *testing.T) {
func TestFormatIPv6(t *testing.T) {
ipv6 := IPv6("::1")
str := string("::2")
- // TODO: test ipv6 zones
+ // Proposal for enhancement: test ipv6 zones
testStringFormat(t, &ipv6, "ipv6", str, []string{}, []string{"127.0.0.1"})
}
diff --git a/doc.go b/doc.go
index b8fe177..6652521 100644
--- a/doc.go
+++ b/doc.go
@@ -2,6 +2,4 @@
// SPDX-License-Identifier: Apache-2.0
// Package strfmt contains custom string formats.
-//
-// TODO: add info on how to define and register a custom format.
package strfmt
diff --git a/docs/MAINTAINERS.md b/docs/MAINTAINERS.md
index 42a60bf..b0bc34e 100644
--- a/docs/MAINTAINERS.md
+++ b/docs/MAINTAINERS.md
@@ -4,7 +4,15 @@
## Repo structure
-This project is organized as a repo with a single go module.
+This project is organized as a mono-repo with multiple go modules:
+
+| Module | Description |
+|--------|-------------|
+| `.` (root) | Core `strfmt` package with all format types |
+| `enable/mongodb` | Blank-import package that replaces the built-in minimal BSON codec with the real MongoDB driver |
+| `internal/testintegration` | Integration tests against real databases (MongoDB, MariaDB, PostgreSQL) |
+
+A `go.work` file at the root ties all modules together for local development.
## Repo configuration
@@ -54,7 +62,7 @@ Coverage threshold status is informative and not blocking.
This is because the thresholds are difficult to tune and codecov oftentimes reports false negatives
or may fail to upload coverage.
-All tests across `go-openapi` use our fork of `stretchr/strfmt` (this repo): `github.com/go-openapi/strfmt`.
+All tests across `go-openapi` use our fork of `stretchr/testify`: `github.com/go-openapi/testify`.
This allows for minimal test dependencies.
> **NOTES**
@@ -117,19 +125,20 @@ Reports are centralized in github security reports for code scanning tools.
## Releases
-**For single module repos:**
-
-A bump release workflow can be triggered from the github actions UI to cut a release with a few clicks.
+A bump release workflow (mono-repo) can be triggered from the github actions UI to cut a release with a few clicks.
-The release process is minimalist:
+The release process updates cross-module dependencies (e.g. `enable/mongodb` → root module)
+before pushing the desired git tag.
-* push a semver tag (i.e v{major}.{minor}.{patch}) to the master branch.
-* the CI handles this to generate a github release with release notes
+It first creates an auto-merged PR that updates the different `go.mod` files,
+then pushes the desired semver tag.
* release notes generator: git-cliff
-* configuration: the `.cliff.toml` is defined as a share configuration on
+* configuration: the `.cliff.toml` is defined as a shared configuration on
remote repo [`ci-workflows/.cliff.toml`][remote-cliff-config]
+Commits and tags pushed by the workflow bot are PGP-signed ("go-openapi[bot]").
+
Commits from maintainers are preferably PGP-signed.
Tags are preferably PGP-signed.
@@ -140,18 +149,6 @@ The tag message introduces the release notes (e.g. a summary of this release).
The release notes generator does not assume that commits are necessarily "conventional commits".
-**For mono-repos with multiple modules:**
-
-The release process is slightly different because we need to update cross-module dependencies
-before pushing a tag.
-
-A bump release workflow (mono-repo) can be triggered from the github actions UI to cut a release with a few clicks.
-
-It works with the same input as the one for single module repos, and first creates a PR (auto-merged)
-that updates the different go.mod files _before_ pushing the desired git tag.
-
-Commits and tags pushed by the workflow bot are PGP-signed ("go-openapi[bot]").
-
## Other files
Standard documentation:
diff --git a/duration.go b/duration.go
index 81532ef..229aa75 100644
--- a/duration.go
+++ b/duration.go
@@ -143,7 +143,7 @@ func ParseDuration(cand string) (time.Duration, error) {
// Scan reads a Duration value from database driver type.
func (d *Duration) Scan(raw any) error {
switch v := raw.(type) {
- // TODO: case []byte: // ?
+ // Proposal for enhancement: case []byte: // ?
case int64:
*d = Duration(v)
case float64:
diff --git a/enable/mongodb/go.mod b/enable/mongodb/go.mod
new file mode 100644
index 0000000..e2dcf2a
--- /dev/null
+++ b/enable/mongodb/go.mod
@@ -0,0 +1,10 @@
+module github.com/go-openapi/strfmt/enable/mongodb
+
+go 1.24.0
+
+require (
+ github.com/go-openapi/strfmt v0.25.0
+ go.mongodb.org/mongo-driver/v2 v2.5.0
+)
+
+replace github.com/go-openapi/strfmt => ../..
diff --git a/enable/mongodb/go.sum b/enable/mongodb/go.sum
new file mode 100644
index 0000000..65a6c8c
--- /dev/null
+++ b/enable/mongodb/go.sum
@@ -0,0 +1,6 @@
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
+go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
diff --git a/enable/mongodb/mongodb.go b/enable/mongodb/mongodb.go
new file mode 100644
index 0000000..d93b5e2
--- /dev/null
+++ b/enable/mongodb/mongodb.go
@@ -0,0 +1,53 @@
+// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
+// SPDX-License-Identifier: Apache-2.0
+
+// Package mongodb replaces strfmt's built-in minimal BSON codec with one
+// backed by the official MongoDB driver (go.mongodb.org/mongo-driver/v2).
+//
+// Usage: blank-import this package to enable the real driver codec:
+//
+// import _ "github.com/go-openapi/strfmt/enable/mongodb"
+package mongodb
+
+import (
+ "time"
+
+ "github.com/go-openapi/strfmt/internal/bsonlite"
+ "go.mongodb.org/mongo-driver/v2/bson"
+)
+
+func init() { //nolint:gochecknoinits // blank-import registration pattern, by design
+ bsonlite.Replace(driverCodec{})
+}
+
+// driverCodec implements bsonlite.Codec using the real MongoDB driver.
+type driverCodec struct{}
+
+func (driverCodec) MarshalDoc(value any) ([]byte, error) {
+ switch v := value.(type) {
+ case [bsonlite.ObjectIDSize]byte:
+ return bson.Marshal(bson.M{"data": bson.ObjectID(v)})
+ default:
+ return bson.Marshal(bson.M{"data": v})
+ }
+}
+
+func (driverCodec) UnmarshalDoc(data []byte) (any, error) {
+ var m bson.M
+ if err := bson.Unmarshal(data, &m); err != nil {
+ return nil, err
+ }
+
+ v := m["data"]
+
+ switch val := v.(type) {
+ case bson.DateTime:
+ return val.Time(), nil
+ case bson.ObjectID:
+ return [bsonlite.ObjectIDSize]byte(val), nil
+ case time.Time:
+ return val, nil
+ default:
+ return v, nil
+ }
+}
diff --git a/go.mod b/go.mod
index b388ac5..db5ae74 100644
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,6 @@ require (
github.com/go-viper/mapstructure/v2 v2.5.0
github.com/google/uuid v1.6.0
github.com/oklog/ulid/v2 v2.1.1
- go.mongodb.org/mongo-driver/v2 v2.5.0
golang.org/x/net v0.50.0
)
diff --git a/go.sum b/go.sum
index c2ef2c3..e6060d3 100644
--- a/go.sum
+++ b/go.sum
@@ -1,20 +1,14 @@
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-openapi/errors v0.22.7 h1:JLFBGC0Apwdzw3484MmBqspjPbwa2SHvpDm0u5aGhUA=
github.com/go-openapi/errors v0.22.7/go.mod h1://QW6SD9OsWtH6gHllUCddOXDL0tk0ZGNYHwsw4sW3w=
github.com/go-openapi/testify/v2 v2.4.0 h1:8nsPrHVCWkQ4p8h1EsRVymA2XABB4OT40gcvAu+voFM=
github.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
-github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
-github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=
github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
-go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
-go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
diff --git a/go.work b/go.work
new file mode 100644
index 0000000..288e765
--- /dev/null
+++ b/go.work
@@ -0,0 +1,7 @@
+use (
+ .
+ ./enable/mongodb
+ ./internal/testintegration
+)
+
+go 1.24.0
diff --git a/go.work.sum b/go.work.sum
new file mode 100644
index 0000000..b69c2da
--- /dev/null
+++ b/go.work.sum
@@ -0,0 +1,6 @@
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
+golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
+golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
diff --git a/internal/bsonlite/codec.go b/internal/bsonlite/codec.go
new file mode 100644
index 0000000..424f454
--- /dev/null
+++ b/internal/bsonlite/codec.go
@@ -0,0 +1,71 @@
+// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
+// SPDX-License-Identifier: Apache-2.0
+
+// Package bsonlite provides a minimal BSON codec for strfmt types.
+//
+// This codec produces BSON output compatible with go.mongodb.org/mongo-driver/v2
+// (v2.5.0). It handles only the exact BSON patterns used by strfmt:
+// single-key {"data": value} documents with string, DateTime, or ObjectID values.
+//
+// This package is intended to provide a backward-compatible API to users of
+// go-openapi/strfmt. It is not intended to be maintained or to follow the
+// evolutions of the official MongoDB drivers. For up-to-date MongoDB support,
+// import "github.com/go-openapi/strfmt/enable/mongodb" to replace this codec
+// with one backed by the real driver.
+package bsonlite
+
+import "time"
+
+// Codec provides BSON document marshal/unmarshal for strfmt types.
+//
+// MarshalDoc encodes a single-key BSON document {"data": value}.
+// The value must be one of: string, time.Time, or [12]byte (ObjectID).
+//
+// UnmarshalDoc decodes a BSON document and returns the "data" field's value.
+// Returns one of: string, time.Time, or [12]byte depending on the BSON type.
+type Codec interface {
+ MarshalDoc(value any) ([]byte, error)
+ UnmarshalDoc(data []byte) (any, error)
+}
+
+// C is the active BSON codec.
+//
+//nolint:gochecknoglobals // replaceable codec, by design
+var C Codec = liteCodec{}
+
+// Replace swaps the active BSON codec with the provided implementation.
+// This is intended to be called from enable/mongodb's init().
+//
+// Since [Replace] affects the global state of the package, it is not intended for concurrent use.
+func Replace(c Codec) {
+ C = c
+}
+
+// BSON type tags (from the BSON specification).
+const (
+ TypeString byte = 0x02
+ TypeObjectID byte = 0x07
+ TypeDateTime byte = 0x09
+ TypeNull byte = 0x0A
+)
+
+// ObjectIDSize is the size of a BSON ObjectID in bytes.
+const ObjectIDSize = 12
+
+// DateTimeToMillis converts a time.Time to BSON DateTime milliseconds.
+func DateTimeToMillis(t time.Time) int64 {
+ const (
+ millisec = 1000
+ microsec = 1_000_000
+ )
+ return t.Unix()*millisec + int64(t.Nanosecond())/microsec
+}
+
+// MillisToTime converts BSON DateTime milliseconds to time.Time.
+func MillisToTime(millis int64) time.Time {
+ const (
+ millisec = 1000
+ nanosPerMs = 1_000_000
+ )
+ return time.Unix(millis/millisec, millis%millisec*nanosPerMs)
+}
diff --git a/internal/bsonlite/lite.go b/internal/bsonlite/lite.go
new file mode 100644
index 0000000..6b0e0e1
--- /dev/null
+++ b/internal/bsonlite/lite.go
@@ -0,0 +1,213 @@
+// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
+// SPDX-License-Identifier: Apache-2.0
+
+package bsonlite
+
+import (
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "time"
+)
+
+// liteCodec is a minimal BSON codec that handles only the patterns used by strfmt:
+// single-key documents of the form {"data": } where value is a string,
+// BSON DateTime (time.Time), or BSON ObjectID ([12]byte).
+type liteCodec struct{}
+
+var _ Codec = liteCodec{}
+
+func (liteCodec) MarshalDoc(value any) ([]byte, error) {
+ switch v := value.(type) {
+ case string:
+ return marshalStringDoc(v), nil
+ case time.Time:
+ return marshalDateTimeDoc(v), nil
+ case [ObjectIDSize]byte:
+ return marshalObjectIDDoc(v), nil
+ default:
+ return nil, fmt.Errorf("bsonlite: unsupported value type %T: %w", value, errUnsupportedType)
+ }
+}
+
+func (liteCodec) UnmarshalDoc(data []byte) (any, error) {
+ return unmarshalDoc(data)
+}
+
+// BSON wire format helpers.
+//
+// Document: int32(size) + elements + 0x00
+// Element: byte(type) + cstring(key) + value
+// String: int32(len+1) + bytes + 0x00
+// DateTime: int64 (LE, millis since epoch)
+// ObjectID: [12]byte
+
+const dataKey = "data\x00"
+
+func marshalStringDoc(s string) []byte {
+ sBytes := []byte(s)
+ // doc_size(4) + type(1) + key("data\0"=5) + strlen(4) + string + \0(1) + doc_term(1)
+ docSize := 4 + 1 + len(dataKey) + 4 + len(sBytes) + 1 + 1
+
+ buf := make([]byte, docSize)
+ pos := 0
+
+ binary.LittleEndian.PutUint32(buf[pos:], uint32(docSize)) //nolint:gosec // size is computed from input, cannot overflow
+ pos += 4
+
+ buf[pos] = TypeString
+ pos++
+
+ pos += copy(buf[pos:], dataKey)
+
+ binary.LittleEndian.PutUint32(buf[pos:], uint32(len(sBytes)+1)) //nolint:gosec // string length cannot overflow uint32
+ pos += 4
+
+ pos += copy(buf[pos:], sBytes)
+ buf[pos] = 0 // string null terminator
+ pos++
+
+ buf[pos] = 0 // document terminator
+
+ return buf
+}
+
+func marshalDateTimeDoc(t time.Time) []byte {
+ // doc_size(4) + type(1) + key("data\0"=5) + int64(8) + doc_term(1)
+ const docSize = 4 + 1 + 5 + 8 + 1
+
+ buf := make([]byte, docSize)
+ pos := 0
+
+ binary.LittleEndian.PutUint32(buf[pos:], docSize)
+ pos += 4
+
+ buf[pos] = TypeDateTime
+ pos++
+
+ pos += copy(buf[pos:], dataKey)
+
+ millis := DateTimeToMillis(t)
+ binary.LittleEndian.PutUint64(buf[pos:], uint64(millis)) //nolint:gosec // negative datetime millis are valid
+ // pos += 8
+
+ buf[docSize-1] = 0 // document terminator
+
+ return buf
+}
+
+func marshalObjectIDDoc(oid [ObjectIDSize]byte) []byte {
+ // doc_size(4) + type(1) + key("data\0"=5) + objectid(12) + doc_term(1)
+ const docSize = 4 + 1 + 5 + ObjectIDSize + 1
+
+ buf := make([]byte, docSize)
+ pos := 0
+
+ binary.LittleEndian.PutUint32(buf[pos:], docSize)
+ pos += 4
+
+ buf[pos] = TypeObjectID
+ pos++
+
+ pos += copy(buf[pos:], dataKey)
+
+ copy(buf[pos:], oid[:])
+ // pos += ObjectIDSize
+
+ buf[docSize-1] = 0 // document terminator
+
+ return buf
+}
+
+var (
+ errUnsupportedType = errors.New("bsonlite: unsupported type")
+ errDocTooShort = errors.New("bsonlite: document too short")
+ errDocSize = errors.New("bsonlite: document size mismatch")
+ errNoTerminator = errors.New("bsonlite: missing key terminator")
+ errTruncated = errors.New("bsonlite: truncated value")
+ errDataNotFound = errors.New("bsonlite: \"data\" field not found")
+)
+
+func unmarshalDoc(raw []byte) (any, error) {
+ const minDocSize = 5 // int32(size) + terminator
+
+ if len(raw) < minDocSize {
+ return nil, errDocTooShort
+ }
+
+ docSize := int(binary.LittleEndian.Uint32(raw[:4]))
+ if docSize != len(raw) {
+ return nil, errDocSize
+ }
+
+ pos := 4
+
+ for pos < docSize-1 {
+ if pos >= len(raw) {
+ return nil, errTruncated
+ }
+ typeByte := raw[pos]
+ pos++
+
+ // Read key (cstring: bytes until 0x00).
+ keyStart := pos
+ for pos < len(raw) && raw[pos] != 0 {
+ pos++
+ }
+ if pos >= len(raw) {
+ return nil, errNoTerminator
+ }
+ key := string(raw[keyStart:pos])
+ pos++ // skip null terminator
+
+ val, newPos, err := readValue(typeByte, raw, pos)
+ if err != nil {
+ return nil, err
+ }
+ pos = newPos
+
+ if key == "data" {
+ return val, nil
+ }
+ }
+
+ return nil, errDataNotFound
+}
+
+func readValue(typeByte byte, raw []byte, pos int) (any, int, error) {
+ switch typeByte {
+ case TypeString:
+ if pos+4 > len(raw) {
+ return nil, 0, errTruncated
+ }
+ strLen := int(binary.LittleEndian.Uint32(raw[pos:]))
+ pos += 4
+ if pos+strLen > len(raw) || strLen < 1 {
+ return nil, 0, errTruncated
+ }
+ s := string(raw[pos : pos+strLen-1]) // exclude null terminator
+ return s, pos + strLen, nil
+
+ case TypeObjectID:
+ if pos+ObjectIDSize > len(raw) {
+ return nil, 0, errTruncated
+ }
+ var oid [ObjectIDSize]byte
+ copy(oid[:], raw[pos:pos+ObjectIDSize])
+ return oid, pos + ObjectIDSize, nil
+
+ case TypeDateTime:
+ const dateTimeSize = 8
+ if pos+dateTimeSize > len(raw) {
+ return nil, 0, errTruncated
+ }
+ millis := int64(binary.LittleEndian.Uint64(raw[pos:])) //nolint:gosec // negative datetime millis are valid
+ return MillisToTime(millis), pos + dateTimeSize, nil
+
+ case TypeNull:
+ return nil, pos, nil
+
+ default:
+ return nil, 0, fmt.Errorf("bsonlite: unsupported BSON type 0x%02x: %w", typeByte, errUnsupportedType)
+ }
+}
diff --git a/internal/testintegration/go.mod b/internal/testintegration/go.mod
index eb7680d..a9e09de 100644
--- a/internal/testintegration/go.mod
+++ b/internal/testintegration/go.mod
@@ -4,6 +4,7 @@ go 1.24.0
require (
github.com/go-openapi/strfmt v0.25.0
+ github.com/go-openapi/strfmt/enable/mongodb v0.0.0
github.com/go-openapi/testify/v2 v2.4.0
github.com/go-sql-driver/mysql v1.9.3
github.com/jackc/pgx/v5 v5.8.0
@@ -30,4 +31,7 @@ require (
golang.org/x/text v0.34.0 // indirect
)
-replace github.com/go-openapi/strfmt => ../..
+replace (
+ github.com/go-openapi/strfmt => ../..
+ github.com/go-openapi/strfmt/enable/mongodb => ../../enable/mongodb
+)
diff --git a/internal/testintegration/mongodb/mongo_test.go b/internal/testintegration/mongodb/mongo_test.go
index 2e6d8f1..a122e5b 100644
--- a/internal/testintegration/mongodb/mongo_test.go
+++ b/internal/testintegration/mongodb/mongo_test.go
@@ -3,351 +3,16 @@
//go:build testintegration
+// Package mongodb_test runs MongoDB integration tests using the default
+// (bsonlite) codec — no enable/mongodb blank import.
package mongodb_test
import (
- "context"
- "encoding/base64"
- "os"
"testing"
- "time"
- "github.com/go-openapi/strfmt"
- "github.com/go-openapi/testify/v2/assert"
- "github.com/go-openapi/testify/v2/require"
- "go.mongodb.org/mongo-driver/v2/bson"
- "go.mongodb.org/mongo-driver/v2/mongo"
- "go.mongodb.org/mongo-driver/v2/mongo/options"
+ "github.com/go-openapi/strfmt/internal/testintegration/mongotest"
)
-func mongoURI() string {
- if uri := os.Getenv("MONGODB_URI"); uri != "" {
- return uri
- }
- return "mongodb://localhost:27017"
-}
-
-func setup(t *testing.T) *mongo.Collection {
- t.Helper()
-
- client, err := mongo.Connect(options.Client().ApplyURI(mongoURI()))
- require.NoError(t, err)
-
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
-
- require.NoError(t, client.Ping(ctx, nil))
-
- db := client.Database("strfmt_integration_test")
- coll := db.Collection(t.Name())
-
- t.Cleanup(func() {
- _ = coll.Drop(context.Background())
- _ = client.Disconnect(context.Background())
- })
-
- return coll
-}
-
-// roundTrip inserts a document containing the value into MongoDB,
-// reads it back, and returns the result document.
-func roundTrip(t *testing.T, coll *mongo.Collection, doc bson.M) bson.M {
- t.Helper()
- ctx := context.Background()
-
- _, err := coll.InsertOne(ctx, doc)
- require.NoError(t, err)
-
- var result bson.M
- err = coll.FindOne(ctx, bson.M{"_id": doc["_id"]}).Decode(&result)
- require.NoError(t, err)
-
- return result
-}
-
-func TestDate(t *testing.T) {
- coll := setup(t)
- original := strfmt.Date(time.Date(2024, 6, 15, 0, 0, 0, 0, time.UTC))
-
- doc := bson.M{"_id": "date_test", "value": original}
- result := roundTrip(t, coll, doc)
-
- raw, ok := result["value"].(bson.D)
- require.TrueT(t, ok, "expected bson.D for value, got %T", result["value"])
-
- rawBytes, err := bson.Marshal(raw)
- require.NoError(t, err)
-
- var got strfmt.Date
- require.NoError(t, bson.Unmarshal(rawBytes, &got))
-
- assert.EqualT(t, original.String(), got.String())
-}
-
-func TestDateTime(t *testing.T) {
- coll := setup(t)
- original := strfmt.DateTime(time.Date(2024, 6, 15, 12, 30, 45, 0, time.UTC))
-
- doc := bson.M{"_id": "datetime_test", "value": original}
- result := roundTrip(t, coll, doc)
-
- // DateTime uses MarshalBSONValue, so MongoDB stores it as a native datetime.
- dt, ok := result["value"].(bson.DateTime)
- require.TrueT(t, ok, "expected bson.DateTime, got %T", result["value"])
-
- got := strfmt.DateTime(dt.Time())
-
- assert.EqualT(t, time.Time(original).UTC().UnixMilli(), time.Time(got).UTC().UnixMilli())
-}
-
-func TestDuration(t *testing.T) {
- coll := setup(t)
- original := strfmt.Duration(42 * time.Second)
-
- doc := bson.M{"_id": "duration_test", "value": original}
- result := roundTrip(t, coll, doc)
-
- raw, ok := result["value"].(bson.D)
- require.TrueT(t, ok, "expected bson.D for value, got %T", result["value"])
-
- rawBytes, err := bson.Marshal(raw)
- require.NoError(t, err)
-
- var got strfmt.Duration
- require.NoError(t, bson.Unmarshal(rawBytes, &got))
-
- assert.EqualT(t, original, got)
-}
-
-func TestBase64(t *testing.T) {
- coll := setup(t)
- payload := []byte("hello world with special chars: éàü")
- original := strfmt.Base64(payload)
-
- doc := bson.M{"_id": "base64_test", "value": original}
- result := roundTrip(t, coll, doc)
-
- raw, ok := result["value"].(bson.D)
- require.TrueT(t, ok, "expected bson.D for value, got %T", result["value"])
-
- rawBytes, err := bson.Marshal(raw)
- require.NoError(t, err)
-
- var got strfmt.Base64
- require.NoError(t, bson.Unmarshal(rawBytes, &got))
-
- assert.EqualT(t, base64.StdEncoding.EncodeToString(original), base64.StdEncoding.EncodeToString(got))
-}
-
-func TestULID(t *testing.T) {
- coll := setup(t)
- original, err := strfmt.ParseULID("01ARZ3NDEKTSV4RRFFQ69G5FAV")
- require.NoError(t, err)
-
- doc := bson.M{"_id": "ulid_test", "value": original}
- result := roundTrip(t, coll, doc)
-
- raw, ok := result["value"].(bson.D)
- require.TrueT(t, ok, "expected bson.D for value, got %T", result["value"])
-
- rawBytes, err := bson.Marshal(raw)
- require.NoError(t, err)
-
- var got strfmt.ULID
- require.NoError(t, bson.Unmarshal(rawBytes, &got))
-
- assert.EqualT(t, original, got)
-}
-
-func TestObjectId(t *testing.T) {
- coll := setup(t)
- original := strfmt.NewObjectId("507f1f77bcf86cd799439011")
-
- doc := bson.M{"_id": "objectid_test", "value": original}
- result := roundTrip(t, coll, doc)
-
- // ObjectId uses MarshalBSONValue, so MongoDB stores it as a native ObjectID.
- oid, ok := result["value"].(bson.ObjectID)
- require.TrueT(t, ok, "expected bson.ObjectID, got %T", result["value"])
-
- got := strfmt.ObjectId(oid)
-
- assert.EqualT(t, original, got)
-}
-
-// stringFormatRoundTrip is a helper for types that serialize as embedded BSON documents
-// with a "data" string field (most strfmt string-based types).
-func stringFormatRoundTrip(t *testing.T, coll *mongo.Collection, id string, input bson.Marshaler, output bson.Unmarshaler, _ string) {
- t.Helper()
-
- doc := bson.M{"_id": id, "value": input}
- result := roundTrip(t, coll, doc)
-
- raw, ok := result["value"].(bson.D)
- require.TrueT(t, ok, "expected bson.D for value, got %T", result["value"])
-
- rawBytes, err := bson.Marshal(raw)
- require.NoError(t, err)
-
- require.NoError(t, bson.Unmarshal(rawBytes, output))
-}
-
-func TestURI(t *testing.T) {
- coll := setup(t)
- original := strfmt.URI("https://example.com/path?q=1")
- var got strfmt.URI
- stringFormatRoundTrip(t, coll, "uri_test", original, &got, string(original))
- assert.EqualT(t, original, got)
-}
-
-func TestEmail(t *testing.T) {
- coll := setup(t)
- original := strfmt.Email("user@example.com")
- var got strfmt.Email
- stringFormatRoundTrip(t, coll, "email_test", original, &got, string(original))
- assert.EqualT(t, original, got)
-}
-
-func TestHostname(t *testing.T) {
- coll := setup(t)
- original := strfmt.Hostname("example.com")
- var got strfmt.Hostname
- stringFormatRoundTrip(t, coll, "hostname_test", original, &got, string(original))
- assert.EqualT(t, original, got)
-}
-
-func TestIPv4(t *testing.T) {
- coll := setup(t)
- original := strfmt.IPv4("192.168.1.1")
- var got strfmt.IPv4
- stringFormatRoundTrip(t, coll, "ipv4_test", original, &got, string(original))
- assert.EqualT(t, original, got)
-}
-
-func TestIPv6(t *testing.T) {
- coll := setup(t)
- original := strfmt.IPv6("::1")
- var got strfmt.IPv6
- stringFormatRoundTrip(t, coll, "ipv6_test", original, &got, string(original))
- assert.EqualT(t, original, got)
-}
-
-func TestCIDR(t *testing.T) {
- coll := setup(t)
- original := strfmt.CIDR("192.168.1.0/24")
- var got strfmt.CIDR
- stringFormatRoundTrip(t, coll, "cidr_test", original, &got, string(original))
- assert.EqualT(t, original, got)
-}
-
-func TestMAC(t *testing.T) {
- coll := setup(t)
- original := strfmt.MAC("01:02:03:04:05:06")
- var got strfmt.MAC
- stringFormatRoundTrip(t, coll, "mac_test", original, &got, string(original))
- assert.EqualT(t, original, got)
-}
-
-func TestUUID(t *testing.T) {
- coll := setup(t)
- original := strfmt.UUID("a8098c1a-f86e-11da-bd1a-00112444be1e")
- var got strfmt.UUID
- stringFormatRoundTrip(t, coll, "uuid_test", original, &got, string(original))
- assert.EqualT(t, original, got)
-}
-
-func TestUUID3(t *testing.T) {
- coll := setup(t)
- original := strfmt.UUID3("bcd02ab7-6beb-3467-84c0-3bdbea962817")
- var got strfmt.UUID3
- stringFormatRoundTrip(t, coll, "uuid3_test", original, &got, string(original))
- assert.EqualT(t, original, got)
-}
-
-func TestUUID4(t *testing.T) {
- coll := setup(t)
- original := strfmt.UUID4("025b0d74-00a2-4885-af46-084e7fbd0701")
- var got strfmt.UUID4
- stringFormatRoundTrip(t, coll, "uuid4_test", original, &got, string(original))
- assert.EqualT(t, original, got)
-}
-
-func TestUUID5(t *testing.T) {
- coll := setup(t)
- original := strfmt.UUID5("886313e1-3b8a-5372-9b90-0c9aee199e5d")
- var got strfmt.UUID5
- stringFormatRoundTrip(t, coll, "uuid5_test", original, &got, string(original))
- assert.EqualT(t, original, got)
-}
-
-func TestUUID7(t *testing.T) {
- coll := setup(t)
- original := strfmt.UUID7("01943ff8-3e9e-7be4-8921-de6a1e04d599")
- var got strfmt.UUID7
- stringFormatRoundTrip(t, coll, "uuid7_test", original, &got, string(original))
- assert.EqualT(t, original, got)
-}
-
-func TestISBN(t *testing.T) {
- coll := setup(t)
- original := strfmt.ISBN("0321751043")
- var got strfmt.ISBN
- stringFormatRoundTrip(t, coll, "isbn_test", original, &got, string(original))
- assert.EqualT(t, original, got)
-}
-
-func TestISBN10(t *testing.T) {
- coll := setup(t)
- original := strfmt.ISBN10("0321751043")
- var got strfmt.ISBN10
- stringFormatRoundTrip(t, coll, "isbn10_test", original, &got, string(original))
- assert.EqualT(t, original, got)
-}
-
-func TestISBN13(t *testing.T) {
- coll := setup(t)
- original := strfmt.ISBN13("978-0321751041")
- var got strfmt.ISBN13
- stringFormatRoundTrip(t, coll, "isbn13_test", original, &got, string(original))
- assert.EqualT(t, original, got)
-}
-
-func TestCreditCard(t *testing.T) {
- coll := setup(t)
- original := strfmt.CreditCard("4111-1111-1111-1111")
- var got strfmt.CreditCard
- stringFormatRoundTrip(t, coll, "creditcard_test", original, &got, string(original))
- assert.EqualT(t, original, got)
-}
-
-func TestSSN(t *testing.T) {
- coll := setup(t)
- original := strfmt.SSN("111-11-1111")
- var got strfmt.SSN
- stringFormatRoundTrip(t, coll, "ssn_test", original, &got, string(original))
- assert.EqualT(t, original, got)
-}
-
-func TestHexColor(t *testing.T) {
- coll := setup(t)
- original := strfmt.HexColor("#FFFFFF")
- var got strfmt.HexColor
- stringFormatRoundTrip(t, coll, "hexcolor_test", original, &got, string(original))
- assert.EqualT(t, original, got)
-}
-
-func TestRGBColor(t *testing.T) {
- coll := setup(t)
- original := strfmt.RGBColor("rgb(255,255,255)")
- var got strfmt.RGBColor
- stringFormatRoundTrip(t, coll, "rgbcolor_test", original, &got, string(original))
- assert.EqualT(t, original, got)
-}
-
-func TestPassword(t *testing.T) {
- coll := setup(t)
- original := strfmt.Password("super secret stuff here")
- var got strfmt.Password
- stringFormatRoundTrip(t, coll, "password_test", original, &got, string(original))
- assert.EqualT(t, original, got)
+func TestMongoDBLiteCodec(t *testing.T) {
+ mongotest.RunAllTests(t)
}
diff --git a/internal/testintegration/mongodb_enabled/mongo_test.go b/internal/testintegration/mongodb_enabled/mongo_test.go
new file mode 100644
index 0000000..919e0d9
--- /dev/null
+++ b/internal/testintegration/mongodb_enabled/mongo_test.go
@@ -0,0 +1,19 @@
+// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
+// SPDX-License-Identifier: Apache-2.0
+
+//go:build testintegration
+
+// Package mongodb_enabled_test runs MongoDB integration tests with the real
+// MongoDB driver codec enabled via blank import.
+package mongodb_enabled_test
+
+import (
+ "testing"
+
+ _ "github.com/go-openapi/strfmt/enable/mongodb"
+ "github.com/go-openapi/strfmt/internal/testintegration/mongotest"
+)
+
+func TestMongoDBDriverCodec(t *testing.T) {
+ mongotest.RunAllTests(t)
+}
diff --git a/internal/testintegration/mongotest/mongotest.go b/internal/testintegration/mongotest/mongotest.go
new file mode 100644
index 0000000..d02cb55
--- /dev/null
+++ b/internal/testintegration/mongotest/mongotest.go
@@ -0,0 +1,360 @@
+// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
+// SPDX-License-Identifier: Apache-2.0
+
+//go:build testintegration
+
+// Package mongotest provides shared MongoDB integration test logic.
+// It is used by both mongodb/ (lite codec) and mongodb_enabled/ (real driver codec)
+// test packages to verify that strfmt types round-trip correctly through MongoDB.
+package mongotest
+
+import (
+ "context"
+ "encoding/base64"
+ "os"
+ "testing"
+ "time"
+
+ "github.com/go-openapi/strfmt"
+ "github.com/go-openapi/testify/v2/assert"
+ "github.com/go-openapi/testify/v2/require"
+ "go.mongodb.org/mongo-driver/v2/bson"
+ "go.mongodb.org/mongo-driver/v2/mongo"
+ "go.mongodb.org/mongo-driver/v2/mongo/options"
+)
+
+func mongoURI() string {
+ if uri := os.Getenv("MONGODB_URI"); uri != "" {
+ return uri
+ }
+ return "mongodb://localhost:27017"
+}
+
+// Setup connects to MongoDB and returns a test collection.
+func Setup(t *testing.T) *mongo.Collection {
+ t.Helper()
+
+ client, err := mongo.Connect(options.Client().ApplyURI(mongoURI()))
+ require.NoError(t, err)
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+
+ require.NoError(t, client.Ping(ctx, nil))
+
+ db := client.Database("strfmt_integration_test")
+ coll := db.Collection(t.Name())
+
+ t.Cleanup(func() {
+ _ = coll.Drop(context.Background())
+ _ = client.Disconnect(context.Background())
+ })
+
+ return coll
+}
+
+// roundTrip inserts a document containing the value into MongoDB,
+// reads it back, and returns the result document.
+func roundTrip(t *testing.T, coll *mongo.Collection, doc bson.M) bson.M {
+ t.Helper()
+ ctx := context.Background()
+
+ _, err := coll.InsertOne(ctx, doc)
+ require.NoError(t, err)
+
+ var result bson.M
+ err = coll.FindOne(ctx, bson.M{"_id": doc["_id"]}).Decode(&result)
+ require.NoError(t, err)
+
+ return result
+}
+
+// stringFormatRoundTrip is a helper for types that serialize as embedded BSON documents
+// with a "data" string field (most strfmt string-based types).
+func stringFormatRoundTrip(t *testing.T, coll *mongo.Collection, id string, input bson.Marshaler, output bson.Unmarshaler) {
+ t.Helper()
+
+ doc := bson.M{"_id": id, "value": input}
+ result := roundTrip(t, coll, doc)
+
+ raw, ok := result["value"].(bson.D)
+ require.TrueT(t, ok, "expected bson.D for value, got %T", result["value"])
+
+ rawBytes, err := bson.Marshal(raw)
+ require.NoError(t, err)
+
+ require.NoError(t, bson.Unmarshal(rawBytes, output))
+}
+
+// RunAllTests runs all strfmt type round-trip tests against the given MongoDB collection.
+func RunAllTests(t *testing.T) {
+ t.Run("Date", func(t *testing.T) {
+ coll := Setup(t)
+ original := strfmt.Date(time.Date(2024, 6, 15, 0, 0, 0, 0, time.UTC))
+
+ doc := bson.M{"_id": "date_test", "value": original}
+ result := roundTrip(t, coll, doc)
+
+ raw, ok := result["value"].(bson.D)
+ require.TrueT(t, ok, "expected bson.D for value, got %T", result["value"])
+
+ rawBytes, err := bson.Marshal(raw)
+ require.NoError(t, err)
+
+ var got strfmt.Date
+ require.NoError(t, bson.Unmarshal(rawBytes, &got))
+
+ assert.EqualT(t, original.String(), got.String())
+ })
+
+ t.Run("DateTime", func(t *testing.T) {
+ coll := Setup(t)
+ original := strfmt.DateTime(time.Date(2024, 6, 15, 12, 30, 45, 0, time.UTC))
+
+ doc := bson.M{"_id": "datetime_test", "value": original}
+ result := roundTrip(t, coll, doc)
+
+ // DateTime uses MarshalBSONValue, so MongoDB stores it as a native datetime.
+ dt, ok := result["value"].(bson.DateTime)
+ require.TrueT(t, ok, "expected bson.DateTime, got %T", result["value"])
+
+ got := strfmt.DateTime(dt.Time())
+
+ assert.EqualT(t, time.Time(original).UTC().UnixMilli(), time.Time(got).UTC().UnixMilli())
+ })
+
+ t.Run("Duration", func(t *testing.T) {
+ coll := Setup(t)
+ original := strfmt.Duration(42 * time.Second)
+
+ doc := bson.M{"_id": "duration_test", "value": original}
+ result := roundTrip(t, coll, doc)
+
+ raw, ok := result["value"].(bson.D)
+ require.TrueT(t, ok, "expected bson.D for value, got %T", result["value"])
+
+ rawBytes, err := bson.Marshal(raw)
+ require.NoError(t, err)
+
+ var got strfmt.Duration
+ require.NoError(t, bson.Unmarshal(rawBytes, &got))
+
+ assert.EqualT(t, original, got)
+ })
+
+ t.Run("Base64", func(t *testing.T) {
+ coll := Setup(t)
+ payload := []byte("hello world with special chars: éàü")
+ original := strfmt.Base64(payload)
+
+ doc := bson.M{"_id": "base64_test", "value": original}
+ result := roundTrip(t, coll, doc)
+
+ raw, ok := result["value"].(bson.D)
+ require.TrueT(t, ok, "expected bson.D for value, got %T", result["value"])
+
+ rawBytes, err := bson.Marshal(raw)
+ require.NoError(t, err)
+
+ var got strfmt.Base64
+ require.NoError(t, bson.Unmarshal(rawBytes, &got))
+
+ assert.EqualT(t, base64.StdEncoding.EncodeToString(original), base64.StdEncoding.EncodeToString(got))
+ })
+
+ t.Run("ULID", func(t *testing.T) {
+ coll := Setup(t)
+ original, err := strfmt.ParseULID("01ARZ3NDEKTSV4RRFFQ69G5FAV")
+ require.NoError(t, err)
+
+ doc := bson.M{"_id": "ulid_test", "value": original}
+ result := roundTrip(t, coll, doc)
+
+ raw, ok := result["value"].(bson.D)
+ require.TrueT(t, ok, "expected bson.D for value, got %T", result["value"])
+
+ rawBytes, err := bson.Marshal(raw)
+ require.NoError(t, err)
+
+ var got strfmt.ULID
+ require.NoError(t, bson.Unmarshal(rawBytes, &got))
+
+ assert.EqualT(t, original, got)
+ })
+
+ t.Run("ObjectId", func(t *testing.T) {
+ coll := Setup(t)
+ original := strfmt.NewObjectId("507f1f77bcf86cd799439011")
+
+ doc := bson.M{"_id": "objectid_test", "value": original}
+ result := roundTrip(t, coll, doc)
+
+ // ObjectId uses MarshalBSONValue, so MongoDB stores it as a native ObjectID.
+ oid, ok := result["value"].(bson.ObjectID)
+ require.TrueT(t, ok, "expected bson.ObjectID, got %T", result["value"])
+
+ got := strfmt.ObjectId(oid)
+
+ assert.EqualT(t, original, got)
+ })
+
+ t.Run("URI", func(t *testing.T) {
+ coll := Setup(t)
+ original := strfmt.URI("https://example.com/path?q=1")
+ var got strfmt.URI
+ stringFormatRoundTrip(t, coll, "uri_test", original, &got)
+ assert.EqualT(t, original, got)
+ })
+
+ t.Run("Email", func(t *testing.T) {
+ coll := Setup(t)
+ original := strfmt.Email("user@example.com")
+ var got strfmt.Email
+ stringFormatRoundTrip(t, coll, "email_test", original, &got)
+ assert.EqualT(t, original, got)
+ })
+
+ t.Run("Hostname", func(t *testing.T) {
+ coll := Setup(t)
+ original := strfmt.Hostname("example.com")
+ var got strfmt.Hostname
+ stringFormatRoundTrip(t, coll, "hostname_test", original, &got)
+ assert.EqualT(t, original, got)
+ })
+
+ t.Run("IPv4", func(t *testing.T) {
+ coll := Setup(t)
+ original := strfmt.IPv4("192.168.1.1")
+ var got strfmt.IPv4
+ stringFormatRoundTrip(t, coll, "ipv4_test", original, &got)
+ assert.EqualT(t, original, got)
+ })
+
+ t.Run("IPv6", func(t *testing.T) {
+ coll := Setup(t)
+ original := strfmt.IPv6("::1")
+ var got strfmt.IPv6
+ stringFormatRoundTrip(t, coll, "ipv6_test", original, &got)
+ assert.EqualT(t, original, got)
+ })
+
+ t.Run("CIDR", func(t *testing.T) {
+ coll := Setup(t)
+ original := strfmt.CIDR("192.168.1.0/24")
+ var got strfmt.CIDR
+ stringFormatRoundTrip(t, coll, "cidr_test", original, &got)
+ assert.EqualT(t, original, got)
+ })
+
+ t.Run("MAC", func(t *testing.T) {
+ coll := Setup(t)
+ original := strfmt.MAC("01:02:03:04:05:06")
+ var got strfmt.MAC
+ stringFormatRoundTrip(t, coll, "mac_test", original, &got)
+ assert.EqualT(t, original, got)
+ })
+
+ t.Run("UUID", func(t *testing.T) {
+ coll := Setup(t)
+ original := strfmt.UUID("a8098c1a-f86e-11da-bd1a-00112444be1e")
+ var got strfmt.UUID
+ stringFormatRoundTrip(t, coll, "uuid_test", original, &got)
+ assert.EqualT(t, original, got)
+ })
+
+ t.Run("UUID3", func(t *testing.T) {
+ coll := Setup(t)
+ original := strfmt.UUID3("bcd02ab7-6beb-3467-84c0-3bdbea962817")
+ var got strfmt.UUID3
+ stringFormatRoundTrip(t, coll, "uuid3_test", original, &got)
+ assert.EqualT(t, original, got)
+ })
+
+ t.Run("UUID4", func(t *testing.T) {
+ coll := Setup(t)
+ original := strfmt.UUID4("025b0d74-00a2-4885-af46-084e7fbd0701")
+ var got strfmt.UUID4
+ stringFormatRoundTrip(t, coll, "uuid4_test", original, &got)
+ assert.EqualT(t, original, got)
+ })
+
+ t.Run("UUID5", func(t *testing.T) {
+ coll := Setup(t)
+ original := strfmt.UUID5("886313e1-3b8a-5372-9b90-0c9aee199e5d")
+ var got strfmt.UUID5
+ stringFormatRoundTrip(t, coll, "uuid5_test", original, &got)
+ assert.EqualT(t, original, got)
+ })
+
+ t.Run("UUID7", func(t *testing.T) {
+ coll := Setup(t)
+ original := strfmt.UUID7("01943ff8-3e9e-7be4-8921-de6a1e04d599")
+ var got strfmt.UUID7
+ stringFormatRoundTrip(t, coll, "uuid7_test", original, &got)
+ assert.EqualT(t, original, got)
+ })
+
+ t.Run("ISBN", func(t *testing.T) {
+ coll := Setup(t)
+ original := strfmt.ISBN("0321751043")
+ var got strfmt.ISBN
+ stringFormatRoundTrip(t, coll, "isbn_test", original, &got)
+ assert.EqualT(t, original, got)
+ })
+
+ t.Run("ISBN10", func(t *testing.T) {
+ coll := Setup(t)
+ original := strfmt.ISBN10("0321751043")
+ var got strfmt.ISBN10
+ stringFormatRoundTrip(t, coll, "isbn10_test", original, &got)
+ assert.EqualT(t, original, got)
+ })
+
+ t.Run("ISBN13", func(t *testing.T) {
+ coll := Setup(t)
+ original := strfmt.ISBN13("978-0321751041")
+ var got strfmt.ISBN13
+ stringFormatRoundTrip(t, coll, "isbn13_test", original, &got)
+ assert.EqualT(t, original, got)
+ })
+
+ t.Run("CreditCard", func(t *testing.T) {
+ coll := Setup(t)
+ original := strfmt.CreditCard("4111-1111-1111-1111")
+ var got strfmt.CreditCard
+ stringFormatRoundTrip(t, coll, "creditcard_test", original, &got)
+ assert.EqualT(t, original, got)
+ })
+
+ t.Run("SSN", func(t *testing.T) {
+ coll := Setup(t)
+ original := strfmt.SSN("111-11-1111")
+ var got strfmt.SSN
+ stringFormatRoundTrip(t, coll, "ssn_test", original, &got)
+ assert.EqualT(t, original, got)
+ })
+
+ t.Run("HexColor", func(t *testing.T) {
+ coll := Setup(t)
+ original := strfmt.HexColor("#FFFFFF")
+ var got strfmt.HexColor
+ stringFormatRoundTrip(t, coll, "hexcolor_test", original, &got)
+ assert.EqualT(t, original, got)
+ })
+
+ t.Run("RGBColor", func(t *testing.T) {
+ coll := Setup(t)
+ original := strfmt.RGBColor("rgb(255,255,255)")
+ var got strfmt.RGBColor
+ stringFormatRoundTrip(t, coll, "rgbcolor_test", original, &got)
+ assert.EqualT(t, original, got)
+ })
+
+ t.Run("Password", func(t *testing.T) {
+ coll := Setup(t)
+ original := strfmt.Password("super secret stuff here")
+ var got strfmt.Password
+ stringFormatRoundTrip(t, coll, "password_test", original, &got)
+ assert.EqualT(t, original, got)
+ })
+}
diff --git a/mongo.go b/mongo.go
index 3be76f0..be904ff 100644
--- a/mongo.go
+++ b/mongo.go
@@ -9,68 +9,89 @@ import (
"fmt"
"time"
+ "github.com/go-openapi/strfmt/internal/bsonlite"
"github.com/oklog/ulid/v2"
- "go.mongodb.org/mongo-driver/v2/bson"
)
+// bsonMarshaler is satisfied by types implementing MarshalBSON.
+type bsonMarshaler interface {
+ MarshalBSON() ([]byte, error)
+}
+
+// bsonUnmarshaler is satisfied by types implementing UnmarshalBSON.
+type bsonUnmarshaler interface {
+ UnmarshalBSON(data []byte) error
+}
+
+// bsonValueMarshaler is satisfied by types implementing MarshalBSONValue.
+type bsonValueMarshaler interface {
+ MarshalBSONValue() (byte, []byte, error)
+}
+
+// bsonValueUnmarshaler is satisfied by types implementing UnmarshalBSONValue.
+type bsonValueUnmarshaler interface {
+ UnmarshalBSONValue(tpe byte, data []byte) error
+}
+
+// Compile-time interface checks.
var (
- _ bson.Marshaler = Date{}
- _ bson.Unmarshaler = &Date{}
- _ bson.Marshaler = Base64{}
- _ bson.Unmarshaler = &Base64{}
- _ bson.Marshaler = Duration(0)
- _ bson.Unmarshaler = (*Duration)(nil)
- _ bson.Marshaler = DateTime{}
- _ bson.Unmarshaler = &DateTime{}
- _ bson.Marshaler = ULID{}
- _ bson.Unmarshaler = &ULID{}
- _ bson.Marshaler = URI("")
- _ bson.Unmarshaler = (*URI)(nil)
- _ bson.Marshaler = Email("")
- _ bson.Unmarshaler = (*Email)(nil)
- _ bson.Marshaler = Hostname("")
- _ bson.Unmarshaler = (*Hostname)(nil)
- _ bson.Marshaler = IPv4("")
- _ bson.Unmarshaler = (*IPv4)(nil)
- _ bson.Marshaler = IPv6("")
- _ bson.Unmarshaler = (*IPv6)(nil)
- _ bson.Marshaler = CIDR("")
- _ bson.Unmarshaler = (*CIDR)(nil)
- _ bson.Marshaler = MAC("")
- _ bson.Unmarshaler = (*MAC)(nil)
- _ bson.Marshaler = Password("")
- _ bson.Unmarshaler = (*Password)(nil)
- _ bson.Marshaler = UUID("")
- _ bson.Unmarshaler = (*UUID)(nil)
- _ bson.Marshaler = UUID3("")
- _ bson.Unmarshaler = (*UUID3)(nil)
- _ bson.Marshaler = UUID4("")
- _ 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("")
- _ bson.Unmarshaler = (*ISBN10)(nil)
- _ bson.Marshaler = ISBN13("")
- _ bson.Unmarshaler = (*ISBN13)(nil)
- _ bson.Marshaler = CreditCard("")
- _ bson.Unmarshaler = (*CreditCard)(nil)
- _ bson.Marshaler = SSN("")
- _ bson.Unmarshaler = (*SSN)(nil)
- _ bson.Marshaler = HexColor("")
- _ bson.Unmarshaler = (*HexColor)(nil)
- _ bson.Marshaler = RGBColor("")
- _ bson.Unmarshaler = (*RGBColor)(nil)
- _ bson.Marshaler = ObjectId{}
- _ bson.Unmarshaler = &ObjectId{}
-
- _ bson.ValueMarshaler = DateTime{}
- _ bson.ValueUnmarshaler = &DateTime{}
- _ bson.ValueMarshaler = ObjectId{}
- _ bson.ValueUnmarshaler = &ObjectId{}
+ _ bsonMarshaler = Date{}
+ _ bsonUnmarshaler = &Date{}
+ _ bsonMarshaler = Base64{}
+ _ bsonUnmarshaler = &Base64{}
+ _ bsonMarshaler = Duration(0)
+ _ bsonUnmarshaler = (*Duration)(nil)
+ _ bsonMarshaler = DateTime{}
+ _ bsonUnmarshaler = &DateTime{}
+ _ bsonMarshaler = ULID{}
+ _ bsonUnmarshaler = &ULID{}
+ _ bsonMarshaler = URI("")
+ _ bsonUnmarshaler = (*URI)(nil)
+ _ bsonMarshaler = Email("")
+ _ bsonUnmarshaler = (*Email)(nil)
+ _ bsonMarshaler = Hostname("")
+ _ bsonUnmarshaler = (*Hostname)(nil)
+ _ bsonMarshaler = IPv4("")
+ _ bsonUnmarshaler = (*IPv4)(nil)
+ _ bsonMarshaler = IPv6("")
+ _ bsonUnmarshaler = (*IPv6)(nil)
+ _ bsonMarshaler = CIDR("")
+ _ bsonUnmarshaler = (*CIDR)(nil)
+ _ bsonMarshaler = MAC("")
+ _ bsonUnmarshaler = (*MAC)(nil)
+ _ bsonMarshaler = Password("")
+ _ bsonUnmarshaler = (*Password)(nil)
+ _ bsonMarshaler = UUID("")
+ _ bsonUnmarshaler = (*UUID)(nil)
+ _ bsonMarshaler = UUID3("")
+ _ bsonUnmarshaler = (*UUID3)(nil)
+ _ bsonMarshaler = UUID4("")
+ _ bsonUnmarshaler = (*UUID4)(nil)
+ _ bsonMarshaler = UUID5("")
+ _ bsonUnmarshaler = (*UUID5)(nil)
+ _ bsonMarshaler = UUID7("")
+ _ bsonUnmarshaler = (*UUID7)(nil)
+ _ bsonMarshaler = ISBN("")
+ _ bsonUnmarshaler = (*ISBN)(nil)
+ _ bsonMarshaler = ISBN10("")
+ _ bsonUnmarshaler = (*ISBN10)(nil)
+ _ bsonMarshaler = ISBN13("")
+ _ bsonUnmarshaler = (*ISBN13)(nil)
+ _ bsonMarshaler = CreditCard("")
+ _ bsonUnmarshaler = (*CreditCard)(nil)
+ _ bsonMarshaler = SSN("")
+ _ bsonUnmarshaler = (*SSN)(nil)
+ _ bsonMarshaler = HexColor("")
+ _ bsonUnmarshaler = (*HexColor)(nil)
+ _ bsonMarshaler = RGBColor("")
+ _ bsonUnmarshaler = (*RGBColor)(nil)
+ _ bsonMarshaler = ObjectId{}
+ _ bsonUnmarshaler = &ObjectId{}
+
+ _ bsonValueMarshaler = DateTime{}
+ _ bsonValueUnmarshaler = &DateTime{}
+ _ bsonValueMarshaler = ObjectId{}
+ _ bsonValueUnmarshaler = &ObjectId{}
)
const (
@@ -80,98 +101,104 @@ const (
)
func (d Date) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": d.String()})
+ return bsonlite.C.MarshalDoc(d.String())
}
func (d *Date) UnmarshalBSON(data []byte) error {
- var m bson.M
- if err := bson.Unmarshal(data, &m); err != nil {
+ v, err := bsonlite.C.UnmarshalDoc(data)
+ if err != nil {
return err
}
- if data, ok := m["data"].(string); ok {
- rd, err := time.ParseInLocation(RFC3339FullDate, data, DefaultTimeLocation)
- if err != nil {
- return err
- }
- *d = Date(rd)
- return nil
+ s, ok := v.(string)
+ if !ok {
+ return fmt.Errorf("couldn't unmarshal bson bytes value as Date: %w", ErrFormat)
}
- return fmt.Errorf("couldn't unmarshal bson bytes value as Date: %w", ErrFormat)
+ rd, err := time.ParseInLocation(RFC3339FullDate, s, DefaultTimeLocation)
+ if err != nil {
+ return err
+ }
+ *d = Date(rd)
+ return nil
}
// MarshalBSON document from this value.
func (b Base64) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": b.String()})
+ return bsonlite.C.MarshalDoc(b.String())
}
// UnmarshalBSON document into this value.
func (b *Base64) UnmarshalBSON(data []byte) error {
- var m bson.M
- if err := bson.Unmarshal(data, &m); err != nil {
+ v, err := bsonlite.C.UnmarshalDoc(data)
+ if err != nil {
return err
}
- if bd, ok := m["data"].(string); ok {
- vb, err := base64.StdEncoding.DecodeString(bd)
- if err != nil {
- return err
- }
- *b = Base64(vb)
- return nil
+ s, ok := v.(string)
+ if !ok {
+ return fmt.Errorf("couldn't unmarshal bson bytes as base64: %w", ErrFormat)
+ }
+
+ vb, err := base64.StdEncoding.DecodeString(s)
+ if err != nil {
+ return err
}
- return fmt.Errorf("couldn't unmarshal bson bytes as base64: %w", ErrFormat)
+ *b = Base64(vb)
+ return nil
}
func (d Duration) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": d.String()})
+ return bsonlite.C.MarshalDoc(d.String())
}
func (d *Duration) UnmarshalBSON(data []byte) error {
- var m bson.M
- if err := bson.Unmarshal(data, &m); err != nil {
+ v, err := bsonlite.C.UnmarshalDoc(data)
+ if err != nil {
return err
}
- if data, ok := m["data"].(string); ok {
- rd, err := ParseDuration(data)
- if err != nil {
- return err
- }
- *d = Duration(rd)
- return nil
+ s, ok := v.(string)
+ if !ok {
+ return fmt.Errorf("couldn't unmarshal bson bytes value as Duration: %w", ErrFormat)
}
- return fmt.Errorf("couldn't unmarshal bson bytes value as Date: %w", ErrFormat)
+ rd, err := ParseDuration(s)
+ if err != nil {
+ return err
+ }
+ *d = Duration(rd)
+ return nil
}
// MarshalBSON renders the [DateTime] as a BSON document.
func (t DateTime) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": t})
+ tNorm := NormalizeTimeForMarshal(time.Time(t))
+ return bsonlite.C.MarshalDoc(tNorm)
}
// UnmarshalBSON reads the [DateTime] from a BSON document.
func (t *DateTime) UnmarshalBSON(data []byte) error {
- var obj struct {
- Data DateTime
- }
-
- if err := bson.Unmarshal(data, &obj); err != nil {
+ v, err := bsonlite.C.UnmarshalDoc(data)
+ if err != nil {
return err
}
- *t = obj.Data
-
+ tv, ok := v.(time.Time)
+ if !ok {
+ return fmt.Errorf("couldn't unmarshal bson bytes value as DateTime: %w", ErrFormat)
+ }
+ *t = DateTime(tv)
return nil
}
+// MarshalBSONValue marshals a [DateTime] as a BSON DateTime value (type 0x09),
+// an int64 representing milliseconds since epoch.
+//
// MarshalBSONValue is an interface implemented by types that can marshal themselves
-// into a BSON document represented as bytes. The bytes returned must be a valid
-// BSON document if the error is nil.
+// into a BSON document represented as bytes.
//
-// Marshals a [DateTime] as a bson.[bson.TypeDateTime], an int64 representing
-// milliseconds since epoch.
+// The bytes returned must be a valid BSON document if the error is nil.
func (t DateTime) MarshalBSONValue() (byte, []byte, error) {
// UnixNano cannot be used directly, the result of calling UnixNano on the zero
// Time is undefined. Thats why we use time.Nanosecond() instead.
@@ -181,15 +208,12 @@ func (t DateTime) MarshalBSONValue() (byte, []byte, error) {
buf := make([]byte, bsonDateTimeSize)
binary.LittleEndian.PutUint64(buf, uint64(i64)) //nolint:gosec // it's okay to handle negative int64 this way
- return byte(bson.TypeDateTime), buf, nil
+ return bsonlite.TypeDateTime, buf, nil
}
-// UnmarshalBSONValue is an interface implemented by types that can unmarshal a
-// BSON value representation of themselves. The BSON bytes and type can be
-// assumed to be valid. UnmarshalBSONValue must copy the BSON value bytes if it
-// wishes to retain the data after returning.
+// UnmarshalBSONValue unmarshals a BSON DateTime value into this [DateTime].
func (t *DateTime) UnmarshalBSONValue(tpe byte, data []byte) error {
- if tpe == byte(bson.TypeNull) {
+ if tpe == bsonlite.TypeNull {
*t = DateTime{}
return nil
}
@@ -206,438 +230,371 @@ func (t *DateTime) UnmarshalBSONValue(tpe byte, data []byte) error {
// MarshalBSON document from this value.
func (u ULID) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": u.String()})
+ return bsonlite.C.MarshalDoc(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 {
+ v, err := bsonlite.C.UnmarshalDoc(data)
+ if 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: %w", err, ErrFormat)
- }
- u.ULID = id
- return nil
+ s, ok := v.(string)
+ if !ok {
+ return fmt.Errorf("couldn't unmarshal bson bytes as ULID: %w", ErrFormat)
+ }
+
+ id, err := ulid.ParseStrict(s)
+ if err != nil {
+ return fmt.Errorf("couldn't parse bson bytes as ULID: %w: %w", err, ErrFormat)
}
- return fmt.Errorf("couldn't unmarshal bson bytes as ULID: %w", ErrFormat)
+ u.ULID = id
+ return nil
+}
+
+// unmarshalBSONString is a helper for string-based strfmt types.
+func unmarshalBSONString(data []byte, typeName string) (string, error) {
+ v, err := bsonlite.C.UnmarshalDoc(data)
+ if err != nil {
+ return "", err
+ }
+ s, ok := v.(string)
+ if !ok {
+ return "", fmt.Errorf("couldn't unmarshal bson bytes as %s: %w", typeName, ErrFormat)
+ }
+ return s, nil
}
// MarshalBSON document from this value.
func (u URI) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": u.String()})
+ return bsonlite.C.MarshalDoc(u.String())
}
// UnmarshalBSON document into this value.
func (u *URI) UnmarshalBSON(data []byte) error {
- var m bson.M
- if err := bson.Unmarshal(data, &m); err != nil {
+ s, err := unmarshalBSONString(data, "uri")
+ if err != nil {
return err
}
-
- if ud, ok := m["data"].(string); ok {
- *u = URI(ud)
- return nil
- }
- return fmt.Errorf("couldn't unmarshal bson bytes as uri: %w", ErrFormat)
+ *u = URI(s)
+ return nil
}
// MarshalBSON document from this value.
func (e Email) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": e.String()})
+ return bsonlite.C.MarshalDoc(e.String())
}
// UnmarshalBSON document into this value.
func (e *Email) UnmarshalBSON(data []byte) error {
- var m bson.M
- if err := bson.Unmarshal(data, &m); err != nil {
+ s, err := unmarshalBSONString(data, "email")
+ if err != nil {
return err
}
-
- if ud, ok := m["data"].(string); ok {
- *e = Email(ud)
- return nil
- }
- return fmt.Errorf("couldn't unmarshal bson bytes as email: %w", ErrFormat)
+ *e = Email(s)
+ return nil
}
// MarshalBSON document from this value.
func (h Hostname) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": h.String()})
+ return bsonlite.C.MarshalDoc(h.String())
}
// UnmarshalBSON document into this value.
func (h *Hostname) UnmarshalBSON(data []byte) error {
- var m bson.M
- if err := bson.Unmarshal(data, &m); err != nil {
+ s, err := unmarshalBSONString(data, "hostname")
+ if err != nil {
return err
}
-
- if ud, ok := m["data"].(string); ok {
- *h = Hostname(ud)
- return nil
- }
- return fmt.Errorf("couldn't unmarshal bson bytes as hostname: %w", ErrFormat)
+ *h = Hostname(s)
+ return nil
}
// MarshalBSON document from this value.
func (u IPv4) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": u.String()})
+ return bsonlite.C.MarshalDoc(u.String())
}
// UnmarshalBSON document into this value.
func (u *IPv4) UnmarshalBSON(data []byte) error {
- var m bson.M
- if err := bson.Unmarshal(data, &m); err != nil {
+ s, err := unmarshalBSONString(data, "ipv4")
+ if err != nil {
return err
}
-
- if ud, ok := m["data"].(string); ok {
- *u = IPv4(ud)
- return nil
- }
- return fmt.Errorf("couldn't unmarshal bson bytes as ipv4: %w", ErrFormat)
+ *u = IPv4(s)
+ return nil
}
// MarshalBSON document from this value.
func (u IPv6) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": u.String()})
+ return bsonlite.C.MarshalDoc(u.String())
}
// UnmarshalBSON document into this value.
func (u *IPv6) UnmarshalBSON(data []byte) error {
- var m bson.M
- if err := bson.Unmarshal(data, &m); err != nil {
+ s, err := unmarshalBSONString(data, "ipv6")
+ if err != nil {
return err
}
-
- if ud, ok := m["data"].(string); ok {
- *u = IPv6(ud)
- return nil
- }
- return fmt.Errorf("couldn't unmarshal bson bytes as ipv6: %w", ErrFormat)
+ *u = IPv6(s)
+ return nil
}
// MarshalBSON document from this value.
func (u CIDR) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": u.String()})
+ return bsonlite.C.MarshalDoc(u.String())
}
// UnmarshalBSON document into this value.
func (u *CIDR) UnmarshalBSON(data []byte) error {
- var m bson.M
- if err := bson.Unmarshal(data, &m); err != nil {
+ s, err := unmarshalBSONString(data, "CIDR")
+ if err != nil {
return err
}
-
- if ud, ok := m["data"].(string); ok {
- *u = CIDR(ud)
- return nil
- }
- return fmt.Errorf("couldn't unmarshal bson bytes as CIDR: %w", ErrFormat)
+ *u = CIDR(s)
+ return nil
}
// MarshalBSON document from this value.
func (u MAC) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": u.String()})
+ return bsonlite.C.MarshalDoc(u.String())
}
// UnmarshalBSON document into this value.
func (u *MAC) UnmarshalBSON(data []byte) error {
- var m bson.M
- if err := bson.Unmarshal(data, &m); err != nil {
+ s, err := unmarshalBSONString(data, "MAC")
+ if err != nil {
return err
}
-
- if ud, ok := m["data"].(string); ok {
- *u = MAC(ud)
- return nil
- }
- return fmt.Errorf("couldn't unmarshal bson bytes as MAC: %w", ErrFormat)
+ *u = MAC(s)
+ return nil
}
// MarshalBSON document from this value.
func (r Password) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": r.String()})
+ return bsonlite.C.MarshalDoc(r.String())
}
// UnmarshalBSON document into this value.
func (r *Password) UnmarshalBSON(data []byte) error {
- var m bson.M
- if err := bson.Unmarshal(data, &m); err != nil {
+ s, err := unmarshalBSONString(data, "Password")
+ if err != nil {
return err
}
-
- if ud, ok := m["data"].(string); ok {
- *r = Password(ud)
- return nil
- }
- return fmt.Errorf("couldn't unmarshal bson bytes as Password: %w", ErrFormat)
+ *r = Password(s)
+ return nil
}
// MarshalBSON document from this value.
func (u UUID) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": u.String()})
+ return bsonlite.C.MarshalDoc(u.String())
}
// UnmarshalBSON document into this value.
func (u *UUID) UnmarshalBSON(data []byte) error {
- var m bson.M
- if err := bson.Unmarshal(data, &m); err != nil {
+ s, err := unmarshalBSONString(data, "UUID")
+ if err != nil {
return err
}
-
- if ud, ok := m["data"].(string); ok {
- *u = UUID(ud)
- return nil
- }
- return fmt.Errorf("couldn't unmarshal bson bytes as UUID: %w", ErrFormat)
+ *u = UUID(s)
+ return nil
}
// MarshalBSON document from this value.
func (u UUID3) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": u.String()})
+ return bsonlite.C.MarshalDoc(u.String())
}
// UnmarshalBSON document into this value.
func (u *UUID3) UnmarshalBSON(data []byte) error {
- var m bson.M
- if err := bson.Unmarshal(data, &m); err != nil {
+ s, err := unmarshalBSONString(data, "UUID3")
+ if err != nil {
return err
}
-
- if ud, ok := m["data"].(string); ok {
- *u = UUID3(ud)
- return nil
- }
- return fmt.Errorf("couldn't unmarshal bson bytes as UUID3: %w", ErrFormat)
+ *u = UUID3(s)
+ return nil
}
// MarshalBSON document from this value.
func (u UUID4) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": u.String()})
+ return bsonlite.C.MarshalDoc(u.String())
}
// UnmarshalBSON document into this value.
func (u *UUID4) UnmarshalBSON(data []byte) error {
- var m bson.M
- if err := bson.Unmarshal(data, &m); err != nil {
+ s, err := unmarshalBSONString(data, "UUID4")
+ if err != nil {
return err
}
-
- if ud, ok := m["data"].(string); ok {
- *u = UUID4(ud)
- return nil
- }
- return fmt.Errorf("couldn't unmarshal bson bytes as UUID4: %w", ErrFormat)
+ *u = UUID4(s)
+ return nil
}
// MarshalBSON document from this value.
func (u UUID5) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": u.String()})
+ return bsonlite.C.MarshalDoc(u.String())
}
// UnmarshalBSON document into this value.
func (u *UUID5) UnmarshalBSON(data []byte) error {
- var m bson.M
- if err := bson.Unmarshal(data, &m); err != nil {
+ s, err := unmarshalBSONString(data, "UUID5")
+ if err != nil {
return err
}
-
- if ud, ok := m["data"].(string); ok {
- *u = UUID5(ud)
- return nil
- }
- return fmt.Errorf("couldn't unmarshal bson bytes as UUID5: %w", ErrFormat)
+ *u = UUID5(s)
+ return nil
}
// MarshalBSON document from this value.
func (u UUID7) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": u.String()})
+ return bsonlite.C.MarshalDoc(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 {
+ s, err := unmarshalBSONString(data, "UUID7")
+ if 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)
+ *u = UUID7(s)
+ return nil
}
// MarshalBSON document from this value.
func (u ISBN) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": u.String()})
+ return bsonlite.C.MarshalDoc(u.String())
}
// UnmarshalBSON document into this value.
func (u *ISBN) UnmarshalBSON(data []byte) error {
- var m bson.M
- if err := bson.Unmarshal(data, &m); err != nil {
+ s, err := unmarshalBSONString(data, "ISBN")
+ if err != nil {
return err
}
-
- if ud, ok := m["data"].(string); ok {
- *u = ISBN(ud)
- return nil
- }
- return fmt.Errorf("couldn't unmarshal bson bytes as ISBN: %w", ErrFormat)
+ *u = ISBN(s)
+ return nil
}
// MarshalBSON document from this value.
func (u ISBN10) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": u.String()})
+ return bsonlite.C.MarshalDoc(u.String())
}
// UnmarshalBSON document into this value.
func (u *ISBN10) UnmarshalBSON(data []byte) error {
- var m bson.M
- if err := bson.Unmarshal(data, &m); err != nil {
+ s, err := unmarshalBSONString(data, "ISBN10")
+ if err != nil {
return err
}
-
- if ud, ok := m["data"].(string); ok {
- *u = ISBN10(ud)
- return nil
- }
- return fmt.Errorf("couldn't unmarshal bson bytes as ISBN10: %w", ErrFormat)
+ *u = ISBN10(s)
+ return nil
}
// MarshalBSON document from this value.
func (u ISBN13) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": u.String()})
+ return bsonlite.C.MarshalDoc(u.String())
}
// UnmarshalBSON document into this value.
func (u *ISBN13) UnmarshalBSON(data []byte) error {
- var m bson.M
- if err := bson.Unmarshal(data, &m); err != nil {
+ s, err := unmarshalBSONString(data, "ISBN13")
+ if err != nil {
return err
}
-
- if ud, ok := m["data"].(string); ok {
- *u = ISBN13(ud)
- return nil
- }
- return fmt.Errorf("couldn't unmarshal bson bytes as ISBN13: %w", ErrFormat)
+ *u = ISBN13(s)
+ return nil
}
// MarshalBSON document from this value.
func (u CreditCard) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": u.String()})
+ return bsonlite.C.MarshalDoc(u.String())
}
// UnmarshalBSON document into this value.
func (u *CreditCard) UnmarshalBSON(data []byte) error {
- var m bson.M
- if err := bson.Unmarshal(data, &m); err != nil {
+ s, err := unmarshalBSONString(data, "CreditCard")
+ if err != nil {
return err
}
-
- if ud, ok := m["data"].(string); ok {
- *u = CreditCard(ud)
- return nil
- }
- return fmt.Errorf("couldn't unmarshal bson bytes as CreditCard: %w", ErrFormat)
+ *u = CreditCard(s)
+ return nil
}
// MarshalBSON document from this value.
func (u SSN) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": u.String()})
+ return bsonlite.C.MarshalDoc(u.String())
}
// UnmarshalBSON document into this value.
func (u *SSN) UnmarshalBSON(data []byte) error {
- var m bson.M
- if err := bson.Unmarshal(data, &m); err != nil {
+ s, err := unmarshalBSONString(data, "SSN")
+ if err != nil {
return err
}
-
- if ud, ok := m["data"].(string); ok {
- *u = SSN(ud)
- return nil
- }
- return fmt.Errorf("couldn't unmarshal bson bytes as SSN: %w", ErrFormat)
+ *u = SSN(s)
+ return nil
}
// MarshalBSON document from this value.
func (h HexColor) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": h.String()})
+ return bsonlite.C.MarshalDoc(h.String())
}
// UnmarshalBSON document into this value.
func (h *HexColor) UnmarshalBSON(data []byte) error {
- var m bson.M
- if err := bson.Unmarshal(data, &m); err != nil {
+ s, err := unmarshalBSONString(data, "HexColor")
+ if err != nil {
return err
}
-
- if ud, ok := m["data"].(string); ok {
- *h = HexColor(ud)
- return nil
- }
- return fmt.Errorf("couldn't unmarshal bson bytes as HexColor: %w", ErrFormat)
+ *h = HexColor(s)
+ return nil
}
// MarshalBSON document from this value.
func (r RGBColor) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": r.String()})
+ return bsonlite.C.MarshalDoc(r.String())
}
// UnmarshalBSON document into this value.
func (r *RGBColor) UnmarshalBSON(data []byte) error {
- var m bson.M
- if err := bson.Unmarshal(data, &m); err != nil {
+ s, err := unmarshalBSONString(data, "RGBColor")
+ if err != nil {
return err
}
-
- if ud, ok := m["data"].(string); ok {
- *r = RGBColor(ud)
- return nil
- }
- return fmt.Errorf("couldn't unmarshal bson bytes as RGBColor: %w", ErrFormat)
+ *r = RGBColor(s)
+ return nil
}
// MarshalBSON renders the object id as a BSON document.
func (id ObjectId) MarshalBSON() ([]byte, error) {
- return bson.Marshal(bson.M{"data": bson.ObjectID(id)})
+ return bsonlite.C.MarshalDoc([12]byte(id))
}
// UnmarshalBSON reads the objectId from a BSON document.
func (id *ObjectId) UnmarshalBSON(data []byte) error {
- var obj struct {
- Data bson.ObjectID
- }
- if err := bson.Unmarshal(data, &obj); err != nil {
+ v, err := bsonlite.C.UnmarshalDoc(data)
+ if err != nil {
return err
}
- *id = ObjectId(obj.Data)
+
+ oid, ok := v.([12]byte)
+ if !ok {
+ return fmt.Errorf("couldn't unmarshal bson bytes as ObjectId: %w", ErrFormat)
+ }
+ *id = ObjectId(oid)
return nil
}
-// MarshalBSONValue is an interface implemented by types that can marshal themselves
-// into a BSON document represented as bytes. The bytes returned must be a valid
-// BSON document if the error is nil.
+// MarshalBSONValue marshals the [ObjectId] as a raw BSON ObjectID value.
func (id ObjectId) MarshalBSONValue() (byte, []byte, error) {
- oid := bson.ObjectID(id)
- return byte(bson.TypeObjectID), oid[:], nil
+ oid := [12]byte(id)
+ return bsonlite.TypeObjectID, oid[:], nil
}
-// UnmarshalBSONValue is an interface implemented by types that can unmarshal a
-// BSON value representation of themselves. The BSON bytes and type can be
-// assumed to be valid. UnmarshalBSONValue must copy the BSON value bytes if it
-// wishes to retain the data after returning.
+// UnmarshalBSONValue unmarshals a raw BSON ObjectID value into this [ObjectId].
func (id *ObjectId) UnmarshalBSONValue(_ byte, data []byte) error {
- var oid bson.ObjectID
+ var oid [12]byte
copy(oid[:], data)
*id = ObjectId(oid)
return nil
diff --git a/mongo_test.go b/mongo_test.go
index 4fbfa37..defec76 100644
--- a/mongo_test.go
+++ b/mongo_test.go
@@ -11,24 +11,23 @@ import (
"github.com/go-openapi/testify/v2/assert"
"github.com/go-openapi/testify/v2/require"
"github.com/google/uuid"
- "go.mongodb.org/mongo-driver/v2/bson"
)
type testableBSONFormat interface {
testableFormat
- bson.Marshaler
- bson.Unmarshaler
+ bsonMarshaler
+ bsonUnmarshaler
}
func TestBSONDate(t *testing.T) {
dateOriginal := Date(time.Date(2014, 10, 10, 0, 0, 0, 0, time.UTC))
- bsonData, err := bson.Marshal(&dateOriginal)
+ bsonData, err := dateOriginal.MarshalBSON()
require.NoError(t, err)
var dateCopy Date
- err = bson.Unmarshal(bsonData, &dateCopy)
+ err = dateCopy.UnmarshalBSON(bsonData)
require.NoError(t, err)
assert.EqualT(t, dateOriginal, dateCopy)
}
@@ -38,22 +37,22 @@ func TestBSONBase64(t *testing.T) {
b := []byte(b64)
subj := Base64(b)
- bsonData, err := bson.Marshal(subj)
+ bsonData, err := subj.MarshalBSON()
require.NoError(t, err)
var b64Copy Base64
- err = bson.Unmarshal(bsonData, &b64Copy)
+ err = b64Copy.UnmarshalBSON(bsonData)
require.NoError(t, err)
assert.Equal(t, subj, b64Copy)
}
func TestBSONDuration(t *testing.T) {
dur := Duration(42)
- bsonData, err := bson.Marshal(&dur)
+ bsonData, err := dur.MarshalBSON()
require.NoError(t, err)
var durCopy Duration
- err = bson.Unmarshal(bsonData, &durCopy)
+ err = durCopy.UnmarshalBSON(bsonData)
require.NoError(t, err)
assert.EqualT(t, dur, durCopy)
}
@@ -63,27 +62,14 @@ func TestBSONDateTime(t *testing.T) {
t.Logf("Case #%d", caseNum)
dt := DateTime(example.time)
- bsonData, err := bson.Marshal(&dt)
+ bsonData, err := dt.MarshalBSON()
require.NoError(t, err)
var dtCopy DateTime
- err = bson.Unmarshal(bsonData, &dtCopy)
+ err = dtCopy.UnmarshalBSON(bsonData)
require.NoError(t, err)
// BSON DateTime type loses timezone information, so compare UTC()
assert.EqualT(t, time.Time(dt).UTC(), time.Time(dtCopy).UTC())
-
- // Check value marshaling explicitly
- m := bson.M{"data": dt}
- bsonData, err = bson.Marshal(&m)
- require.NoError(t, err)
-
- var mCopy bson.M
- err = bson.Unmarshal(bsonData, &mCopy)
- require.NoError(t, err)
-
- data, ok := m["data"].(DateTime)
- assert.TrueT(t, ok)
- assert.EqualT(t, time.Time(dt).UTC(), time.Time(data).UTC())
}
}
@@ -93,35 +79,22 @@ func TestBSONULID(t *testing.T) {
t.Parallel()
ulid, _ := ParseULID(testUlid)
- bsonData, err := bson.Marshal(&ulid)
+ bsonData, err := ulid.MarshalBSON()
require.NoError(t, err)
var ulidUnmarshaled ULID
- err = bson.Unmarshal(bsonData, &ulidUnmarshaled)
+ err = ulidUnmarshaled.UnmarshalBSON(bsonData)
require.NoError(t, err)
assert.EqualT(t, ulid, ulidUnmarshaled)
-
- // Check value marshaling explicitly
- m := bson.M{"data": ulid}
- bsonData, err = bson.Marshal(&m)
- require.NoError(t, err)
-
- var mUnmarshaled bson.M
- err = bson.Unmarshal(bsonData, &mUnmarshaled)
- require.NoError(t, err)
-
- data, ok := m["data"].(ULID)
- assert.TrueT(t, ok)
- assert.EqualT(t, ulid, data)
})
t.Run("negative", func(t *testing.T) {
t.Parallel()
- uuid := UUID("00000000-0000-0000-0000-000000000000")
- bsonData, err := bson.Marshal(&uuid)
+ uid := UUID("00000000-0000-0000-0000-000000000000")
+ bsonData, err := uid.MarshalBSON()
require.NoError(t, err)
var ulidUnmarshaled ULID
- err = bson.Unmarshal(bsonData, &ulidUnmarshaled)
+ err = ulidUnmarshaled.UnmarshalBSON(bsonData)
require.Error(t, err)
})
}
@@ -282,12 +255,12 @@ func testBSONStringFormat(t *testing.T, what testableBSONFormat, format, with st
require.NoError(t, err)
// bson encoding interface
- bsonData, err := bson.Marshal(what)
+ bsonData, err := what.MarshalBSON()
require.NoError(t, err)
resetValue(t, format, what)
- err = bson.Unmarshal(bsonData, what)
+ err = what.UnmarshalBSON(bsonData)
require.NoError(t, err)
val := reflect.Indirect(reflect.ValueOf(what))
strVal := val.String()
diff --git a/time.go b/time.go
index d7424b5..c07ae04 100644
--- a/time.go
+++ b/time.go
@@ -169,7 +169,7 @@ func (t *DateTime) UnmarshalText(text []byte) error {
// Scan scans a [DateTime] value from database driver type.
func (t *DateTime) Scan(raw any) error {
- // TODO: case int64: and case float64: ?
+ // Proposal for enhancement: case int64: and case float64: ?
switch v := raw.(type) {
case []byte:
return t.UnmarshalText(v)