pax_global_header00006660000000000000000000000064151743666360014533gustar00rootroot0000000000000052 comment=fb29dd2ad0368c261d68cf46f437dec497af0cf0 go-openapi-strfmt-879971e/000077500000000000000000000000001517436663600154055ustar00rootroot00000000000000go-openapi-strfmt-879971e/.claude/000077500000000000000000000000001517436663600167205ustar00rootroot00000000000000go-openapi-strfmt-879971e/.claude/.gitignore000066400000000000000000000000401517436663600207020ustar00rootroot00000000000000commands/ hooks/ plans/ skills/ go-openapi-strfmt-879971e/.claude/CLAUDE.md000066400000000000000000000122311517436663600201760ustar00rootroot00000000000000# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview Go library that gives first-class Go types to the `format` keyword from [JSON Schema](https://json-schema.org/understanding-json-schema/reference/string#format) and [OpenAPI](https://spec.openapis.org/oas/v3.1.1.html#data-types). In those specs, `format` is a string annotation that hints at richer semantics — `"date-time"`, `"uuid"`, `"email"`, `"uri"`, `"duration"`, etc. This package turns each format into a concrete Go type (`DateTime`, `UUID`, `Email`, …) that knows how to validate, parse, and round-trip through JSON, SQL (`driver.Valuer`/`sql.Scanner`), text, and BSON encodings. A pluggable `Registry` lets consumers (code generators, validators, runtime libraries) discover and work with formats generically. The package is a foundational building block of the [go-swagger](https://github.com/go-swagger/go-swagger) ecosystem, but is useful anywhere OpenAPI/JSON Schema formats need to travel across serialization boundaries. See [docs/MAINTAINERS.md](../docs/MAINTAINERS.md) for CI/CD, release process, and repo structure details. ### Package layout This is a mono-repo with multiple Go modules tied together by `go.work`. #### Root module (`github.com/go-openapi/strfmt`) | File | Contents | |------|----------| | `doc.go` | Package-level godoc | | `ifaces.go` | Core interfaces: `Format` (string + text marshaling) and `Registry` (format registration, validation, parsing) | | `format.go` | `Default` registry, `NewFormats()`, `NewSeededFormats()`, `NameNormalizer`, registry implementation | | `default.go` | Simple string-wrapper types: `Base64`, `URI`, `Email`, `Hostname`, `IPv4`, `IPv6`, `CIDR`, `MAC`, `UUID`/`UUID3`/`UUID4`/`UUID5`/`UUID7`, `ISBN`/`ISBN10`/`ISBN13`, `CreditCard`, `SSN`, `HexColor`, `RGBColor`, `Password`; validators (`IsEmail`, `IsURI`, …) | | `date.go` | `Date` type (wraps `time.Time`, RFC3339 full-date format) | | `time.go` | `DateTime` type (wraps `time.Time`, RFC3339 date-time with flexible parsing) | | `duration.go` | `Duration` type (wraps `time.Duration`, ISO 8601 duration parsing) | | `ulid.go` | `ULID` type (wraps `oklog/ulid`) | | `bson.go` | `ObjectId` type (`[12]byte`), hex encoding, no mongo-driver dependency | | `mongo.go` | `MarshalBSON`/`UnmarshalBSON` implementations for all types, uses `internal/bsonlite` codec | | `errors.go` | `ErrFormat` sentinel error | | `conv/` | Pointer-helper sub-package: `conv.Date(v)` → `*Date`, `conv.DateValue(p)` → `Date`, etc. for every type | #### Internal packages (`internal/`) | Package | Contents | |---------|----------| | `internal/bsonlite` | Minimal BSON codec (wire-compatible with mongo-driver v2.5.0), handles single-key documents with string/DateTime/ObjectID values | #### Sub-modules | Module | Contents | |--------|----------| | `enable/mongodb` | Blank-import package that swaps the built-in `bsonlite` codec with the real MongoDB driver codec | | `internal/testintegration` | Integration tests against real databases (MongoDB, MariaDB, PostgreSQL) | ### Key API - `Format` interface — `String()` + `encoding.TextMarshaler` / `encoding.TextUnmarshaler` - `Registry` interface — `Add()`, `DelByName()`, `GetType()`, `ContainsName()`, `Validates()`, `Parse()`, `MapStructureHookFunc()` - `Default` — the global `Registry` instance, pre-seeded with all built-in formats - `NewFormats()` — creates a new registry seeded from `Default` - `NewSeededFormats(seeds, normalizer)` — creates a registry with custom seeds and name normalizer - Format types — `Date`, `DateTime`, `Duration`, `ULID`, `ObjectId`, `UUID`, `Email`, `URI`, `Hostname`, `Base64`, etc. - Validators — `IsDate()`, `IsDateTime()`, `IsDuration()`, `IsUUID()`, `IsEmail()`, `IsHostname()`, etc. ### Dependencies - `github.com/go-openapi/errors` — error types for validation failures - `github.com/go-viper/mapstructure/v2` — decode hook for format-aware struct mapping - `github.com/google/uuid` — UUID parsing and validation - `github.com/oklog/ulid/v2` — ULID implementation - `golang.org/x/net` — IDNA hostname validation - `github.com/go-openapi/testify/v2` — test-only assertions (zero-dep testify fork) ### Notable design decisions - **Self-registering types via `init()`** — each format type file has an `init()` function that registers itself in the `Default` registry. This means importing the package automatically makes all formats available. - **BSON without mongo-driver** — the `internal/bsonlite` package provides a minimal BSON codec so the root module has no dependency on the MongoDB driver. Users who need the full driver import `enable/mongodb` as a blank import to swap in the real codec. - **`ObjectId` is `[12]byte`, not a string** — unlike many Go BSON libraries, `ObjectId` is a fixed-size byte array for zero-allocation hex encoding and efficient comparison. - **`DateTime` accepts flexible input** — the parser tries multiple RFC3339 variations (with/without fractional seconds, Z vs offset) to handle real-world JSON from various sources. - **`Duration` parses ISO 8601** — supports both Go-style (`1h30m`) and ISO 8601 (`P1DT12H`) duration strings. go-openapi-strfmt-879971e/.claude/rules/000077500000000000000000000000001517436663600200525ustar00rootroot00000000000000go-openapi-strfmt-879971e/.claude/rules/comments.md000066400000000000000000000014341517436663600222230ustar00rootroot00000000000000--- paths: - "**/*.go" --- # Comments for strfmt types Formatted types (all types exported by this library, e.g. `DateTime`, `UUID`, ...) come with a special annotation `swagger:strfmt [format]`. These comments are significant and consumed by the `go-swagger` to generate OpenAPI specifications from go source. The comment must reside exactly on a single line, preferably as the last block of comment lines for this type. The `swagger:strfmt` string must remain as-is. The `[format]` string must be the name of the registered format (e.g. `date`, `date-time`, `uuid` etc.). The comment line _may_ be terminated with a `.` to mark the end of the comment sentence (common comment linting rule). ```go // [MyFormattedType] ... // // swagger:strfmt [format]. type [MyFormattedType] ... ``` go-openapi-strfmt-879971e/.claude/rules/contributions.md000066400000000000000000000026151517436663600233020ustar00rootroot00000000000000--- paths: - "**/*" --- # Contribution rules (go-openapi) Read `.github/CONTRIBUTING.md` before opening a pull request. ## Commit hygiene - Every commit **must** be DCO signed-off (`git commit -s`) with a real email address. PGP-signed commits are appreciated but not required. - Agents may be listed as co-authors (`Co-Authored-By:`) but the commit **author must be the human sponsor**. We do not accept commits solely authored by bots or agents. - Squash commits into logical units of work before requesting review (`git rebase -i`). ## Linting Before pushing, verify your changes pass linting against the base branch: ```sh golangci-lint run --new-from-rev master ``` Install the latest version if you don't have it: ```sh go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest ``` ## Problem statement - Clearly describe the problem the PR solves, or reference an existing issue. - PR descriptions must not be vague ("fix bug", "improve code") — explain *what* was wrong and *why* the change is correct. ## Tests are mandatory - Every bug fix or feature **must** include tests that demonstrate the problem and verify the fix. - The only exceptions are documentation changes and typo fixes. - Aim for at least 80% coverage of your patch. - Run the full test suite before submitting: For mono-repos: ```sh go test work ./... ``` For single module repos: ```sh go test ./... ``` go-openapi-strfmt-879971e/.claude/rules/github-workflows-conventions.md000066400000000000000000000206451517436663600262630ustar00rootroot00000000000000--- paths: - ".github/workflows/**.yml" - ".github/workflows/**.yaml" --- # GitHub Actions Workflows Formatting and Style Conventions This rule captures YAML and bash formatting rules to provide a consistent maintainer's experience across CI workflows. ## File Structure **REQUIRED**: All github action workflows are organized as a flat structure beneath `.github/workflows/`. > GitHub does not support a hierarchical organization for workflows yet. **REQUIRED**: YAML files are conventionally named `{workflow}.yml`, with the `.yml` extension. ## Code Style & Formatting ### Expression Spacing **REQUIRED**: All GitHub Actions expressions must have spaces inside the braces: ```yaml # ✅ CORRECT env: PR_URL: ${{ github.event.pull_request.html_url }} TOKEN: ${{ secrets.GITHUB_TOKEN }} # ❌ WRONG env: PR_URL: ${{github.event.pull_request.html_url}} TOKEN: ${{secrets.GITHUB_TOKEN}} ``` > Provides a consistent formatting rule. ### Conditional Syntax **REQUIRED**: Always use `${{ }}` in `if:` conditions: ```yaml # ✅ CORRECT if: ${{ inputs.enable-signing == 'true' }} if: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }} # ❌ WRONG (works but inconsistent) if: inputs.enable-signing == 'true' ``` > Provides a consistent formatting rule. ### GitHub Workflow Commands **REQUIRED**: Use workflow commands for status messages that should appear as annotations, with **double colon separator**: ```bash # ✅ CORRECT - Double colon (::) separator after title echo "::notice title=build::Build completed successfully" echo "::warning title=race-condition::Merge already in progress" echo "::error title=deployment::Failed to deploy" # ❌ WRONG - Single colon separator (won't render as annotation) echo "::notice title=build:Build completed" # Missing second ':' echo "::warning title=x:message" # Won't display correctly ``` **Syntax pattern:** `::LEVEL title=TITLE::MESSAGE` - `LEVEL`: notice, warning, or error - Double `::` separator is required between title and message > Wrong syntax may raise untidy warnings and produce botched output. ### YAML arrays formatting For steps, YAML arrays are formatted with the following indentation: ```yaml # ✅ CORRECT - Clear spacing between steps steps: - name: Dependabot metadata id: metadata uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2.5.0 - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 # ❌ WRONG - Dense format, more difficult to read steps: - name: Dependabot metadata id: metadata uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2.5.0 - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 # ❌ WRONG - YAML comment or blank line could be avoided steps: # - name: Dependabot metadata id: metadata uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2.5.0 - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 ``` ## Security Best Practices ### Version Pinning using SHAs **REQUIRED**: Always pin action versions to commit SHAs: > Runs must be repeatable with known pinned version. Automated updates are pushed frequently (e.g. daily or weekly) > to keep pinned versions up-to-date. ```yaml # ✅ CORRECT - Pinned to commit SHA with version comment uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 uses: crazy-max/ghaction-import-gpg@e89d40939c28e39f97cf32126055eeae86ba74ec # v6.3.0 # ❌ WRONG - Mutable tag reference uses: actions/checkout@v6 ``` ### Permission settings **REQUIRED**: Always set minimal permissions at the workflow level. ```yaml # ✅ CORRECT - Workflow level permissions set to minimum permissions: contents: read # ❌ WRONG - Workflow level permissions with undue privilege escalation permissions: contents: write pull-requests: write ``` **REQUIRED**: Whenever a job needs elevated privileges, always raise required permissions at the job level. ```yaml # ✅ CORRECT - Job level permissions set to the specific requirements for that job jobs: dependabot: permissions: contents: write pull-requests: write uses: ./.github/workflows/auto-merge.yml secrets: inherit # ❌ WRONG - Same permissions but set at workflow level instead of job level permissions: contents: write pull-requests: write ``` > (Security best practice detected by CodeQL analysis) ### Undue secret exposure **NEVER** use `secrets[inputs.name]` — always use explicit secret parameters. > Using keyed access to secrets forces the runner to expose ALL secrets to the job, which causes a security risk > (caught and reported by CodeQL security analysis). ```yaml # ❌ SECURITY VULNERABILITY # This exposes ALL organization and repository secrets to the runner on: workflow_call: inputs: secret-name: type: string jobs: my-job: steps: - uses: some-action@v1 with: token: ${{ secrets[inputs.secret-name] }} # ❌ DANGEROUS! ``` **SOLUTION**: Use explicit secret parameters with fallback for defaults: ```yaml # ✅ SECURE on: workflow_call: secrets: gpg-private-key: required: false jobs: my-job: steps: - uses: go-openapi/gh-actions/ci-jobs/bot-credentials@master with: # Falls back to go-openapi default if not explicitly passed gpg-private-key: ${{ secrets.gpg-private-key || secrets.CI_BOT_GPG_PRIVATE_KEY }} ``` ## Common Gotchas ### Description fields containing parsable expressions **REQUIRED**: **DO NOT** use `${{ }}` expressions in description fields: > They may be parsed by the runner, wrongly interpreted or causing failure (e.g. "not defined in this context"). ```yaml # ❌ WRONG - Can cause YAML parsing errors description: | Pass it as: gpg-private-key: ${{ secrets.MY_KEY }} # ✅ CORRECT description: | Pass it as: secrets.MY_KEY ``` ### Boolean inputs **Boolean inputs are forbidden**: NEVER use `type: boolean` for workflow inputs due to unpredictable type coercion > gh-action expressions using boolean job inputs are hard to predict and come with many quirks. ```yaml # ❌ FORBIDDEN - Boolean inputs have type coercion issues on: workflow_call: inputs: enable-feature: type: boolean # ❌ NEVER USE THIS default: true # The pattern `x == 'true' || x == true` seems safe but fails when: # - x is not a boolean: `x == true` evaluates to true if x != null # - Type coercion is unpredictable and error-prone # ✅ CORRECT - Always use string type for boolean-like inputs on: workflow_call: inputs: enable-feature: type: string # ✅ Use string instead default: 'true' # String value jobs: my-job: # Simple, reliable comparison if: ${{ inputs.enable-feature == 'true' }} # ✅ In bash, this works perfectly (inputs are always strings in bash): if [[ '${{ inputs.enable-feature }}' == 'true' ]]; then echo "Feature enabled" fi ``` **Rule**: Use `type: string` with values `'true'` or `'false'` for all boolean-like workflow inputs. **Note**: Step outputs and bash variables are always strings, so `x == 'true'` works fine for those. ### YAML fold scalars in action inputs **NEVER** use `>` or `>-` (fold scalars) for `with:` input values: > The YAML spec says fold scalars replace newlines with spaces, but the GitHub Actions runner > does not reliably honor this for action inputs. The action receives the literal multi-line string > instead of a single folded line, which breaks flag parsing. ```yaml # ❌ BROKEN - Fold scalar, args received with embedded newlines - uses: goreleaser/goreleaser-action@... with: args: >- release --clean --release-notes /tmp/notes.md # ✅ CORRECT - Single line - uses: goreleaser/goreleaser-action@... with: args: release --clean --release-notes /tmp/notes.md # ✅ CORRECT - Literal block scalar (|) is fine for run: scripts - run: | echo "line 1" echo "line 2" ``` **Rule**: Use single-line strings for `with:` inputs. Only use `|` (literal block scalar) for `run:` scripts where multi-line is intentional. go-openapi-strfmt-879971e/.claude/rules/go-conventions.md000066400000000000000000000004621517436663600233460ustar00rootroot00000000000000--- paths: - "**/*.go" --- # Code conventions (go-openapi) - All files must have SPDX license headers (Apache-2.0). - Go version policy: support the 2 latest stable Go minor versions. - Commits require DCO sign-off (`git commit -s`). - use `golangci-lint fmt` to format code (not `gofmt` or `gofumpt`) go-openapi-strfmt-879971e/.claude/rules/linting.md000066400000000000000000000006251517436663600220430ustar00rootroot00000000000000--- paths: - "**/*.go" --- # Linting conventions (go-openapi) ```sh golangci-lint run ``` Config: `.golangci.yml` — posture is `default: all` with explicit disables. See `docs/STYLE.md` for the rationale behind each disabled linter. Key rules: - Every `//nolint` directive **must** have an inline comment explaining why. - Prefer disabling a linter over scattering `//nolint` across the codebase. go-openapi-strfmt-879971e/.claude/rules/testing.md000066400000000000000000000017121517436663600220520ustar00rootroot00000000000000--- paths: - "**/*_test.go" --- # Testing conventions (go-openapi) ## Running tests **Single module repos:** ```sh go test ./... ``` **Mono-repos (with `go.work`):** ```sh # All modules go test work ./... # Single module go test ./conv/... ``` Note: in mono-repos, plain `go test ./...` only tests the root module. The `work` pattern expands to all modules listed in `go.work`. CI runs tests on `{ubuntu, macos, windows} x {stable, oldstable}` with `-race` via `gotestsum`. ## Fuzz tests ```sh # List all fuzz targets go test -list Fuzz ./... # Run a specific target (go test -fuzz cannot span multiple packages) go test -fuzz=Fuzz -run='FuzzTargetName$' -fuzztime=1m30s ./package ``` Fuzz corpus lives in `testdata/fuzz/` within each package. CI runs each fuzz target for 1m30s with a 5m minimize timeout. ## Test framework `github.com/go-openapi/testify/v2` — a zero-dep fork of `stretchr/testify`. Because it's a fork, `testifylint` does not work. go-openapi-strfmt-879971e/.codecov.yml000066400000000000000000000001541517436663600176300ustar00rootroot00000000000000codecov: notify: after_n_builds: 2 coverage: status: patch: default: target: 80% go-openapi-strfmt-879971e/.editorconfig000066400000000000000000000010331517436663600200570ustar00rootroot00000000000000# top-most EditorConfig file root = true # Unix-style newlines with a newline ending every file [*] end_of_line = lf insert_final_newline = true indent_style = space indent_size = 2 trim_trailing_whitespace = true # Set default charset [*.{js,py,go,scala,rb,java,html,css,less,sass,md}] charset = utf-8 # Tab indentation (no size specified) [*.go] indent_style = tab [*.md] trim_trailing_whitespace = false # Matches the exact files either package.json or .travis.yml [{package.json,.travis.yml}] indent_style = space indent_size = 2 go-openapi-strfmt-879971e/.gitattributes000066400000000000000000000000221517436663600202720ustar00rootroot00000000000000*.go text eol=lf go-openapi-strfmt-879971e/.github/000077500000000000000000000000001517436663600167455ustar00rootroot00000000000000go-openapi-strfmt-879971e/.github/copilot000077700000000000000000000000001517436663600231372../.claude/rulesustar00rootroot00000000000000go-openapi-strfmt-879971e/.github/copilot-instructions.md000066400000000000000000000104611517436663600235040ustar00rootroot00000000000000# Copilot Instructions ## Project Overview Go library that gives first-class Go types to the `format` keyword from [JSON Schema](https://json-schema.org/understanding-json-schema/reference/string#format) and [OpenAPI](https://spec.openapis.org/oas/v3.1.1.html#data-types). In those specs, `format` is a string annotation that hints at richer semantics — `"date-time"`, `"uuid"`, `"email"`, `"uri"`, `"duration"`, etc. This package turns each format into a concrete Go type (`DateTime`, `UUID`, `Email`, …) that knows how to validate, parse, and round-trip through JSON, SQL (`driver.Valuer`/`sql.Scanner`), text, and BSON encodings. A pluggable `Registry` lets consumers (code generators, validators, runtime libraries) discover and work with formats generically. The package is a foundational building block of the [go-swagger](https://github.com/go-swagger/go-swagger) ecosystem, but is useful anywhere OpenAPI/JSON Schema formats need to travel across serialization boundaries. This is a mono-repo (`go.work`) with three modules: root, `enable/mongodb`, `internal/testintegration`. ## Package Layout | File | Contents | |------|----------| | `ifaces.go` | Core interfaces: `Format` (string + text marshaling) and `Registry` (format registration, validation, parsing) | | `format.go` | `Default` registry, `NewFormats()`, `NewSeededFormats()`, `NameNormalizer` | | `default.go` | Simple string-wrapper types: `URI`, `Email`, `Hostname`, `IPv4`, `IPv6`, `CIDR`, `MAC`, `UUID`/`UUID3-7`, `ISBN`, `CreditCard`, `SSN`, `HexColor`, `RGBColor`, `Password`, `Base64`; validators | | `date.go` | `Date` type (wraps `time.Time`, RFC3339 full-date) | | `time.go` | `DateTime` type (wraps `time.Time`, RFC3339 date-time with flexible parsing) | | `duration.go` | `Duration` type (wraps `time.Duration`, ISO 8601 duration parsing) | | `ulid.go` | `ULID` type (wraps `oklog/ulid`) | | `bson.go` | `ObjectId` type (`[12]byte`), hex encoding, no mongo-driver dependency | | `mongo.go` | `MarshalBSON`/`UnmarshalBSON` for all types via `internal/bsonlite` codec | | `errors.go` | `ErrFormat` sentinel error | | `conv/` | Pointer helpers: `conv.Date(v)` → `*Date`, `conv.DateValue(p)` → `Date`, etc. | ## Key API - `Format` interface — `String()` + `encoding.TextMarshaler` / `encoding.TextUnmarshaler` - `Registry` interface — `Add()`, `DelByName()`, `GetType()`, `ContainsName()`, `Validates()`, `Parse()` - `Default` — global `Registry`, pre-seeded with all built-in formats - Format types — `Date`, `DateTime`, `Duration`, `ULID`, `ObjectId`, `UUID`, `Email`, `URI`, `Hostname`, `Base64`, … - Validators — `IsDate()`, `IsDateTime()`, `IsDuration()`, `IsUUID()`, `IsEmail()`, … ## Design Decisions - Types self-register via `init()` — importing the package makes all formats available. - BSON without mongo-driver — `internal/bsonlite` provides a minimal codec; `enable/mongodb` swaps in the real driver. - `ObjectId` is `[12]byte`, not a string — zero-allocation hex encoding, efficient comparison. - `DateTime` accepts flexible RFC3339 input (with/without fractional seconds, Z vs offset). - `Duration` parses both Go-style (`1h30m`) and ISO 8601 (`P1DT12H`) strings. - Formatted types carry a `swagger:strfmt [format]` annotation consumed by go-swagger (see rules). ## Dependencies - `github.com/go-openapi/errors` — validation error types - `github.com/go-viper/mapstructure/v2` — decode hook for format-aware struct mapping - `github.com/google/uuid` — UUID parsing and validation - `github.com/oklog/ulid/v2` — ULID implementation - `golang.org/x/net` — IDNA hostname validation - `github.com/go-openapi/testify/v2` — test-only assertions (zero-dep fork of `stretchr/testify`) ## Conventions - All `.go` files must have SPDX license headers (Apache-2.0). - Commits require DCO sign-off (`git commit -s`). - Linting: `golangci-lint run` — config in `.golangci.yml` (posture: `default: all` with explicit disables). - Every `//nolint` directive **must** have an inline comment explaining why. - Tests: `go test work ./...` (mono-repo). CI runs on `{ubuntu, macos, windows} x {stable, oldstable}` with `-race`. - Test framework: `github.com/go-openapi/testify/v2` (not `stretchr/testify`; `testifylint` does not work). See `.github/copilot/` (symlinked to `.claude/rules/`) for detailed rules on Go conventions, linting, testing, and swagger comments. go-openapi-strfmt-879971e/.github/dependabot.yaml000066400000000000000000000032151517436663600217370ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" day: "friday" open-pull-requests-limit: 2 # <- default is 5 groups: # <- group all github actions updates in a single PR # 1. development-dependencies are auto-merged development-dependencies: patterns: - '*' - package-ecosystem: "gomod" # We define 4 groups of dependencies to regroup update pull requests: # - development (e.g. test dependencies) # - go-openapi updates # - golang.org (e.g. golang.org/x/... packages) # - other dependencies (direct or indirect) # # * All groups are checked once a week and each produce at most 1 PR. # * All dependabot PRs are auto-approved # # Auto-merging policy, when requirements are met: # 1. development-dependencies are auto-merged # 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 directories: - "**/*" schedule: interval: "weekly" day: "friday" open-pull-requests-limit: 4 groups: development-dependencies: patterns: - "github.com/stretchr/testify" golang-org-dependencies: patterns: - "golang.org/*" go-openapi-dependencies: patterns: - "github.com/go-openapi/*" other-dependencies: exclude-patterns: - "github.com/go-openapi/*" - "github.com/stretchr/testify" - "golang.org/*" go-openapi-strfmt-879971e/.github/wordlist.txt000066400000000000000000000006251517436663600213600ustar00rootroot00000000000000APIs BSON DCO FQDN GobDecoder GobEncoder IANA IDNA IPv JSON ObjectID OpenAPI PRs RGB TODO Triaging UI URLEncoding UUIDs XYZ agentic bsonobjectid ci cidr creditcard datetime developercertificate github godoc hexcolor hostname hostnames html http https impl isbn maintainer's marshaling millis nano nanos objectId openapi repo rfc rgbcolor scala ssn unicode unix unmarshal unmarshaling uri validator xXOvVd go-openapi-strfmt-879971e/.github/workflows/000077500000000000000000000000001517436663600210025ustar00rootroot00000000000000go-openapi-strfmt-879971e/.github/workflows/auto-merge.yml000066400000000000000000000004621517436663600235740ustar00rootroot00000000000000name: Dependabot auto-merge permissions: contents: read on: pull_request: jobs: dependabot: permissions: contents: write pull-requests: write uses: go-openapi/ci-workflows/.github/workflows/auto-merge.yml@6ed4490472a56b1d952231565aac80f13c2d143c # v0.2.16 secrets: inherit go-openapi-strfmt-879971e/.github/workflows/bump-release.yml000066400000000000000000000020111517436663600241000ustar00rootroot00000000000000name: Bump Release permissions: contents: read on: workflow_dispatch: inputs: bump-type: description: Type of bump (patch, minor, major) type: choice options: - patch - minor - major default: patch required: false tag-message-title: description: Tag message title to prepend to the release notes required: false type: string tag-message-body: description: | Tag message body to prepend to the release notes. (use "|" to replace end of line). required: false type: string jobs: bump-release: permissions: contents: write pull-requests: write uses: go-openapi/ci-workflows/.github/workflows/bump-release-monorepo.yml@6ed4490472a56b1d952231565aac80f13c2d143c # v0.2.16 with: bump-type: ${{ inputs.bump-type }} tag-message-title: ${{ inputs.tag-message-title }} tag-message-body: ${{ inputs.tag-message-body }} secrets: inherit go-openapi-strfmt-879971e/.github/workflows/codeql.yml000066400000000000000000000007311517436663600227750ustar00rootroot00000000000000name: "CodeQL" on: push: branches: [ "master" ] pull_request: branches: [ "master" ] paths-ignore: # remove this clause if CodeQL is a required check - '**/*.md' schedule: - cron: '39 19 * * 5' permissions: contents: read jobs: codeql: permissions: contents: read security-events: write uses: go-openapi/ci-workflows/.github/workflows/codeql.yml@6ed4490472a56b1d952231565aac80f13c2d143c # v0.2.16 secrets: inherit go-openapi-strfmt-879971e/.github/workflows/contributors.yml000066400000000000000000000005301517436663600242600ustar00rootroot00000000000000name: Contributors on: schedule: - cron: '18 4 * * 6' workflow_dispatch: permissions: contents: read jobs: contributors: permissions: pull-requests: write contents: write uses: go-openapi/ci-workflows/.github/workflows/contributors.yml@6ed4490472a56b1d952231565aac80f13c2d143c # v0.2.16 secrets: inherit go-openapi-strfmt-879971e/.github/workflows/go-test.yml000066400000000000000000000004361517436663600231120ustar00rootroot00000000000000name: go test permissions: pull-requests: read contents: read on: push: branches: - master pull_request: jobs: test: uses: go-openapi/ci-workflows/.github/workflows/go-test-monorepo.yml@6ed4490472a56b1d952231565aac80f13c2d143c # v0.2.16 secrets: inherit go-openapi-strfmt-879971e/.github/workflows/integration-test.yml000066400000000000000000000046461517436663600250370ustar00rootroot00000000000000name: integration test permissions: pull-requests: read contents: read on: push: branches: - master pull_request: jobs: integration-test: runs-on: ubuntu-latest services: mongodb: image: mongo:7 ports: - 27017:27017 mariadb: image: mariadb:10.11 env: MARIADB_ROOT_PASSWORD: root MARIADB_DATABASE: strfmt_integration_test MARIADB_USER: strfmt_test MARIADB_PASSWORD: strfmt_test ports: - 3306:3306 options: >- --health-cmd="healthcheck.sh --connect --innodb_initialized" --health-interval=10s --health-timeout=5s --health-retries=5 postgresql: image: postgres:17 env: POSTGRES_USER: strfmt_test POSTGRES_PASSWORD: strfmt_test POSTGRES_DB: strfmt_integration_test ports: - 5432:5432 options: >- --health-cmd="pg_isready -U strfmt_test -d strfmt_integration_test" --health-interval=10s --health-timeout=5s --health-retries=5 steps: - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version: stable - name: Run integration tests working-directory: internal/testintegration env: MONGODB_URI: mongodb://localhost:27017 MARIADB_DSN: "strfmt_test:strfmt_test@tcp(localhost:3306)/strfmt_integration_test?parseTime=true&loc=UTC" POSTGRESQL_DSN: "postgres://strfmt_test:strfmt_test@localhost:5432/strfmt_integration_test?sslmode=disable" run: > go test -tags testintegration -v -count=1 -coverprofile='integration.coverage.ubuntu-latest-stable.out' -covermode=atomic -coverpkg='github.com/go-openapi/strfmt/...,github.com/go-openapi/strfmt/enable/mongodb/...' ./... - name: Upload coverage artifacts uses: actions/upload-artifact@v7 with: path: 'internal/testintegration/integration.coverage.ubuntu-latest-stable.out' name: 'integration.coverage.ubuntu-latest-stable' retention-days: 1 collect-coverage: needs: [integration-test] if: ${{ !cancelled() && needs.integration-test.result == 'success' }} uses: go-openapi/ci-workflows/.github/workflows/collect-coverage.yml@6ed4490472a56b1d952231565aac80f13c2d143c # v0.2.16 secrets: inherit go-openapi-strfmt-879971e/.github/workflows/scanner.yml000066400000000000000000000005761517436663600231660ustar00rootroot00000000000000name: Vulnerability scans on: branch_protection_rule: push: branches: [ "master" ] schedule: - cron: '18 4 * * 3' permissions: contents: read jobs: scanners: permissions: contents: read security-events: write uses: go-openapi/ci-workflows/.github/workflows/scanner.yml@6ed4490472a56b1d952231565aac80f13c2d143c # v0.2.16 secrets: inherit go-openapi-strfmt-879971e/.github/workflows/tag-release.yml000066400000000000000000000005751517436663600237250ustar00rootroot00000000000000name: Release on tag permissions: contents: read on: push: tags: - v[0-9]+* jobs: gh-release: name: Create release permissions: contents: write uses: go-openapi/ci-workflows/.github/workflows/release.yml@6ed4490472a56b1d952231565aac80f13c2d143c # v0.2.16 with: tag: ${{ github.ref_name }} is-monorepo: true secrets: inherit go-openapi-strfmt-879971e/.gitignore000066400000000000000000000000551517436663600173750ustar00rootroot00000000000000*.out *.cov .idea .env .mcp.json go.work.sum go-openapi-strfmt-879971e/.golangci.yml000066400000000000000000000023131517436663600177700ustar00rootroot00000000000000version: "2" linters: default: all disable: - depguard - funlen - gomoddirectives - godox - exhaustruct - nlreturn - nonamedreturns - noinlineerr - paralleltest - recvcheck - testpackage - thelper - tparallel - varnamelen - whitespace - wrapcheck - wsl - wsl_v5 settings: dupl: threshold: 200 goconst: min-len: 2 min-occurrences: 3 cyclop: max-complexity: 20 gocyclo: min-complexity: 20 exhaustive: default-signifies-exhaustive: true default-case-required: true lll: line-length: 180 exclusions: generated: lax presets: - comments - common-false-positives - legacy - std-error-handling paths: - third_party$ - builtin$ - examples$ formatters: enable: - gofmt - goimports - gofumpt exclusions: generated: lax paths: - third_party$ - builtin$ - examples$ issues: # Maximum issues count per one linter. # Set to 0 to disable. # Default: 50 max-issues-per-linter: 0 # Maximum count of issues with the same text. # Set to 0 to disable. # Default: 3 max-same-issues: 0 go-openapi-strfmt-879971e/AGENTS.md000077700000000000000000000000001517436663600250002.github/copilot-instructions.mdustar00rootroot00000000000000go-openapi-strfmt-879971e/CODE_OF_CONDUCT.md000066400000000000000000000062471517436663600202150ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at . All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ go-openapi-strfmt-879971e/CONTRIBUTORS.md000066400000000000000000000071571517436663600176760ustar00rootroot00000000000000# Contributors - Repository: ['go-openapi/strfmt'] | Total Contributors | Total Contributions | | --- | --- | | 40 | 231 | | Username | All Time Contribution Count | All Commits | | --- | --- | --- | | @casualjim | 88 | | | @fredbi | 63 | | | @youyuanwu | 13 | | | @jlambatl | 9 | | | @GlenDC | 5 | | | @padamstx | 4 | | | @dimovnike | 3 | | | @carlv-stripe | 3 | | | @Copilot | 3 | | | @keramix | 3 | | | @gregmarr | 2 | | | @vadorovsky | 2 | | | @Ompluscator | 2 | | | @johnnyg | 2 | | | @chakrit | 2 | | | @bg451 | 2 | | | @aleksandr-vin | 2 | | | @ujjwalsh | 1 | | | @kenjones-cisco | 1 | | | @jwalter1-quest | 1 | | | @ccoVeille | 1 | | | @tylerb | 1 | | | @tzneal | 1 | | | @tklauser | 1 | | | @SuperQ | 1 | | | @srizzling | 1 | | | @shawnps | 1 | | | @prashantv | 1 | | | @krnkl | 1 | | | @mstoykov | 1 | | | @maxatome | 1 | | | @jerome-laforge | 1 | | | @justincormack | 1 | | | @elipavlov | 1 | | | @gbjk | 1 | | | @enesanbar | 1 | | | @CodeLingoBot | 1 | | | @Kunde21 | 1 | | | @bvwells | 1 | | | @ligustah | 1 | | _this file was generated by the [Contributors GitHub Action](https://github.com/github-community-projects/contributors)_ go-openapi-strfmt-879971e/LICENSE000066400000000000000000000261361517436663600164220ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. go-openapi-strfmt-879971e/README.md000066400000000000000000000215041517436663600166660ustar00rootroot00000000000000# strfmt [![Tests][test-badge]][test-url] [![Coverage][cov-badge]][cov-url] [![CI vuln scan][vuln-scan-badge]][vuln-scan-url] [![CodeQL][codeql-badge]][codeql-url] [![Release][release-badge]][release-url] [![Go Report Card][gocard-badge]][gocard-url] [![CodeFactor Grade][codefactor-badge]][codefactor-url] [![License][license-badge]][license-url] [![GoDoc][godoc-badge]][godoc-url] [![Discord Channel][discord-badge]][discord-url] [![go version][goversion-badge]][goversion-url] ![Top language][top-badge] ![Commits since latest release][commits-badge] --- Golang support for string formats defined by JSON Schema and OpenAPI. ## Announcements * **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. ## Import this library in your project ```cmd go get github.com/go-openapi/strfmt ``` ## Contents This package exposes a registry of data types to support string formats in the go-openapi toolkit. `strfmt` represents a well known string format such as hostname or email. This package provides a few extra formats such as credit card (US), color, etc. Format types can serialize and deserialize JSON or from a SQL database. BSON is also supported (MongoDB). ### Supported formats `go-openapi/strfmt` follows the swagger 2.0 specification with the following formats defined [here](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types). It also provides convenient extensions to go-openapi users. - [x] JSON-schema draft 4 formats - date-time - email - hostname - ipv4 - ipv6 - uri - [x] swagger 2.0 format extensions - binary - byte (e.g. base64 encoded string) - date (e.g. "1970-01-01") - password - [x] go-openapi custom format extensions - bsonobjectid (BSON objectID) - creditcard - duration (e.g. "3 weeks", "1ms") - hexcolor (e.g. "#FFFFFF") - isbn, isbn10, isbn13 - mac (e.g "01:02:03:04:05:06") - rgbcolor (e.g. "rgb(100,100,100)") - ssn - uuid, uuid3, uuid4, uuid5, uuid7 - cidr (e.g. "192.0.2.1/24", "2001:db8:a0b:12f0::1/32") - ulid (e.g. "00000PP9HGSBSSDZ1JTEXBJ0PW", [spec](https://github.com/ulid/spec)) > 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 > "integer" (e.g. float, double, int32...). ### Type conversion All types defined here are stringers and may be converted to strings with `.String()`. Note that most types defined by this package may be converted directly to string like `string(Email{})`. `Date` and `DateTime` may be converted directly to `time.Time` like `time.Time(Time{})`. Similarly, you can convert `Duration` to `time.Duration` as in `time.Duration(Duration{})` ### Using pointers The `conv` subpackage provides helpers to convert the types to and from pointers, just like `go-openapi/swag` does with primitive types. ### Format types Types defined in `strfmt` expose marshaling and validation capabilities. List of defined types: - Base64 - CreditCard - Date - DateTime - Duration - Email - HexColor - Hostname - IPv4 - IPv6 - CIDR - ISBN - ISBN10 - ISBN13 - MAC - ObjectId - Password - RGBColor - SSN - URI - UUID - [UUID3](https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-3) - [UUID4](https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-4) - [UUID5](https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-5) - [UUID7](https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-7) - [ULID](https://github.com/ulid/spec) ### Database support 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. 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 > intercept type redefinitions like `strfmt.DateTime`. As a result, `DateTime.Value()` sends > an RFC 3339 string (e.g. `"2024-06-15T12:30:45.123Z"`) that MySQL/MariaDB rejects for > `DATETIME` columns. > > Workaround: set `strfmt.MarshalFormat` to a MySQL-compatible format such as > `strfmt.ISO8601LocalTime` and normalize to UTC before marshaling: > > ```go > strfmt.MarshalFormat = strfmt.ISO8601LocalTime > strfmt.NormalizeTimeForMarshal = func(t time.Time) time.Time { return t.UTC() } > ``` > > See [#174](https://github.com/go-openapi/strfmt/issues/174) for details. Integration tests for MongoDB, MariaDB, and PostgreSQL run in CI to verify database roundtrip compatibility for all format types. See [`internal/testintegration/`](internal/testintegration/). ## Change log See ## References ## Licensing This library ships under the [SPDX-License-Identifier: Apache-2.0](./LICENSE). ## Other documentation * [All-time contributors](./CONTRIBUTORS.md) * [Contributing guidelines][contributing-doc-site] * [Maintainers documentation][maintainers-doc-site] * [Code style][style-doc-site] ## Cutting a new release Maintainers can cut a new release by either: * running [this workflow](https://github.com/go-openapi/strfmt/actions/workflows/bump-release.yml) * or pushing a semver tag * signed tags are preferred * The tag message is prepended to release notes [test-badge]: https://github.com/go-openapi/strfmt/actions/workflows/go-test.yml/badge.svg [test-url]: https://github.com/go-openapi/strfmt/actions/workflows/go-test.yml [cov-badge]: https://codecov.io/gh/go-openapi/strfmt/branch/master/graph/badge.svg [cov-url]: https://codecov.io/gh/go-openapi/strfmt [vuln-scan-badge]: https://github.com/go-openapi/strfmt/actions/workflows/scanner.yml/badge.svg [vuln-scan-url]: https://github.com/go-openapi/strfmt/actions/workflows/scanner.yml [codeql-badge]: https://github.com/go-openapi/strfmt/actions/workflows/codeql.yml/badge.svg [codeql-url]: https://github.com/go-openapi/strfmt/actions/workflows/codeql.yml [release-badge]: https://badge.fury.io/gh/go-openapi%2Fstrfmt.svg [release-url]: https://badge.fury.io/gh/go-openapi%2Fstrfmt [gomod-badge]: https://badge.fury.io/go/github.com%2Fgo-openapi%2Fstrfmt.svg [gomod-url]: https://badge.fury.io/go/github.com%2Fgo-openapi%2Fstrfmt [gocard-badge]: https://goreportcard.com/badge/github.com/go-openapi/strfmt [gocard-url]: https://goreportcard.com/report/github.com/go-openapi/strfmt [codefactor-badge]: https://img.shields.io/codefactor/grade/github/go-openapi/strfmt [codefactor-url]: https://www.codefactor.io/repository/github/go-openapi/strfmt [doc-badge]: https://img.shields.io/badge/doc-site-blue?link=https%3A%2F%2Fgoswagger.io%2Fgo-openapi%2F [doc-url]: https://goswagger.io/go-openapi [godoc-badge]: https://pkg.go.dev/badge/github.com/go-openapi/strfmt [godoc-url]: http://pkg.go.dev/github.com/go-openapi/strfmt [discord-badge]: https://img.shields.io/discord/1446918742398341256?logo=discord&label=discord&color=blue [discord-url]: https://discord.gg/FfnFYaC3k5 [license-badge]: http://img.shields.io/badge/license-Apache%20v2-orange.svg [license-url]: https://github.com/go-openapi/strfmt/?tab=Apache-2.0-1-ov-file#readme [goversion-badge]: https://img.shields.io/github/go-mod/go-version/go-openapi/strfmt [goversion-url]: https://github.com/go-openapi/strfmt/blob/master/go.mod [top-badge]: https://img.shields.io/github/languages/top/go-openapi/strfmt [commits-badge]: https://img.shields.io/github/commits-since/go-openapi/strfmt/latest [contributing-doc-site]: https://go-openapi.github.io/doc-site/contributing/contributing/index.html [maintainers-doc-site]: https://go-openapi.github.io/doc-site/maintainers/index.html [style-doc-site]: https://go-openapi.github.io/doc-site/contributing/style/index.html go-openapi-strfmt-879971e/SECURITY.md000066400000000000000000000026031517436663600171770ustar00rootroot00000000000000# Security Policy This policy outlines the commitment and practices of the go-openapi maintainers regarding security. ## Supported Versions | Version | Supported | | ------- | ------------------ | | 0.x | :white_check_mark: | ## Vulnerability checks in place This repository uses automated vulnerability scans, at every merged commit and at least once a week. We use: * [`GitHub CodeQL`][codeql-url] * [`trivy`][trivy-url] * [`govulncheck`][govulncheck-url] Reports are centralized in github security reports and visible only to the maintainers. ## Reporting a vulnerability If you become aware of a security vulnerability that affects the current repository, **please report it privately to the maintainers** rather than opening a publicly visible GitHub issue. Please follow the instructions provided by github to [Privately report a security vulnerability][github-guidance-url]. > [!NOTE] > On Github, navigate to the project's "Security" tab then click on "Report a vulnerability". [codeql-url]: https://github.com/github/codeql [trivy-url]: https://trivy.dev/docs/latest/getting-started [govulncheck-url]: https://go.dev/blog/govulncheck [github-guidance-url]: https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability go-openapi-strfmt-879971e/bson.go000066400000000000000000000064451517436663600167060ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package strfmt import ( "database/sql/driver" "encoding/hex" "encoding/json" "fmt" ) func init() { //nolint:gochecknoinits // registers bsonobjectid format in the default registry var id ObjectId Default.Add("bsonobjectid", &id, IsBSONObjectID) } // IsBSONObjectID returns true when the string is a valid BSON [ObjectId]. func IsBSONObjectID(str string) bool { _, err := objectIDFromHex(str) return err == nil } // ObjectId represents a BSON object ID (a 12-byte unique identifier). // // 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 hexadecimal String. func NewObjectId(hex string) ObjectId { //nolint:revive oid, err := objectIDFromHex(hex) if err != nil { panic(err) } return oid } // MarshalText turns this instance into text. func (id ObjectId) MarshalText() ([]byte, error) { if id == nilObjectID { return nil, 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 = nilObjectID return nil } oid, err := objectIDFromHex(string(data)) if err != nil { return err } *id = oid return nil } // Scan read a value from a database driver. func (id *ObjectId) Scan(raw any) error { var data []byte switch v := raw.(type) { case []byte: data = v case string: data = []byte(v) default: return fmt.Errorf("cannot sql.Scan() strfmt.URI from: %#v: %w", v, ErrFormat) } return id.UnmarshalText(data) } // Value converts a value to a database driver value. func (id ObjectId) Value() (driver.Value, error) { 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 id.Hex() } // MarshalJSON returns the [ObjectId] as JSON. func (id ObjectId) MarshalJSON() ([]byte, error) { return json.Marshal(id.Hex()) } // UnmarshalJSON sets the [ObjectId] from JSON. func (id *ObjectId) UnmarshalJSON(data []byte) error { var hexStr string if err := json.Unmarshal(data, &hexStr); err != nil { return err } oid, err := objectIDFromHex(hexStr) if err != nil { return err } *id = oid return nil } // DeepCopyInto copies the receiver and writes its value into out. func (id *ObjectId) DeepCopyInto(out *ObjectId) { *out = *id } // DeepCopy copies the receiver into a new [ObjectId]. func (id *ObjectId) DeepCopy() *ObjectId { if id == nil { return nil } out := new(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 } go-openapi-strfmt-879971e/bson_test.go000066400000000000000000000024451517436663600177410ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package strfmt import ( "testing" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" ) func TestBSONObjectId_fullCycle(t *testing.T) { const str = "507f1f77bcf86cd799439011" id := NewObjectId("507f1f77bcf86cd799439011") bytes, err := id.MarshalText() require.NoError(t, err) require.TrueT(t, IsBSONObjectID(str)) var idCopy ObjectId err = idCopy.Scan(bytes) require.NoError(t, err) assert.EqualT(t, id, idCopy) err = idCopy.UnmarshalText(bytes) require.NoError(t, err) assert.EqualT(t, id, idCopy) require.EqualT(t, str, idCopy.String()) jsonBytes, err := id.MarshalJSON() require.NoError(t, err) err = idCopy.UnmarshalJSON(jsonBytes) require.NoError(t, err) assert.EqualT(t, id, idCopy) bsonBytes, err := id.MarshalBSON() require.NoError(t, err) err = idCopy.UnmarshalBSON(bsonBytes) require.NoError(t, err) assert.EqualT(t, id, idCopy) } func TestDeepCopyObjectId(t *testing.T) { id := NewObjectId("507f1f77bcf86cd799439011") in := &id out := new(ObjectId) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *ObjectId out3 := inNil.DeepCopy() assert.Nil(t, out3) } go-openapi-strfmt-879971e/conv/000077500000000000000000000000001517436663600163525ustar00rootroot00000000000000go-openapi-strfmt-879971e/conv/date.go000066400000000000000000000007651517436663600176260ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package conv import "github.com/go-openapi/strfmt" // Date returns a pointer to of the [strfmt.Date] value passed in. func Date(v strfmt.Date) *strfmt.Date { return &v } // DateValue returns the value of the [strfmt.Date] pointer passed in or // the default value if the pointer is nil. func DateValue(v *strfmt.Date) strfmt.Date { if v == nil { return strfmt.Date{} } return *v } go-openapi-strfmt-879971e/conv/date_test.go000066400000000000000000000006111517436663600206530ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package conv import ( "testing" "time" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/strfmt" ) func TestDateValue(t *testing.T) { assert.EqualT(t, strfmt.Date{}, DateValue(nil)) date := strfmt.Date(time.Now()) assert.EqualT(t, date, DateValue(&date)) } go-openapi-strfmt-879971e/conv/default.go000066400000000000000000000166351517436663600203400ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package conv import ( "github.com/go-openapi/strfmt" ) // Base64 returns a pointer to the [strfmt.Base64] value passed in. func Base64(v strfmt.Base64) *strfmt.Base64 { return &v } // Base64Value returns the value of the [strfmt.Base64] pointer passed in or // the default value if the pointer is nil. func Base64Value(v *strfmt.Base64) strfmt.Base64 { if v == nil { return nil } return *v } // URI returns a pointer to the [strfmt.URI] value passed in. func URI(v strfmt.URI) *strfmt.URI { return &v } // URIValue returns the value of the [strfmt.URI] pointer passed in or // the default value if the pointer is nil. func URIValue(v *strfmt.URI) strfmt.URI { if v == nil { return strfmt.URI("") } return *v } // Email returns a pointer to the [strfmt.Email] value passed in. func Email(v strfmt.Email) *strfmt.Email { return &v } // EmailValue returns the value of the [strfmt.Email] pointer passed in or // the default value if the pointer is nil. func EmailValue(v *strfmt.Email) strfmt.Email { if v == nil { return strfmt.Email("") } return *v } // Hostname returns a pointer to the [strfmt.Hostname] value passed in. func Hostname(v strfmt.Hostname) *strfmt.Hostname { return &v } // HostnameValue returns the value of the [strfmt.Hostname] pointer passed in or // the default value if the pointer is nil. func HostnameValue(v *strfmt.Hostname) strfmt.Hostname { if v == nil { return strfmt.Hostname("") } return *v } // IPv4 returns a pointer to the [strfmt.IPv4] value passed in. func IPv4(v strfmt.IPv4) *strfmt.IPv4 { return &v } // IPv4Value returns the value of the [strfmt.IPv4] pointer passed in or // the default value if the pointer is nil. func IPv4Value(v *strfmt.IPv4) strfmt.IPv4 { if v == nil { return strfmt.IPv4("") } return *v } // IPv6 returns a pointer to the [strfmt.IPv6] value passed in. func IPv6(v strfmt.IPv6) *strfmt.IPv6 { return &v } // IPv6Value returns the value of the [strfmt.IPv6] pointer passed in or // the default value if the pointer is nil. func IPv6Value(v *strfmt.IPv6) strfmt.IPv6 { if v == nil { return strfmt.IPv6("") } return *v } // CIDR returns a pointer to the [strfmt.CIDR] value passed in. func CIDR(v strfmt.CIDR) *strfmt.CIDR { return &v } // CIDRValue returns the value of the [strfmt.CIDR] pointer passed in or // the default value if the pointer is nil. func CIDRValue(v *strfmt.CIDR) strfmt.CIDR { if v == nil { return strfmt.CIDR("") } return *v } // MAC returns a pointer to the [strfmt.MAC] value passed in. func MAC(v strfmt.MAC) *strfmt.MAC { return &v } // MACValue returns the value of the [strfmt.MAC] pointer passed in or // the default value if the pointer is nil. func MACValue(v *strfmt.MAC) strfmt.MAC { if v == nil { return strfmt.MAC("") } return *v } // UUID returns a pointer to the [strfmt.UUID] value passed in. func UUID(v strfmt.UUID) *strfmt.UUID { return &v } // UUIDValue returns the value of the [strfmt.UUID] pointer passed in or // the default value if the pointer is nil. func UUIDValue(v *strfmt.UUID) strfmt.UUID { if v == nil { return strfmt.UUID("") } return *v } // UUID3 returns a pointer to the [strfmt.UUID3] value passed in. func UUID3(v strfmt.UUID3) *strfmt.UUID3 { return &v } // UUID3Value returns the value of the [strfmt.UUID3] pointer passed in or // the default value if the pointer is nil. func UUID3Value(v *strfmt.UUID3) strfmt.UUID3 { if v == nil { return strfmt.UUID3("") } return *v } // UUID4 returns a pointer to the [strfmt.UUID4] value passed in. func UUID4(v strfmt.UUID4) *strfmt.UUID4 { return &v } // UUID4Value returns the value of the [strfmt.UUID4] pointer passed in or // the default value if the pointer is nil. func UUID4Value(v *strfmt.UUID4) strfmt.UUID4 { if v == nil { return strfmt.UUID4("") } return *v } // UUID5 returns a pointer to the [strfmt.UUID5] value passed in. func UUID5(v strfmt.UUID5) *strfmt.UUID5 { return &v } // UUID5Value returns the value of the [strfmt.UUID5] pointer passed in or // the default value if the pointer is nil. func UUID5Value(v *strfmt.UUID5) strfmt.UUID5 { if v == nil { return strfmt.UUID5("") } return *v } // UUID7 returns a pointer to the [strfmt.UUID7] value passed in. func UUID7(v strfmt.UUID7) *strfmt.UUID7 { return &v } // UUID7Value returns the value of the [strfmt.UUID7] pointer passed in or // the default value if the pointer is nil. func UUID7Value(v *strfmt.UUID7) strfmt.UUID7 { if v == nil { return strfmt.UUID7("") } return *v } // ISBN returns a pointer to the [strfmt.ISBN] value passed in. func ISBN(v strfmt.ISBN) *strfmt.ISBN { return &v } // ISBNValue returns the value of the [strfmt.ISBN] pointer passed in or // the default value if the pointer is nil. func ISBNValue(v *strfmt.ISBN) strfmt.ISBN { if v == nil { return strfmt.ISBN("") } return *v } // ISBN10 returns a pointer to the [strfmt.ISBN10] value passed in. func ISBN10(v strfmt.ISBN10) *strfmt.ISBN10 { return &v } // ISBN10Value returns the value of the [strfmt.ISBN10] pointer passed in or // the default value if the pointer is nil. func ISBN10Value(v *strfmt.ISBN10) strfmt.ISBN10 { if v == nil { return strfmt.ISBN10("") } return *v } // ISBN13 returns a pointer to the [strfmt.ISBN13] value passed in. func ISBN13(v strfmt.ISBN13) *strfmt.ISBN13 { return &v } // ISBN13Value returns the value of the [strfmt.ISBN13] pointer passed in or // the default value if the pointer is nil. func ISBN13Value(v *strfmt.ISBN13) strfmt.ISBN13 { if v == nil { return strfmt.ISBN13("") } return *v } // CreditCard returns a pointer to the [strfmt.CreditCard] value passed in. func CreditCard(v strfmt.CreditCard) *strfmt.CreditCard { return &v } // CreditCardValue returns the value of the [strfmt.CreditCard] pointer passed in or // the default value if the pointer is nil. func CreditCardValue(v *strfmt.CreditCard) strfmt.CreditCard { if v == nil { return strfmt.CreditCard("") } return *v } // SSN returns a pointer to the [strfmt.SSN] value passed in. func SSN(v strfmt.SSN) *strfmt.SSN { return &v } // SSNValue returns the value of the [strfmt.SSN] pointer passed in or // the default value if the pointer is nil. func SSNValue(v *strfmt.SSN) strfmt.SSN { if v == nil { return strfmt.SSN("") } return *v } // HexColor returns a pointer to the [strfmt.HexColor] value passed in. func HexColor(v strfmt.HexColor) *strfmt.HexColor { return &v } // HexColorValue returns the value of the [strfmt.HexColor] pointer passed in or // the default value if the pointer is nil. func HexColorValue(v *strfmt.HexColor) strfmt.HexColor { if v == nil { return strfmt.HexColor("") } return *v } // RGBColor returns a pointer to the [strfmt.RGBColor] value passed in. func RGBColor(v strfmt.RGBColor) *strfmt.RGBColor { return &v } // RGBColorValue returns the value of the [strfmt.RGBColor] pointer passed in or // the default value if the pointer is nil. func RGBColorValue(v *strfmt.RGBColor) strfmt.RGBColor { if v == nil { return strfmt.RGBColor("") } return *v } // Password returns a pointer to the [strfmt.Password] value passed in. func Password(v strfmt.Password) *strfmt.Password { return &v } // PasswordValue returns the value of the [strfmt.Password] pointer passed in or // the default value if the pointer is nil. func PasswordValue(v *strfmt.Password) strfmt.Password { if v == nil { return strfmt.Password("") } return *v } go-openapi-strfmt-879971e/conv/default_test.go000066400000000000000000000073101517436663600213650ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package conv import ( "testing" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/strfmt" ) func TestBase64Value(t *testing.T) { assert.Equal(t, strfmt.Base64(nil), Base64Value(nil)) base64 := strfmt.Base64([]byte{4, 2}) assert.Equal(t, base64, Base64Value(&base64)) } func TestURIValue(t *testing.T) { assert.EqualT(t, strfmt.URI(""), URIValue(nil)) value := strfmt.URI("foo") assert.EqualT(t, value, URIValue(&value)) } func TestEmailValue(t *testing.T) { assert.EqualT(t, strfmt.Email(""), EmailValue(nil)) value := strfmt.Email("foo") assert.EqualT(t, value, EmailValue(&value)) } func TestHostnameValue(t *testing.T) { assert.EqualT(t, strfmt.Hostname(""), HostnameValue(nil)) value := strfmt.Hostname("foo") assert.EqualT(t, value, HostnameValue(&value)) } func TestIPv4Value(t *testing.T) { assert.EqualT(t, strfmt.IPv4(""), IPv4Value(nil)) value := strfmt.IPv4("foo") assert.EqualT(t, value, IPv4Value(&value)) } func TestIPv6Value(t *testing.T) { assert.EqualT(t, strfmt.IPv6(""), IPv6Value(nil)) value := strfmt.IPv6("foo") assert.EqualT(t, value, IPv6Value(&value)) } func TestCIDRValue(t *testing.T) { assert.EqualT(t, strfmt.CIDR(""), CIDRValue(nil)) value := strfmt.CIDR("foo") assert.EqualT(t, value, CIDRValue(&value)) } func TestMACValue(t *testing.T) { assert.EqualT(t, strfmt.MAC(""), MACValue(nil)) value := strfmt.MAC("foo") assert.EqualT(t, value, MACValue(&value)) } func TestUUIDValue(t *testing.T) { assert.EqualT(t, strfmt.UUID(""), UUIDValue(nil)) value := strfmt.UUID("foo") assert.EqualT(t, value, UUIDValue(&value)) } func TestUUID3Value(t *testing.T) { assert.EqualT(t, strfmt.UUID3(""), UUID3Value(nil)) value := strfmt.UUID3("foo") assert.EqualT(t, value, UUID3Value(&value)) } func TestUUID4Value(t *testing.T) { assert.EqualT(t, strfmt.UUID4(""), UUID4Value(nil)) value := strfmt.UUID4("foo") assert.EqualT(t, value, UUID4Value(&value)) } func TestUUID5Value(t *testing.T) { assert.EqualT(t, strfmt.UUID5(""), UUID5Value(nil)) value := strfmt.UUID5("foo") assert.EqualT(t, value, UUID5Value(&value)) } func TestUUID7Value(t *testing.T) { assert.EqualT(t, strfmt.UUID7(""), UUID7Value(nil)) value := strfmt.UUID7("foo") assert.EqualT(t, value, UUID7Value(&value)) } func TestISBNValue(t *testing.T) { assert.EqualT(t, strfmt.ISBN(""), ISBNValue(nil)) value := strfmt.ISBN("foo") assert.EqualT(t, value, ISBNValue(&value)) } func TestISBN10Value(t *testing.T) { assert.EqualT(t, strfmt.ISBN10(""), ISBN10Value(nil)) value := strfmt.ISBN10("foo") assert.EqualT(t, value, ISBN10Value(&value)) } func TestISBN13Value(t *testing.T) { assert.EqualT(t, strfmt.ISBN13(""), ISBN13Value(nil)) value := strfmt.ISBN13("foo") assert.EqualT(t, value, ISBN13Value(&value)) } func TestCreditCardValue(t *testing.T) { assert.EqualT(t, strfmt.CreditCard(""), CreditCardValue(nil)) value := strfmt.CreditCard("foo") assert.EqualT(t, value, CreditCardValue(&value)) } func TestSSNValue(t *testing.T) { assert.EqualT(t, strfmt.SSN(""), SSNValue(nil)) value := strfmt.SSN("foo") assert.EqualT(t, value, SSNValue(&value)) } func TestHexColorValue(t *testing.T) { assert.EqualT(t, strfmt.HexColor(""), HexColorValue(nil)) value := strfmt.HexColor("foo") assert.EqualT(t, value, HexColorValue(&value)) } func TestRGBColorValue(t *testing.T) { assert.EqualT(t, strfmt.RGBColor(""), RGBColorValue(nil)) value := strfmt.RGBColor("foo") assert.EqualT(t, value, RGBColorValue(&value)) } func TestPasswordValue(t *testing.T) { assert.EqualT(t, strfmt.Password(""), PasswordValue(nil)) value := strfmt.Password("foo") assert.EqualT(t, value, PasswordValue(&value)) } go-openapi-strfmt-879971e/conv/duration.go000066400000000000000000000010421517436663600205230ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package conv import "github.com/go-openapi/strfmt" // Duration returns a pointer to of the [strfmt.Duration] value passed in. func Duration(v strfmt.Duration) *strfmt.Duration { return &v } // DurationValue returns the value of the [strfmt.Duration] pointer passed in or // the default value if the pointer is nil. func DurationValue(v *strfmt.Duration) strfmt.Duration { if v == nil { return strfmt.Duration(0) } return *v } go-openapi-strfmt-879971e/conv/duration_test.go000066400000000000000000000006321517436663600215660ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package conv import ( "testing" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/strfmt" ) func TestDurationValue(t *testing.T) { assert.EqualT(t, strfmt.Duration(0), DurationValue(nil)) duration := strfmt.Duration(42) assert.EqualT(t, duration, DurationValue(&duration)) } go-openapi-strfmt-879971e/conv/time.go000066400000000000000000000010411517436663600176330ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package conv import "github.com/go-openapi/strfmt" // DateTime returns a pointer to of the [strfmt.DateTime] value passed in. func DateTime(v strfmt.DateTime) *strfmt.DateTime { return &v } // DateTimeValue returns the value of the [strfmt.DateTime] pointer passed in or // the default value if the pointer is nil. func DateTimeValue(v *strfmt.DateTime) strfmt.DateTime { if v == nil { return strfmt.DateTime{} } return *v } go-openapi-strfmt-879971e/conv/time_test.go000066400000000000000000000006351517436663600207020ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package conv import ( "testing" "time" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/strfmt" ) func TestDateTimeValue(t *testing.T) { assert.EqualT(t, strfmt.DateTime{}, DateTimeValue(nil)) time := strfmt.DateTime(time.Now()) assert.EqualT(t, time, DateTimeValue(&time)) } go-openapi-strfmt-879971e/conv/ulid.go000066400000000000000000000007651517436663600176460ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package conv import "github.com/go-openapi/strfmt" // ULID returns a pointer to of the [strfmt.ULID] value passed in. func ULID(v strfmt.ULID) *strfmt.ULID { return &v } // ULIDValue returns the value of the [strfmt.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 } go-openapi-strfmt-879971e/conv/ulid_test.go000066400000000000000000000011371517436663600206770ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package conv import ( "testing" "github.com/go-openapi/strfmt" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" ) const testUlid = string("01EYXZVGBHG26MFTG4JWR4K558") func TestULIDValue(t *testing.T) { assert.EqualT(t, strfmt.ULID{}, ULIDValue(nil)) value := strfmt.ULID{} err := value.UnmarshalText([]byte(testUlid)) require.NoError(t, err) assert.EqualT(t, value, ULIDValue(&value)) ulidRef := ULID(value) assert.Equal(t, &value, ulidRef) } go-openapi-strfmt-879971e/date.go000066400000000000000000000066041517436663600166570ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package strfmt import ( "database/sql/driver" "encoding/json" "fmt" "time" ) func init() { //nolint:gochecknoinits // registers date format in the default registry d := Date{} Default.Add("date", &d, IsDate) } // IsDate returns true when the string is a valid date. func IsDate(str string) bool { _, err := time.Parse(RFC3339FullDate, str) return err == nil } const ( // RFC3339FullDate represents a full-date as specified by RFC3339. // See: http://goo.gl/xXOvVd RFC3339FullDate = "2006-01-02" ) // Date represents a date from the API. // // swagger:strfmt date. type Date time.Time // String converts this date into a string. func (d Date) String() string { return time.Time(d).Format(RFC3339FullDate) } // UnmarshalText parses a text representation into a date type. func (d *Date) UnmarshalText(text []byte) error { if len(text) == 0 { return nil } dd, err := time.ParseInLocation(RFC3339FullDate, string(text), DefaultTimeLocation) if err != nil { return err } *d = Date(dd) return nil } // MarshalText serializes this date type to string. func (d Date) MarshalText() ([]byte, error) { return []byte(d.String()), nil } // Scan scans a Date value from database driver type. func (d *Date) Scan(raw any) error { switch v := raw.(type) { case []byte: return d.UnmarshalText(v) case string: return d.UnmarshalText([]byte(v)) case time.Time: *d = Date(v) return nil case nil: *d = Date{} return nil default: return fmt.Errorf("cannot sql.Scan() strfmt.Date from: %#v: %w", v, ErrFormat) } } // Value converts Date to a primitive value ready to written to a database. func (d Date) Value() (driver.Value, error) { return driver.Value(d.String()), nil } // MarshalJSON returns the Date as JSON. func (d Date) MarshalJSON() ([]byte, error) { return json.Marshal(time.Time(d).Format(RFC3339FullDate)) } // UnmarshalJSON sets the Date from JSON. func (d *Date) UnmarshalJSON(data []byte) error { if string(data) == jsonNull { return nil } var strdate string if err := json.Unmarshal(data, &strdate); err != nil { return err } tt, err := time.ParseInLocation(RFC3339FullDate, strdate, DefaultTimeLocation) if err != nil { return err } *d = Date(tt) return nil } // DeepCopyInto copies the receiver and writes its value into out. func (d *Date) DeepCopyInto(out *Date) { *out = *d } // DeepCopy copies the receiver into a new Date. func (d *Date) DeepCopy() *Date { if d == nil { return nil } out := new(Date) d.DeepCopyInto(out) return out } // GobEncode implements the gob.GobEncoder interface. func (d Date) GobEncode() ([]byte, error) { return d.MarshalBinary() } // GobDecode implements the gob.GobDecoder interface. func (d *Date) GobDecode(data []byte) error { return d.UnmarshalBinary(data) } // MarshalBinary implements the encoding.[encoding.BinaryMarshaler] interface. func (d Date) MarshalBinary() ([]byte, error) { return time.Time(d).MarshalBinary() } // UnmarshalBinary implements the encoding.[encoding.BinaryUnmarshaler] interface. func (d *Date) UnmarshalBinary(data []byte) error { var original time.Time err := original.UnmarshalBinary(data) if err != nil { return err } *d = Date(original) return nil } // Equal checks if two Date instances are equal. func (d Date) Equal(d2 Date) bool { return time.Time(d).Equal(time.Time(d2)) } go-openapi-strfmt-879971e/date_test.go000066400000000000000000000073311517436663600177140ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package strfmt import ( "bytes" "database/sql" "database/sql/driver" "encoding/gob" "testing" "time" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" ) var ( _ sql.Scanner = &Date{} _ driver.Valuer = Date{} ) func TestDate(t *testing.T) { pp := Date{} err := pp.UnmarshalText([]byte{}) require.NoError(t, err) err = pp.UnmarshalText([]byte("yada")) require.Error(t, err) orig := "2014-12-15" bj := []byte("\"" + orig + "\"") err = pp.UnmarshalText([]byte(orig)) require.NoError(t, err) txt, err := pp.MarshalText() require.NoError(t, err) assert.EqualT(t, orig, string(txt)) err = pp.UnmarshalJSON(bj) require.NoError(t, err) assert.EqualT(t, orig, pp.String()) err = pp.UnmarshalJSON([]byte(`"1972/01/01"`)) require.Error(t, err) assert.JSONMarshalAsT(t, string(bj), pp) var dateZero Date err = dateZero.UnmarshalJSON([]byte(jsonNull)) require.NoError(t, err) assert.EqualT(t, Date{}, dateZero) } func TestDate_Scan(t *testing.T) { ref := time.Now().Truncate(24 * time.Hour).UTC() date, str := Date(ref), ref.Format(RFC3339FullDate) values := []any{str, []byte(str), ref} for _, value := range values { result := Date{} _ = (&result).Scan(value) assert.EqualT(t, date, result, "value: %#v", value) } dd := Date{} err := dd.Scan(nil) require.NoError(t, err) assert.EqualT(t, Date{}, dd) err = dd.Scan(19700101) require.Error(t, err) } func TestDate_Value(t *testing.T) { ref := time.Now().Truncate(24 * time.Hour).UTC() date := Date(ref) dbv, err := date.Value() require.NoError(t, err) assert.EqualValues(t, dbv, ref.Format("2006-01-02")) } func TestDate_IsDate(t *testing.T) { tests := []struct { value string valid bool }{ {"2017-12-22", true}, {"2017-1-1", false}, {"17-13-22", false}, {"2017-02-29", false}, // not a valid date : 2017 is not a leap year {"1900-02-29", false}, // not a valid date : 1900 is not a leap year {"2100-02-29", false}, // not a valid date : 2100 is not a leap year {"2000-02-29", true}, // a valid date : 2000 is a leap year {"2400-02-29", true}, // a valid date : 2000 is a leap year {"2017-13-22", false}, {"2017-12-32", false}, {"20171-12-32", false}, {"YYYY-MM-DD", false}, {"20-17-2017", false}, {"2017-12-22T01:02:03Z", false}, } for _, test := range tests { assert.EqualT(t, test.valid, IsDate(test.value), "value [%s] should be valid: [%t]", test.value, test.valid) } } func TestDeepCopyDate(t *testing.T) { ref := time.Now().Truncate(24 * time.Hour).UTC() date := Date(ref) in := &date out := new(Date) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *Date out3 := inNil.DeepCopy() assert.Nil(t, out3) } func TestGobEncodingDate(t *testing.T) { now := time.Now() b := bytes.Buffer{} enc := gob.NewEncoder(&b) err := enc.Encode(Date(now)) require.NoError(t, err) assert.NotEmpty(t, b.Bytes()) var result Date dec := gob.NewDecoder(&b) err = dec.Decode(&result) require.NoError(t, err) assert.EqualT(t, now.Year(), time.Time(result).Year()) assert.EqualT(t, now.Month(), time.Time(result).Month()) assert.EqualT(t, now.Day(), time.Time(result).Day()) } func TestDate_Equal(t *testing.T) { t.Parallel() d1 := Date(time.Date(2020, 10, 11, 12, 13, 14, 15, time.UTC)) d2 := Date(time.Date(2020, 10, 11, 12, 13, 14, 15, time.UTC)) d3 := Date(time.Date(2020, 11, 12, 13, 14, 15, 16, time.UTC)) //nolint:gocritic assert.TrueT(t, d1.Equal(d1), "Same Date should Equal itself") assert.TrueT(t, d1.Equal(d2), "Date instances should be equal") assert.FalseT(t, d1.Equal(d3), "Date instances should not be equal") } go-openapi-strfmt-879971e/default.go000066400000000000000000001407361517436663600173730ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package strfmt import ( "database/sql/driver" "encoding/base64" "encoding/json" "fmt" "net" "net/mail" "net/netip" "net/url" "regexp" "strconv" "strings" "github.com/google/uuid" "golang.org/x/net/idna" ) const ( // HostnamePattern http://[json]-schema.org/latest/[json]-schema-validation.html#anchor114. // // Deprecated: this package no longer uses regular expressions to validate hostnames. HostnamePattern = `^([a-zA-Z0-9\p{S}\p{L}]((-?[a-zA-Z0-9\p{S}\p{L}]{0,62})?)` + `|([a-zA-Z0-9\p{S}\p{L}](([a-zA-Z0-9-\p{S}\p{L}]{0,61}[a-zA-Z0-9\p{S}\p{L}])?)(\.)){1,}([a-zA-Z0-9-\p{L}]){2,63})$` // json null type. jsonNull = "null" ) const ( // UUIDPattern Regex for [UUID] that allows uppercase // // Deprecated: [strfmt] no longer uses regular expressions to validate UUIDs. UUIDPattern = `(?i)(^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$)|(^[0-9a-f]{32}$)` // UUID3Pattern Regex for [UUID3] that allows uppercase // // Deprecated: [strfmt] no longer uses regular expressions to validate UUIDs. UUID3Pattern = `(?i)(^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$)|(^[0-9a-f]{12}3[0-9a-f]{3}?[0-9a-f]{16}$)` // UUID4Pattern Regex for [UUID4] that allows uppercase // // Deprecated: [strfmt] no longer uses regular expressions to validate UUIDs. UUID4Pattern = `(?i)(^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$)|(^[0-9a-f]{12}4[0-9a-f]{3}[89ab][0-9a-f]{15}$)` // UUID5Pattern Regex for [UUID]5 that allows uppercase // // Deprecated: [strfmt] no longer uses regular expressions to validate UUIDs. UUID5Pattern = `(?i)(^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$)|(^[0-9a-f]{12}5[0-9a-f]{3}[89ab][0-9a-f]{15}$)` isbn10Pattern string = "^(?:[0-9]{9}X|[0-9]{10})$" isbn13Pattern string = "^(?:[0-9]{13})$" usCardPattern string = "^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}" + "|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}" + "|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}" + "|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11}|6[27][0-9]{14})$" ssnPattern string = `^\d{3}[- ]?\d{2}[- ]?\d{4}$` hexColorPattern string = "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$" rgbColorPattern string = "^rgb\\(\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*," + "\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*," + "\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*\\)$" ) const ( isbnVersion10 = 10 isbnVersion13 = 13 decimalBase = 10 ) //nolint:gochecknoglobals // package-level compiled patterns and validators var ( idnaHostChecker = idna.New( idna.ValidateForRegistration(), // shorthand for [idna.StrictDomainName], [idna.ValidateLabels], [idna.VerifyDNSLength], [idna.BidiRule] ) whiteSpacesAndMinus = regexp.MustCompile(`[\s-]+`) rxISBN10 = regexp.MustCompile(isbn10Pattern) rxISBN13 = regexp.MustCompile(isbn13Pattern) rxCreditCard = regexp.MustCompile(usCardPattern) rxSSN = regexp.MustCompile(ssnPattern) rxHexcolor = regexp.MustCompile(hexColorPattern) rxRGBcolor = regexp.MustCompile(rgbColorPattern) ) // IsHostname returns true when the string is a valid hostname. // // It follows the rules detailed at https://url.spec.whatwg.org/#concept-host-parser // and implemented by most modern web browsers. // // It supports IDNA rules regarding internationalized names with unicode. // // Besides: // // - the empty string is not a valid host name // - a trailing dot is allowed in names and [IPv4]'s (not [IPv6]) // - a host name can be a valid [IPv4] (with decimal, octal or hexadecimal numbers) or [IPv6] address // - [IPv6] zones are disallowed // - top-level domains can be unicode (cf. https://www.iana.org/domains/root/db). // // NOTE: this validator doesn't check top-level domains against the IANA root database. // It merely ensures that a top-level domain in a FQDN is at least 2 code points long. func IsHostname(str string) bool { if len(str) == 0 { return false } // IP v6 check if ipv6Cleaned, found := strings.CutPrefix(str, "["); found { ipv6Cleaned, found = strings.CutSuffix(ipv6Cleaned, "]") if !found { return false } return isValidIPv6(ipv6Cleaned) } // IDNA check res, err := idnaHostChecker.ToASCII(strings.ToLower(str)) if err != nil || res == "" { return false } parts := strings.Split(res, ".") // IP v4 check lastPart, lastIndex, shouldBeIPv4 := domainEndsAsNumber(parts) if shouldBeIPv4 { // domain ends in a number: must be an IPv4 return isValidIPv4(parts[:lastIndex+1]) // if the last part is a trailing dot, remove it } // check TLD length (excluding trailing dot) const minTLDLength = 2 if lastIndex > 0 && len(lastPart) < minTLDLength { return false } return true } // domainEndsAsNumber determines if a domain name ends with a decimal, octal or hex digit, // accounting for a possible trailing dot (the last part being empty in that case). // // It returns the last non-trailing dot part and if that part consists only of (dec/hex/oct) digits. func domainEndsAsNumber(parts []string) (lastPart string, lastIndex int, ok bool) { // NOTE: using ParseUint(x, 0, 32) is not an option, as the IPv4 format supported why WHATWG // doesn't support notations such as "0b1001" (binary digits) or "0o666" (alternate notation for octal digits). lastIndex = len(parts) - 1 lastPart = parts[lastIndex] if len(lastPart) == 0 { // trailing dot if len(parts) == 1 { // dot-only string: normally already ruled out by the IDNA check above return lastPart, lastIndex, false } lastIndex-- lastPart = parts[lastIndex] } if startOfHexDigit(lastPart) { for _, b := range []byte(lastPart[2:]) { if !isHexDigit(b) { return lastPart, lastIndex, false } } return lastPart, lastIndex, true } // check for decimal and octal for _, b := range []byte(lastPart) { if !isASCIIDigit(b) { return lastPart, lastIndex, false } } return lastPart, lastIndex, true } func startOfHexDigit(str string) bool { return strings.HasPrefix(str, "0x") // the input has already been lower-cased } func startOfOctalDigit(str string) bool { if str == "0" { // a single "0" is considered decimal return false } return strings.HasPrefix(str, "0") } func isValidIPv6(str string) bool { // disallow empty ipv6 address if len(str) == 0 { return false } addr, err := netip.ParseAddr(str) if err != nil { return false } if !addr.Is6() { return false } // explicit desupport of IPv6 zones if addr.Zone() != "" { return false } return true } // isValidIPv4 parses an IPv4 with deciaml, hex or octal digit parts. // // We can't rely on [netip.ParseAddr] because we may get a mix of decimal, octal and hex digits. // // Examples of valid addresses not supported by [netip.ParseAddr] or [net.ParseIP]: // // "192.0x00A80001" // "0300.0250.0340.001" // "1.0x.1.1" // // But not: // // "0b1010.2.3.4" // "0o07.2.3.4" func isValidIPv4(parts []string) bool { // NOTE: using ParseUint(x, 0, 32) is not an option, even though it would simplify this code a lot. // The IPv4 format supported why WHATWG doesn't support notations such as "0b1001" (binary digits) // or "0o666" (alternate notation for octal digits). const ( maxPartsInIPv4 = 4 maxDigitsInPart = 11 // max size of a 4-bytes hex or octal digit ) if len(parts) == 0 || len(parts) > maxPartsInIPv4 { return false } // we call this when we know that the last part is a digit part, so len(lastPart)>0 digits := make([]uint64, 0, maxPartsInIPv4) for _, part := range parts { if len(part) == 0 { // empty part: this case has normally been already ruled out by the IDNA check above return false } if len(part) > maxDigitsInPart { // whether decimal, octal or hex, an address can't exceed that length return false } if !isASCIIDigit(part[0]) { // start of an IPv4 part is always a digit return false } switch { case startOfHexDigit(part): const hexDigitOffset = 2 hexString := part[hexDigitOffset:] if len(hexString) == 0 { // 0x part: assume 0 digits = append(digits, 0) continue } hexDigit, err := strconv.ParseUint(hexString, 16, 32) if err != nil { return false } digits = append(digits, hexDigit) continue case startOfOctalDigit(part): const octDigitOffset = 1 octString := part[octDigitOffset:] // we know that this is not empty octDigit, err := strconv.ParseUint(octString, 8, 32) if err != nil { return false } digits = append(digits, octDigit) default: // assume decimal digits (0-255) // we know that we don't have a leading 0 (would have been caught by octal digit) decDigit, err := strconv.ParseUint(part, 10, 8) if err != nil { return false } digits = append(digits, decDigit) } } // now check the digits: the last digit may encompass several parts of the address lastDigit := digits[len(digits)-1] if lastDigit > uint64(1)< 1 { const maxUint8 = uint64(^uint8(0)) for i := range len(digits) - 2 { if digits[i] > maxUint8 { return false } } } return true } func isHexDigit(c byte) bool { switch { case '0' <= c && c <= '9': return true case 'a' <= c && c <= 'f': // assume the input string to be lower case return true } return false } func isASCIIDigit(c byte) bool { return c >= '0' && c <= '9' } // IsUUID returns true if the string matches a [UUID] (in any version, including v6 and v7), upper case is allowed. func IsUUID(str string) bool { _, err := uuid.Parse(str) return err == nil } const ( uuidV3 = 3 uuidV4 = 4 uuidV5 = 5 uuidV7 = 7 ) // IsUUID3 returns true if the string matches a [UUID] v3, upper case is allowed. func IsUUID3(str string) bool { id, err := uuid.Parse(str) return err == nil && id.Version() == uuid.Version(uuidV3) } // IsUUID4 returns true is the string matches a [UUID] v4, upper case is allowed. func IsUUID4(str string) bool { id, err := uuid.Parse(str) return err == nil && id.Version() == uuid.Version(uuidV4) } // IsUUID5 returns true if the string matches a [UUID] v5, upper case is allowed. func IsUUID5(str string) bool { id, err := uuid.Parse(str) return err == nil && id.Version() == uuid.Version(uuidV5) } // IsUUID7 returns true if the string matches a [UUID] v7, upper case is allowed. func IsUUID7(str string) bool { id, err := uuid.Parse(str) return err == nil && id.Version() == uuid.Version(uuidV7) } // IsEmail validates an email address. func IsEmail(str string) bool { addr, e := mail.ParseAddress(str) return e == nil && addr.Address != "" } func init() { //nolint:gochecknoinits // registers all default string formats in the registry // register formats in the default registry: // - byte // - creditcard // - email // - hexcolor // - hostname // - ipv4 // - ipv6 // - cidr // - isbn // - isbn10 // - isbn13 // - mac // - password // - rgbcolor // - ssn // - uri // - uuid // - uuid3 // - uuid4 // - uuid5 // - uuid7 u := URI("") Default.Add("uri", &u, isRequestURI) eml := Email("") Default.Add("email", &eml, IsEmail) hn := Hostname("") Default.Add("hostname", &hn, IsHostname) ip4 := IPv4("") Default.Add("ipv4", &ip4, isIPv4) ip6 := IPv6("") Default.Add("ipv6", &ip6, isIPv6) cidr := CIDR("") Default.Add("cidr", &cidr, isCIDR) mac := MAC("") Default.Add("mac", &mac, isMAC) uid := UUID("") Default.Add("uuid", &uid, IsUUID) uid3 := UUID3("") Default.Add("uuid3", &uid3, IsUUID3) uid4 := UUID4("") Default.Add("uuid4", &uid4, IsUUID4) uid5 := UUID5("") Default.Add("uuid5", &uid5, IsUUID5) uid7 := UUID7("") Default.Add("uuid7", &uid7, IsUUID7) isbn := ISBN("") Default.Add("isbn", &isbn, func(str string) bool { return isISBN10(str) || isISBN13(str) }) isbn10 := ISBN10("") Default.Add("isbn10", &isbn10, isISBN10) isbn13 := ISBN13("") Default.Add("isbn13", &isbn13, isISBN13) cc := CreditCard("") Default.Add("creditcard", &cc, isCreditCard) ssn := SSN("") Default.Add("ssn", &ssn, isSSN) hc := HexColor("") Default.Add("hexcolor", &hc, isHexcolor) rc := RGBColor("") Default.Add("rgbcolor", &rc, isRGBcolor) b64 := Base64([]byte(nil)) Default.Add("byte", &b64, isBase64) pw := Password("") Default.Add("password", &pw, func(_ string) bool { return true }) } // Base64 represents a base64 encoded string, using URLEncoding alphabet. // // swagger:strfmt byte. type Base64 []byte // MarshalText turns this instance into text. func (b Base64) MarshalText() ([]byte, error) { enc := base64.URLEncoding src := []byte(b) buf := make([]byte, enc.EncodedLen(len(src))) enc.Encode(buf, src) return buf, nil } // UnmarshalText hydrates this instance from text. func (b *Base64) UnmarshalText(data []byte) error { // validation is performed later on enc := base64.URLEncoding dbuf := make([]byte, enc.DecodedLen(len(data))) n, err := enc.Decode(dbuf, data) if err != nil { return err } *b = dbuf[:n] return nil } // Scan read a value from a database driver. func (b *Base64) Scan(raw any) error { switch v := raw.(type) { case []byte: dbuf := make([]byte, base64.StdEncoding.DecodedLen(len(v))) n, err := base64.StdEncoding.Decode(dbuf, v) if err != nil { return err } *b = dbuf[:n] case string: vv, err := base64.StdEncoding.DecodeString(v) if err != nil { return err } *b = Base64(vv) default: return fmt.Errorf("cannot sql.Scan() strfmt.Base64 from: %#v: %w", v, ErrFormat) } return nil } // Value converts a value to a database driver value. func (b Base64) Value() (driver.Value, error) { return driver.Value(b.String()), nil } func (b Base64) String() string { return base64.StdEncoding.EncodeToString([]byte(b)) } // MarshalJSON returns the Base64 as JSON. func (b Base64) MarshalJSON() ([]byte, error) { return json.Marshal(b.String()) } // UnmarshalJSON sets the Base64 from JSON. func (b *Base64) UnmarshalJSON(data []byte) error { var b64str string if err := json.Unmarshal(data, &b64str); err != nil { return err } vb, err := base64.StdEncoding.DecodeString(b64str) if err != nil { return err } *b = Base64(vb) return nil } // DeepCopyInto copies the receiver and writes its value into out. func (b *Base64) DeepCopyInto(out *Base64) { *out = *b } // DeepCopy copies the receiver into a new Base64. func (b *Base64) DeepCopy() *Base64 { if b == nil { return nil } out := new(Base64) b.DeepCopyInto(out) return out } // URI represents the uri string format as specified by the [json] schema spec. // // swagger:strfmt uri. type URI string // MarshalText turns this instance into text. func (u URI) MarshalText() ([]byte, error) { return []byte(string(u)), nil } // UnmarshalText hydrates this instance from text. func (u *URI) UnmarshalText(data []byte) error { // validation is performed later on *u = URI(string(data)) return nil } // Scan read a value from a database driver. func (u *URI) Scan(raw any) error { switch v := raw.(type) { case []byte: *u = URI(string(v)) case string: *u = URI(v) default: return fmt.Errorf("cannot sql.Scan() strfmt.URI from: %#v: %w", v, ErrFormat) } return nil } // Value converts a value to a database driver value. func (u URI) Value() (driver.Value, error) { return driver.Value(string(u)), nil } func (u URI) String() string { return string(u) } // MarshalJSON returns the [URI] as JSON. func (u URI) MarshalJSON() ([]byte, error) { return json.Marshal(string(u)) } // UnmarshalJSON sets the [URI] from JSON. func (u *URI) UnmarshalJSON(data []byte) error { var uristr string if err := json.Unmarshal(data, &uristr); err != nil { return err } *u = URI(uristr) return nil } // DeepCopyInto copies the receiver and writes its value into out. func (u *URI) DeepCopyInto(out *URI) { *out = *u } // DeepCopy copies the receiver into a new [URI]. func (u *URI) DeepCopy() *URI { if u == nil { return nil } out := new(URI) u.DeepCopyInto(out) return out } // Email represents the email string format as specified by the [json] schema spec. // // swagger:strfmt email. type Email string // MarshalText turns this instance into text. func (e Email) MarshalText() ([]byte, error) { return []byte(string(e)), nil } // UnmarshalText hydrates this instance from text. func (e *Email) UnmarshalText(data []byte) error { // validation is performed later on *e = Email(string(data)) return nil } // Scan read a value from a database driver. func (e *Email) Scan(raw any) error { switch v := raw.(type) { case []byte: *e = Email(string(v)) case string: *e = Email(v) default: return fmt.Errorf("cannot sql.Scan() strfmt.Email from: %#v: %w", v, ErrFormat) } return nil } // Value converts a value to a database driver value. func (e Email) Value() (driver.Value, error) { return driver.Value(string(e)), nil } func (e Email) String() string { return string(e) } // MarshalJSON returns the Email as JSON. func (e Email) MarshalJSON() ([]byte, error) { return json.Marshal(string(e)) } // UnmarshalJSON sets the Email from JSON. func (e *Email) UnmarshalJSON(data []byte) error { var estr string if err := json.Unmarshal(data, &estr); err != nil { return err } *e = Email(estr) return nil } // DeepCopyInto copies the receiver and writes its value into out. func (e *Email) DeepCopyInto(out *Email) { *out = *e } // DeepCopy copies the receiver into a new Email. func (e *Email) DeepCopy() *Email { if e == nil { return nil } out := new(Email) e.DeepCopyInto(out) return out } // Hostname represents the hostname string format as specified by the [json] schema spec. // // swagger:strfmt hostname. type Hostname string // MarshalText turns this instance into text. func (h Hostname) MarshalText() ([]byte, error) { return []byte(string(h)), nil } // UnmarshalText hydrates this instance from text. func (h *Hostname) UnmarshalText(data []byte) error { // validation is performed later on *h = Hostname(string(data)) return nil } // Scan read a value from a database driver. func (h *Hostname) Scan(raw any) error { switch v := raw.(type) { case []byte: *h = Hostname(string(v)) case string: *h = Hostname(v) default: return fmt.Errorf("cannot sql.Scan() strfmt.Hostname from: %#v: %w", v, ErrFormat) } return nil } // Value converts a value to a database driver value. func (h Hostname) Value() (driver.Value, error) { return driver.Value(string(h)), nil } func (h Hostname) String() string { return string(h) } // MarshalJSON returns the [Hostname] as JSON. func (h Hostname) MarshalJSON() ([]byte, error) { return json.Marshal(string(h)) } // UnmarshalJSON sets the [Hostname] from JSON. func (h *Hostname) UnmarshalJSON(data []byte) error { var hstr string if err := json.Unmarshal(data, &hstr); err != nil { return err } *h = Hostname(hstr) return nil } // DeepCopyInto copies the receiver and writes its value into out. func (h *Hostname) DeepCopyInto(out *Hostname) { *out = *h } // DeepCopy copies the receiver into a new [Hostname]. func (h *Hostname) DeepCopy() *Hostname { if h == nil { return nil } out := new(Hostname) h.DeepCopyInto(out) return out } // IPv4 represents an IP v4 address. // // swagger:strfmt ipv4. type IPv4 string // MarshalText turns this instance into text. func (u IPv4) MarshalText() ([]byte, error) { return []byte(string(u)), nil } // UnmarshalText hydrates this instance from text. func (u *IPv4) UnmarshalText(data []byte) error { // validation is performed later on *u = IPv4(string(data)) return nil } // Scan read a value from a database driver. func (u *IPv4) Scan(raw any) error { switch v := raw.(type) { case []byte: *u = IPv4(string(v)) case string: *u = IPv4(v) default: return fmt.Errorf("cannot sql.Scan() strfmt.IPv4 from: %#v: %w", v, ErrFormat) } return nil } // Value converts a value to a database driver value. func (u IPv4) Value() (driver.Value, error) { return driver.Value(string(u)), nil } func (u IPv4) String() string { return string(u) } // MarshalJSON returns the [IPv4] as JSON. func (u IPv4) MarshalJSON() ([]byte, error) { return json.Marshal(string(u)) } // UnmarshalJSON sets the [IPv4] from JSON. func (u *IPv4) UnmarshalJSON(data []byte) error { var ustr string if err := json.Unmarshal(data, &ustr); err != nil { return err } *u = IPv4(ustr) return nil } // DeepCopyInto copies the receiver and writes its value into out. func (u *IPv4) DeepCopyInto(out *IPv4) { *out = *u } // DeepCopy copies the receiver into a new [IPv4]. func (u *IPv4) DeepCopy() *IPv4 { if u == nil { return nil } out := new(IPv4) u.DeepCopyInto(out) return out } // IPv6 represents an IP v6 address. // // swagger:strfmt ipv6. type IPv6 string // MarshalText turns this instance into text. func (u IPv6) MarshalText() ([]byte, error) { return []byte(string(u)), nil } // UnmarshalText hydrates this instance from text. func (u *IPv6) UnmarshalText(data []byte) error { // validation is performed later on *u = IPv6(string(data)) return nil } // Scan read a value from a database driver. func (u *IPv6) Scan(raw any) error { switch v := raw.(type) { case []byte: *u = IPv6(string(v)) case string: *u = IPv6(v) default: return fmt.Errorf("cannot sql.Scan() strfmt.IPv6 from: %#v: %w", v, ErrFormat) } return nil } // Value converts a value to a database driver value. func (u IPv6) Value() (driver.Value, error) { return driver.Value(string(u)), nil } func (u IPv6) String() string { return string(u) } // MarshalJSON returns the [IPv6] as JSON. func (u IPv6) MarshalJSON() ([]byte, error) { return json.Marshal(string(u)) } // UnmarshalJSON sets the [IPv6] from JSON. func (u *IPv6) UnmarshalJSON(data []byte) error { var ustr string if err := json.Unmarshal(data, &ustr); err != nil { return err } *u = IPv6(ustr) return nil } // DeepCopyInto copies the receiver and writes its value into out. func (u *IPv6) DeepCopyInto(out *IPv6) { *out = *u } // DeepCopy copies the receiver into a new [IPv6]. func (u *IPv6) DeepCopy() *IPv6 { if u == nil { return nil } out := new(IPv6) u.DeepCopyInto(out) return out } // CIDR represents a Classless Inter-Domain Routing notation. // // swagger:strfmt cidr. type CIDR string // MarshalText turns this instance into text. func (u CIDR) MarshalText() ([]byte, error) { return []byte(string(u)), nil } // UnmarshalText hydrates this instance from text. func (u *CIDR) UnmarshalText(data []byte) error { // validation is performed later on *u = CIDR(string(data)) return nil } // Scan read a value from a database driver. func (u *CIDR) Scan(raw any) error { switch v := raw.(type) { case []byte: *u = CIDR(string(v)) case string: *u = CIDR(v) default: return fmt.Errorf("cannot sql.Scan() strfmt.CIDR from: %#v: %w", v, ErrFormat) } return nil } // Value converts a value to a database driver value. func (u CIDR) Value() (driver.Value, error) { return driver.Value(string(u)), nil } func (u CIDR) String() string { return string(u) } // MarshalJSON returns the [CIDR] as JSON. func (u CIDR) MarshalJSON() ([]byte, error) { return json.Marshal(string(u)) } // UnmarshalJSON sets the [CIDR] from JSON. func (u *CIDR) UnmarshalJSON(data []byte) error { var ustr string if err := json.Unmarshal(data, &ustr); err != nil { return err } *u = CIDR(ustr) return nil } // DeepCopyInto copies the receiver and writes its value into out. func (u *CIDR) DeepCopyInto(out *CIDR) { *out = *u } // DeepCopy copies the receiver into a new [CIDR]. func (u *CIDR) DeepCopy() *CIDR { if u == nil { return nil } out := new(CIDR) u.DeepCopyInto(out) return out } // MAC represents a 48 bit MAC address. // // swagger:strfmt mac. type MAC string // MarshalText turns this instance into text. func (u MAC) MarshalText() ([]byte, error) { return []byte(string(u)), nil } // UnmarshalText hydrates this instance from text. func (u *MAC) UnmarshalText(data []byte) error { // validation is performed later on *u = MAC(string(data)) return nil } // Scan read a value from a database driver. func (u *MAC) Scan(raw any) error { switch v := raw.(type) { case []byte: *u = MAC(string(v)) case string: *u = MAC(v) default: return fmt.Errorf("cannot sql.Scan() strfmt.IPv4 from: %#v: %w", v, ErrFormat) } return nil } // Value converts a value to a database driver value. func (u MAC) Value() (driver.Value, error) { return driver.Value(string(u)), nil } func (u MAC) String() string { return string(u) } // MarshalJSON returns the [MAC] as JSON. func (u MAC) MarshalJSON() ([]byte, error) { return json.Marshal(string(u)) } // UnmarshalJSON sets the [MAC] from JSON. func (u *MAC) UnmarshalJSON(data []byte) error { var ustr string if err := json.Unmarshal(data, &ustr); err != nil { return err } *u = MAC(ustr) return nil } // DeepCopyInto copies the receiver and writes its value into out. func (u *MAC) DeepCopyInto(out *MAC) { *out = *u } // DeepCopy copies the receiver into a new [MAC]. func (u *MAC) DeepCopy() *MAC { if u == nil { return nil } out := new(MAC) u.DeepCopyInto(out) return out } // UUID represents a [uuid] string format // // swagger:strfmt uuid. type UUID string // MarshalText turns this instance into text. func (u UUID) MarshalText() ([]byte, error) { return []byte(string(u)), nil } // UnmarshalText hydrates this instance from text. func (u *UUID) UnmarshalText(data []byte) error { // validation is performed later on *u = UUID(string(data)) return nil } // Scan read a value from a database driver. func (u *UUID) Scan(raw any) error { switch v := raw.(type) { case []byte: *u = UUID(string(v)) case string: *u = UUID(v) default: return fmt.Errorf("cannot sql.Scan() strfmt.UUID from: %#v: %w", v, ErrFormat) } return nil } // Value converts a value to a database driver value. func (u UUID) Value() (driver.Value, error) { return driver.Value(string(u)), nil } func (u UUID) String() string { return string(u) } // MarshalJSON returns the [UUID] as JSON. func (u UUID) MarshalJSON() ([]byte, error) { return json.Marshal(string(u)) } // UnmarshalJSON sets the [UUID] from JSON. func (u *UUID) UnmarshalJSON(data []byte) error { if string(data) == jsonNull { return nil } var ustr string if err := json.Unmarshal(data, &ustr); err != nil { return err } *u = UUID(ustr) return nil } // DeepCopyInto copies the receiver and writes its value into out. func (u *UUID) DeepCopyInto(out *UUID) { *out = *u } // DeepCopy copies the receiver into a new [UUID]. func (u *UUID) DeepCopy() *UUID { if u == nil { return nil } out := new(UUID) u.DeepCopyInto(out) return out } // UUID3 represents a uuid3 string format. // // swagger:strfmt uuid3. type UUID3 string // MarshalText turns this instance into text. func (u UUID3) MarshalText() ([]byte, error) { return []byte(string(u)), nil } // UnmarshalText hydrates this instance from text. func (u *UUID3) UnmarshalText(data []byte) error { // validation is performed later on *u = UUID3(string(data)) return nil } // Scan read a value from a database driver. func (u *UUID3) Scan(raw any) error { switch v := raw.(type) { case []byte: *u = UUID3(string(v)) case string: *u = UUID3(v) default: return fmt.Errorf("cannot sql.Scan() strfmt.UUID3 from: %#v: %w", v, ErrFormat) } return nil } // Value converts a value to a database driver value. func (u UUID3) Value() (driver.Value, error) { return driver.Value(string(u)), nil } func (u UUID3) String() string { return string(u) } // MarshalJSON returns the [UUID3] as JSON. func (u UUID3) MarshalJSON() ([]byte, error) { return json.Marshal(string(u)) } // UnmarshalJSON sets the [UUID3] from JSON. func (u *UUID3) UnmarshalJSON(data []byte) error { if string(data) == jsonNull { return nil } var ustr string if err := json.Unmarshal(data, &ustr); err != nil { return err } *u = UUID3(ustr) return nil } // DeepCopyInto copies the receiver and writes its value into out. func (u *UUID3) DeepCopyInto(out *UUID3) { *out = *u } // DeepCopy copies the receiver into a new [UUID]3. func (u *UUID3) DeepCopy() *UUID3 { if u == nil { return nil } out := new(UUID3) u.DeepCopyInto(out) return out } // UUID4 represents a uuid4 string format. // // swagger:strfmt uuid4. type UUID4 string // MarshalText turns this instance into text. func (u UUID4) MarshalText() ([]byte, error) { return []byte(string(u)), nil } // UnmarshalText hydrates this instance from text. func (u *UUID4) UnmarshalText(data []byte) error { // validation is performed later on *u = UUID4(string(data)) return nil } // Scan read a value from a database driver. func (u *UUID4) Scan(raw any) error { switch v := raw.(type) { case []byte: *u = UUID4(string(v)) case string: *u = UUID4(v) default: return fmt.Errorf("cannot sql.Scan() strfmt.UUID4 from: %#v: %w", v, ErrFormat) } return nil } // Value converts a value to a database driver value. func (u UUID4) Value() (driver.Value, error) { return driver.Value(string(u)), nil } func (u UUID4) String() string { return string(u) } // MarshalJSON returns the [UUID4] as JSON. func (u UUID4) MarshalJSON() ([]byte, error) { return json.Marshal(string(u)) } // UnmarshalJSON sets the [UUID4] from JSON. func (u *UUID4) UnmarshalJSON(data []byte) error { if string(data) == jsonNull { return nil } var ustr string if err := json.Unmarshal(data, &ustr); err != nil { return err } *u = UUID4(ustr) return nil } // DeepCopyInto copies the receiver and writes its value into out. func (u *UUID4) DeepCopyInto(out *UUID4) { *out = *u } // DeepCopy copies the receiver into a new UUID4. func (u *UUID4) DeepCopy() *UUID4 { if u == nil { return nil } out := new(UUID4) u.DeepCopyInto(out) return out } // UUID5 represents a uuid5 string format. // // swagger:strfmt uuid5. type UUID5 string // MarshalText turns this instance into text. func (u UUID5) MarshalText() ([]byte, error) { return []byte(string(u)), nil } // UnmarshalText hydrates this instance from text. func (u *UUID5) UnmarshalText(data []byte) error { // validation is performed later on *u = UUID5(string(data)) return nil } // Scan read a value from a database driver. func (u *UUID5) Scan(raw any) error { switch v := raw.(type) { case []byte: *u = UUID5(string(v)) case string: *u = UUID5(v) default: return fmt.Errorf("cannot sql.Scan() strfmt.UUID5 from: %#v: %w", v, ErrFormat) } return nil } // Value converts a value to a database driver value. func (u UUID5) Value() (driver.Value, error) { return driver.Value(string(u)), nil } func (u UUID5) String() string { return string(u) } // MarshalJSON returns the [UUID5] as JSON. func (u UUID5) MarshalJSON() ([]byte, error) { return json.Marshal(string(u)) } // UnmarshalJSON sets the [UUID5] from JSON. func (u *UUID5) UnmarshalJSON(data []byte) error { if string(data) == jsonNull { return nil } var ustr string if err := json.Unmarshal(data, &ustr); err != nil { return err } *u = UUID5(ustr) return nil } // DeepCopyInto copies the receiver and writes its value into out. func (u *UUID5) DeepCopyInto(out *UUID5) { *out = *u } // DeepCopy copies the receiver into a new [UUID5]. func (u *UUID5) DeepCopy() *UUID5 { if u == nil { return nil } out := new(UUID5) u.DeepCopyInto(out) return out } // UUID7 represents a uuid7 string format. // // swagger:strfmt uuid7. type UUID7 string // MarshalText turns this instance into text. func (u UUID7) MarshalText() ([]byte, error) { return []byte(string(u)), nil } // UnmarshalText hydrates this instance from text. func (u *UUID7) UnmarshalText(data []byte) error { // validation is performed later on *u = UUID7(string(data)) return nil } // Scan read a value from a database driver. func (u *UUID7) Scan(raw any) error { switch v := raw.(type) { case []byte: *u = UUID7(string(v)) case string: *u = UUID7(v) default: return fmt.Errorf("cannot sql.Scan() strfmt.UUID7 from: %#v: %w", v, ErrFormat) } return nil } // Value converts a value to a database driver value. func (u UUID7) Value() (driver.Value, error) { return driver.Value(string(u)), nil } func (u UUID7) String() string { return string(u) } // MarshalJSON returns the [UUID7] as JSON. func (u UUID7) MarshalJSON() ([]byte, error) { return json.Marshal(string(u)) } // UnmarshalJSON sets the [UUID7] from JSON. func (u *UUID7) UnmarshalJSON(data []byte) error { if string(data) == jsonNull { return nil } var ustr string if err := json.Unmarshal(data, &ustr); err != nil { return err } *u = UUID7(ustr) return nil } // DeepCopyInto copies the receiver and writes its value into out. func (u *UUID7) DeepCopyInto(out *UUID7) { *out = *u } // DeepCopy copies the receiver into a new [UUID]7. func (u *UUID7) DeepCopy() *UUID7 { if u == nil { return nil } out := new(UUID7) u.DeepCopyInto(out) return out } // ISBN represents an isbn string format. // // swagger:strfmt isbn. type ISBN string // MarshalText turns this instance into text. func (u ISBN) MarshalText() ([]byte, error) { return []byte(string(u)), nil } // UnmarshalText hydrates this instance from text. func (u *ISBN) UnmarshalText(data []byte) error { // validation is performed later on *u = ISBN(string(data)) return nil } // Scan read a value from a database driver. func (u *ISBN) Scan(raw any) error { switch v := raw.(type) { case []byte: *u = ISBN(string(v)) case string: *u = ISBN(v) default: return fmt.Errorf("cannot sql.Scan() strfmt.ISBN from: %#v: %w", v, ErrFormat) } return nil } // Value converts a value to a database driver value. func (u ISBN) Value() (driver.Value, error) { return driver.Value(string(u)), nil } func (u ISBN) String() string { return string(u) } // MarshalJSON returns the [ISBN] as JSON. func (u ISBN) MarshalJSON() ([]byte, error) { return json.Marshal(string(u)) } // UnmarshalJSON sets the [ISBN] from JSON. func (u *ISBN) UnmarshalJSON(data []byte) error { if string(data) == jsonNull { return nil } var ustr string if err := json.Unmarshal(data, &ustr); err != nil { return err } *u = ISBN(ustr) return nil } // DeepCopyInto copies the receiver and writes its value into out. func (u *ISBN) DeepCopyInto(out *ISBN) { *out = *u } // DeepCopy copies the receiver into a new [ISBN]. func (u *ISBN) DeepCopy() *ISBN { if u == nil { return nil } out := new(ISBN) u.DeepCopyInto(out) return out } // ISBN10 represents an isbn 10 string format. // // swagger:strfmt isbn10. type ISBN10 string // MarshalText turns this instance into text. func (u ISBN10) MarshalText() ([]byte, error) { return []byte(string(u)), nil } // UnmarshalText hydrates this instance from text. func (u *ISBN10) UnmarshalText(data []byte) error { // validation is performed later on *u = ISBN10(string(data)) return nil } // Scan read a value from a database driver. func (u *ISBN10) Scan(raw any) error { switch v := raw.(type) { case []byte: *u = ISBN10(string(v)) case string: *u = ISBN10(v) default: return fmt.Errorf("cannot sql.Scan() strfmt.ISBN10 from: %#v: %w", v, ErrFormat) } return nil } // Value converts a value to a database driver value. func (u ISBN10) Value() (driver.Value, error) { return driver.Value(string(u)), nil } func (u ISBN10) String() string { return string(u) } // MarshalJSON returns the [ISBN10] as JSON. func (u ISBN10) MarshalJSON() ([]byte, error) { return json.Marshal(string(u)) } // UnmarshalJSON sets the [ISBN10] from JSON. func (u *ISBN10) UnmarshalJSON(data []byte) error { if string(data) == jsonNull { return nil } var ustr string if err := json.Unmarshal(data, &ustr); err != nil { return err } *u = ISBN10(ustr) return nil } // DeepCopyInto copies the receiver and writes its value into out. func (u *ISBN10) DeepCopyInto(out *ISBN10) { *out = *u } // DeepCopy copies the receiver into a new [ISBN10]. func (u *ISBN10) DeepCopy() *ISBN10 { if u == nil { return nil } out := new(ISBN10) u.DeepCopyInto(out) return out } // ISBN13 represents an isbn 13 string format. // // swagger:strfmt isbn13. type ISBN13 string // MarshalText turns this instance into text. func (u ISBN13) MarshalText() ([]byte, error) { return []byte(string(u)), nil } // UnmarshalText hydrates this instance from text. func (u *ISBN13) UnmarshalText(data []byte) error { // validation is performed later on *u = ISBN13(string(data)) return nil } // Scan read a value from a database driver. func (u *ISBN13) Scan(raw any) error { switch v := raw.(type) { case []byte: *u = ISBN13(string(v)) case string: *u = ISBN13(v) default: return fmt.Errorf("cannot sql.Scan() strfmt.ISBN13 from: %#v: %w", v, ErrFormat) } return nil } // Value converts a value to a database driver value. func (u ISBN13) Value() (driver.Value, error) { return driver.Value(string(u)), nil } func (u ISBN13) String() string { return string(u) } // MarshalJSON returns the [ISBN13] as JSON. func (u ISBN13) MarshalJSON() ([]byte, error) { return json.Marshal(string(u)) } // UnmarshalJSON sets the [ISBN13] from JSON. func (u *ISBN13) UnmarshalJSON(data []byte) error { if string(data) == jsonNull { return nil } var ustr string if err := json.Unmarshal(data, &ustr); err != nil { return err } *u = ISBN13(ustr) return nil } // DeepCopyInto copies the receiver and writes its value into out. func (u *ISBN13) DeepCopyInto(out *ISBN13) { *out = *u } // DeepCopy copies the receiver into a new [ISBN13]. func (u *ISBN13) DeepCopy() *ISBN13 { if u == nil { return nil } out := new(ISBN13) u.DeepCopyInto(out) return out } // CreditCard represents a credit card string format. // // swagger:strfmt creditcard. type CreditCard string // MarshalText turns this instance into text. func (u CreditCard) MarshalText() ([]byte, error) { return []byte(string(u)), nil } // UnmarshalText hydrates this instance from text. func (u *CreditCard) UnmarshalText(data []byte) error { // validation is performed later on *u = CreditCard(string(data)) return nil } // Scan read a value from a database driver. func (u *CreditCard) Scan(raw any) error { switch v := raw.(type) { case []byte: *u = CreditCard(string(v)) case string: *u = CreditCard(v) default: return fmt.Errorf("cannot sql.Scan() strfmt.CreditCard from: %#v: %w", v, ErrFormat) } return nil } // Value converts a value to a database driver value. func (u CreditCard) Value() (driver.Value, error) { return driver.Value(string(u)), nil } func (u CreditCard) String() string { return string(u) } // MarshalJSON returns the [CreditCard] as JSON. func (u CreditCard) MarshalJSON() ([]byte, error) { return json.Marshal(string(u)) } // UnmarshalJSON sets the [CreditCard] from JSON. func (u *CreditCard) UnmarshalJSON(data []byte) error { if string(data) == jsonNull { return nil } var ustr string if err := json.Unmarshal(data, &ustr); err != nil { return err } *u = CreditCard(ustr) return nil } // DeepCopyInto copies the receiver and writes its value into out. func (u *CreditCard) DeepCopyInto(out *CreditCard) { *out = *u } // DeepCopy copies the receiver into a new [CreditCard]. func (u *CreditCard) DeepCopy() *CreditCard { if u == nil { return nil } out := new(CreditCard) u.DeepCopyInto(out) return out } // SSN represents a social security string format. // // swagger:strfmt ssn. type SSN string // MarshalText turns this instance into text. func (u SSN) MarshalText() ([]byte, error) { return []byte(string(u)), nil } // UnmarshalText hydrates this instance from text. func (u *SSN) UnmarshalText(data []byte) error { // validation is performed later on *u = SSN(string(data)) return nil } // Scan read a value from a database driver. func (u *SSN) Scan(raw any) error { switch v := raw.(type) { case []byte: *u = SSN(string(v)) case string: *u = SSN(v) default: return fmt.Errorf("cannot sql.Scan() strfmt.SSN from: %#v: %w", v, ErrFormat) } return nil } // Value converts a value to a database driver value. func (u SSN) Value() (driver.Value, error) { return driver.Value(string(u)), nil } func (u SSN) String() string { return string(u) } // MarshalJSON returns the [SSN] as JSON. func (u SSN) MarshalJSON() ([]byte, error) { return json.Marshal(string(u)) } // UnmarshalJSON sets the [SSN] from JSON. func (u *SSN) UnmarshalJSON(data []byte) error { if string(data) == jsonNull { return nil } var ustr string if err := json.Unmarshal(data, &ustr); err != nil { return err } *u = SSN(ustr) return nil } // DeepCopyInto copies the receiver and writes its value into out. func (u *SSN) DeepCopyInto(out *SSN) { *out = *u } // DeepCopy copies the receiver into a new [SSN]. func (u *SSN) DeepCopy() *SSN { if u == nil { return nil } out := new(SSN) u.DeepCopyInto(out) return out } // HexColor represents a hex color string format. // // swagger:strfmt hexcolor. type HexColor string // MarshalText turns this instance into text. func (h HexColor) MarshalText() ([]byte, error) { return []byte(string(h)), nil } // UnmarshalText hydrates this instance from text. func (h *HexColor) UnmarshalText(data []byte) error { // validation is performed later on *h = HexColor(string(data)) return nil } // Scan read a value from a database driver. func (h *HexColor) Scan(raw any) error { switch v := raw.(type) { case []byte: *h = HexColor(string(v)) case string: *h = HexColor(v) default: return fmt.Errorf("cannot sql.Scan() strfmt.HexColor from: %#v: %w", v, ErrFormat) } return nil } // Value converts a value to a database driver value. func (h HexColor) Value() (driver.Value, error) { return driver.Value(string(h)), nil } func (h HexColor) String() string { return string(h) } // MarshalJSON returns the [HexColor] as JSON. func (h HexColor) MarshalJSON() ([]byte, error) { return json.Marshal(string(h)) } // UnmarshalJSON sets the [HexColor] from JSON. func (h *HexColor) UnmarshalJSON(data []byte) error { if string(data) == jsonNull { return nil } var ustr string if err := json.Unmarshal(data, &ustr); err != nil { return err } *h = HexColor(ustr) return nil } // DeepCopyInto copies the receiver and writes its value into out. func (h *HexColor) DeepCopyInto(out *HexColor) { *out = *h } // DeepCopy copies the receiver into a new [HexColor]. func (h *HexColor) DeepCopy() *HexColor { if h == nil { return nil } out := new(HexColor) h.DeepCopyInto(out) return out } // RGBColor represents a RGB color string format. // // swagger:strfmt rgbcolor. type RGBColor string // MarshalText turns this instance into text. func (r RGBColor) MarshalText() ([]byte, error) { return []byte(string(r)), nil } // UnmarshalText hydrates this instance from text. func (r *RGBColor) UnmarshalText(data []byte) error { // validation is performed later on *r = RGBColor(string(data)) return nil } // Scan read a value from a database driver. func (r *RGBColor) Scan(raw any) error { switch v := raw.(type) { case []byte: *r = RGBColor(string(v)) case string: *r = RGBColor(v) default: return fmt.Errorf("cannot sql.Scan() strfmt.RGBColor from: %#v: %w", v, ErrFormat) } return nil } // Value converts a value to a database driver value. func (r RGBColor) Value() (driver.Value, error) { return driver.Value(string(r)), nil } func (r RGBColor) String() string { return string(r) } // MarshalJSON returns the [RGBColor] as JSON. func (r RGBColor) MarshalJSON() ([]byte, error) { return json.Marshal(string(r)) } // UnmarshalJSON sets the [RGBColor] from JSON. func (r *RGBColor) UnmarshalJSON(data []byte) error { if string(data) == jsonNull { return nil } var ustr string if err := json.Unmarshal(data, &ustr); err != nil { return err } *r = RGBColor(ustr) return nil } // DeepCopyInto copies the receiver and writes its value into out. func (r *RGBColor) DeepCopyInto(out *RGBColor) { *out = *r } // DeepCopy copies the receiver into a new [RGBColor]. func (r *RGBColor) DeepCopy() *RGBColor { if r == nil { return nil } out := new(RGBColor) r.DeepCopyInto(out) return out } // Password represents a password. // This has no validations and is mainly used as a marker for UI components. // // swagger:strfmt password. type Password string // MarshalText turns this instance into text. func (r Password) MarshalText() ([]byte, error) { return []byte(string(r)), nil } // UnmarshalText hydrates this instance from text. func (r *Password) UnmarshalText(data []byte) error { // validation is performed later on *r = Password(string(data)) return nil } // Scan read a value from a database driver. func (r *Password) Scan(raw any) error { switch v := raw.(type) { case []byte: *r = Password(string(v)) case string: *r = Password(v) default: return fmt.Errorf("cannot sql.Scan() strfmt.Password from: %#v: %w", v, ErrFormat) } return nil } // Value converts a value to a database driver value. func (r Password) Value() (driver.Value, error) { return driver.Value(string(r)), nil } func (r Password) String() string { return string(r) } // MarshalJSON returns the Password as JSON. func (r Password) MarshalJSON() ([]byte, error) { return json.Marshal(string(r)) } // UnmarshalJSON sets the Password from JSON. func (r *Password) UnmarshalJSON(data []byte) error { if string(data) == jsonNull { return nil } var ustr string if err := json.Unmarshal(data, &ustr); err != nil { return err } *r = Password(ustr) return nil } // DeepCopyInto copies the receiver and writes its value into out. func (r *Password) DeepCopyInto(out *Password) { *out = *r } // DeepCopy copies the receiver into a new Password. func (r *Password) DeepCopy() *Password { if r == nil { return nil } out := new(Password) r.DeepCopyInto(out) return out } func isRequestURI(rawurl string) bool { _, err := url.ParseRequestURI(rawurl) return err == nil } // isIPv4 checks if the string is an IP version 4. func isIPv4(str string) bool { ip := net.ParseIP(str) return ip != nil && strings.Contains(str, ".") } // isIPv6 checks if the string is an IP version 6. func isIPv6(str string) bool { ip := net.ParseIP(str) return ip != nil && strings.Contains(str, ":") } // isCIDR checks if the string is a valid CIDR notation (IPV4 & IPV6). func isCIDR(str string) bool { _, _, err := net.ParseCIDR(str) return err == nil } // isMAC checks if a string is valid MAC address. // Possible MAC formats: // 01:23:45:67:89:ab // 01:23:45:67:89:ab:cd:ef // 01-23-45-67-89-ab // 01-23-45-67-89-ab-cd-ef // 0123.4567.89ab // 0123.4567.89ab.cdef. func isMAC(str string) bool { _, err := net.ParseMAC(str) return err == nil } // isISBN checks if the string is an ISBN (version 10 or 13). // If version value is not equal to 10 or 13, it will be checks both variants. func isISBN(str string, version int) bool { sanitized := whiteSpacesAndMinus.ReplaceAllString(str, "") var checksum int32 var i int32 switch version { case isbnVersion10: if !rxISBN10.MatchString(sanitized) { return false } for i = range isbnVersion10 - 1 { checksum += (i + 1) * int32(sanitized[i]-'0') } if sanitized[isbnVersion10-1] == 'X' { checksum += isbnVersion10 * isbnVersion10 } else { checksum += isbnVersion10 * int32(sanitized[isbnVersion10-1]-'0') } if checksum%(isbnVersion10+1) == 0 { return true } return false case isbnVersion13: if !rxISBN13.MatchString(sanitized) { return false } factor := []int32{1, 3} for i = range isbnVersion13 - 1 { checksum += factor[i%2] * int32(sanitized[i]-'0') } return (int32(sanitized[isbnVersion13-1]-'0'))-((decimalBase-(checksum%decimalBase))%decimalBase) == 0 default: return isISBN(str, isbnVersion10) || isISBN(str, isbnVersion13) } } // isISBN10 checks if the string is an ISBN version 10. func isISBN10(str string) bool { return isISBN(str, isbnVersion10) } // isISBN13 checks if the string is an ISBN version 13. func isISBN13(str string) bool { return isISBN(str, isbnVersion13) } // isCreditCard checks if the string is a credit card. func isCreditCard(str string) bool { sanitized := whiteSpacesAndMinus.ReplaceAllString(str, "") if !rxCreditCard.MatchString(sanitized) { return false } number, err := strconv.ParseInt(sanitized, 0, 64) if err != nil { return false } number, lastDigit := number/decimalBase, number%decimalBase var sum int64 for i := 0; number > 0; i++ { digit := number % decimalBase if i%2 == 0 { digit *= 2 if digit > decimalBase-1 { digit -= decimalBase - 1 } } sum += digit number /= decimalBase } return (sum+lastDigit)%decimalBase == 0 } // isSSN will validate the given string as a U.S. Social Security Number. func isSSN(str string) bool { if str == "" || len(str) != 11 { return false } return rxSSN.MatchString(str) } // isHexcolor checks if the string is a hexadecimal color. func isHexcolor(str string) bool { return rxHexcolor.MatchString(str) } // isRGBcolor checks if the string is a valid RGB color in form rgb(RRR, GGG, BBB). func isRGBcolor(str string) bool { return rxRGBcolor.MatchString(str) } // isBase64 checks if a string is base64 encoded. func isBase64(str string) bool { _, err := base64.StdEncoding.DecodeString(str) return err == nil } go-openapi-strfmt-879971e/default_test.go000066400000000000000000000677221517436663600204350ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package strfmt import ( "database/sql" "database/sql/driver" "encoding" "encoding/base64" "encoding/json" "fmt" "io" "reflect" "regexp" "strings" "testing" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" "github.com/google/uuid" ) func TestFormatURI(t *testing.T) { uri := URI("http://somewhere.com") str := "http://somewhereelse.com" testStringFormat(t, &uri, "uri", str, []string{}, []string{"somewhere.com"}) } func validEmails() []string { return []string{ "blah@gmail.com", "test@d.verylongtoplevel", "email+tag@gmail.com", `" "@example.com`, `"Abc\@def"@example.com`, `"Fred Bloggs"@example.com`, `"Joe\\Blow"@example.com`, `"Abc@def"@example.com`, "customer/department=shipping@example.com", "$A12345@example.com", "!def!xyz%abc@example.com", "_somename@example.com", "!#$%&'*+-/=?^_`{}|~@example.com", "Miles.O'Brian@example.com", "postmaster@☁→❄→☃→☀→☺→☂→☹→✝.ws", "root@localhost", "john@com", "api@piston.ninja", } } func TestFormatEmail(t *testing.T) { email := Email("somebody@somewhere.com") str := string("somebodyelse@somewhere.com") testStringFormat(t, &email, "email", str, validEmails(), []string{"somebody@somewhere@com"}) } func invalidHostnames() []string { veryLongStr := strings.Repeat("a", 256) longStr := strings.Repeat("a", 64) longAddrSegment := strings.Join([]string{"x", "y", longStr}, ".") return []string{ "somewhere.com!", "user@email.domain", veryLongStr, longAddrSegment, // dashes "www.example-.org", "www.--example.org", "-www.example.org", "www-.example.org", "www.d-.org", "www.-d.org", "www-", "-www", "a.b.c.dot-", // other characters (not in symbols) "www.ex ample.org", "_www.example.org", "www.ex;ample.org", "www.example_underscored.org", // top-level domains too short "www.詹姆斯.x", //nolint:gosmopolitan // testing internationalized hostname validation "a.b.c.d", "www.詹姆斯.XN--1B4C3D", //nolint:gosmopolitan // testing internationalized hostname validation "@www", "a.b.c.é;ö", // these code points are invalid "ex=ample.com", "ex$ample", "example^example", "", "ex_ample", "ex*ample", "ex\\ample", "www.\u0025", "www.\u007f", "www.\u0000", "www..com", "..example.com", "5512", // only digits is invalid "[fe80::b059:65f4:e877:c40", // invalid ip v6 "fe80::b059:65f4:e877:c40]", "[192.168.250.1]", // is an ip v4 not an ip v6 "[", // invalid start of ipv6 "[]", // empty ip v6 "[1:2:3:4:5:6:7:8:9]", // invalid ip v6 "[:1]]", // invalid ip v6 "[1::1::1]]", // invalid ip v6 "[fe80::1%en0]", // ip v6 with zone "[fe80::b059:65f4:e877:c40%20]", // ip v6 with zone "[2001:0db8:85a3:0000:0000:8a2e:0370:7334].", // invalid in this context "", // empty host ".", "..", "192.168.219.168.254", // invalid ip v4 "256.256.256.256", // looks like an IP v4 but is not "192.168..168", // invalid ip v4 "192.168.0xg.168", // invalid ip v4 "0..0x300", // out of range IP "1.2.3.09", // leading 0, not an octal value "09.2.3.4", // leading 0, not an octal value "0x100.2.3.4", // out of range IP v4 "192.0xffA80001", // out of range IP v4 "0x0a.2.0x0000000000f.3", // number part is too long "foo.2.3.4", // expected an IP v4 "foo.09", // expected an IP v4 "foo.0x04", // expected an IP v4 "💩.123", // expected an IP v4 "0b1010.2.3.4", // unsupported binary digits "0o07.2.3.4", // unsupported alternated octal notation "localhost:81", } } func validHostnames() []string { return []string{ "somewhere.com", "Somewhere.Com", "888.com", "a.com", "a.b.com", "a.b.c.com", "a.b.c.d.com", "a.b.c.d.e.com", "1.com", "1.2.com", "1.2.3.com", "1.2.3.4.com", "99.domain.com", "99.99.domain.com", "1wwworg.example.com", // valid, per RFC1123 "1000wwworg.example.com", "xn--bcher-kva.example.com", // puny encoded "xn-80ak6aa92e.co", "xn-80ak6aa92e.com", "xn--ls8h.la", "x.", // valid trailing dot "foo.bar.", // valid trailing dot // extended symbol alphabet "☁→❄→☃→☀→☺→☂→☹→✝.ws", "💩.tv", "www.example.onion", "www.example.ôlà", "ôlà.ôlà", "ôlà.ôlà.ôlà", "localhost", "example", "x", "x-y", "a.b.c.dot", "www.example.org", "a.b.c.d.e.f.g.dot", "www.example-hyphenated.org", "foo.x04", // valid (last part not a number) "foo.0xz", // valid (last part not a number) // localized hostnames "www.詹姆斯.org", //nolint:gosmopolitan // testing internationalized hostname validation "example.إختبار", "www.élégigôö.org", "www.詹姆斯.london", //nolint:gosmopolitan // testing internationalized hostname validation // localized top-level domains (valid unicode top-level domains) "www.च.चऒ", "www.कॉम", "www.詹姆斯.xn--11b4c3d", //nolint:gosmopolitan // testing internationalized hostname validation "1.1.1.1", // is a valid IP v4 address "1.1.1.1.", // is a valid IP v4 address, with trailing dot "1.1.1.06", // valid IP, with last part octal "1.1.1.0xf", // valid IP, with last part hex "1.1.1.0xz", // valid hostname, not IP "1.0.1.1", // is a valid IP v4 address "1.0x.1.1", // is a valid IP v4 address "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", // is a valid IP v6 address "192.168.219.a1", // looks like an invalid ip v4, but is actually a valid domain "192.0x00A80001", // mixed decimal / hex IP v4 "0300.0250.0340.001", // octal IP v4 "1.2.3.00", // leading 0, valid octal value } } func TestFormatHostname(t *testing.T) { hostname := Hostname("somewhere.com") str := string("somewhere.com") testStringFormat(t, &hostname, "hostname", str, []string{}, invalidHostnames()) testStringFormat(t, &hostname, "hostname", str, validHostnames(), []string{}) } func TestFormatIPv4(t *testing.T) { ipv4 := IPv4("192.168.254.1") str := string("192.168.254.2") testStringFormat(t, &ipv4, "ipv4", str, []string{}, []string{"198.168.254.2.2"}) } func TestFormatIPv6(t *testing.T) { ipv6 := IPv6("::1") str := string("::2") // Proposal for enhancement: test ipv6 zones testStringFormat(t, &ipv6, "ipv6", str, []string{}, []string{"127.0.0.1"}) } func TestFormatCIDR(t *testing.T) { cidr := CIDR("192.168.254.1/24") str := string("192.168.254.2/24") testStringFormat(t, &cidr, "cidr", str, []string{"192.0.2.1/24", "2001:db8:a0b:12f0::1/32"}, []string{"198.168.254.2", "2001:db8:a0b:12f0::1"}) } func TestFormatMAC(t *testing.T) { mac := MAC("01:02:03:04:05:06") str := string("06:05:04:03:02:01") testStringFormat(t, &mac, "mac", str, []string{}, []string{"01:02:03:04:05"}) } func validUUID3s() []string { other3 := uuid.NewMD5(uuid.NameSpaceURL, []byte("somewhereelse.com")) return []string{ other3.String(), strings.ReplaceAll(other3.String(), "-", ""), } } func invalidUUID3s() []string { other3 := uuid.NewMD5(uuid.NameSpaceURL, []byte("somewhereelse.com")) other4 := uuid.Must(uuid.NewRandom()) other5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhereelse.com")) return []string{ "not-a-uuid", other4.String(), other5.String(), strings.ReplaceAll(other4.String(), "-", ""), strings.ReplaceAll(other5.String(), "-", ""), strings.Replace(other3.String(), "-", "", 2), strings.Replace(other4.String(), "-", "", 2), strings.Replace(other5.String(), "-", "", 2), } } func TestFormatUUID3(t *testing.T) { first3 := uuid.NewMD5(uuid.NameSpaceURL, []byte("somewhere.com")) uuid3 := UUID3(first3.String()) str := first3.String() testStringFormat(t, &uuid3, "uuid3", str, validUUID3s(), invalidUUID3s(), ) // special case for zero UUID var uuidZero UUID3 err := uuidZero.UnmarshalJSON([]byte(jsonNull)) require.NoError(t, err) assert.EqualT(t, UUID3(""), uuidZero) } func validUUID4s() []string { other4 := uuid.Must(uuid.NewRandom()) return []string{ other4.String(), strings.ReplaceAll(other4.String(), "-", ""), } } func invalidUUID4s() []string { other3 := uuid.NewMD5(uuid.NameSpaceURL, []byte("somewhere.com")) other4 := uuid.Must(uuid.NewRandom()) other5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhereelse.com")) return []string{ "not-a-uuid", other3.String(), other5.String(), strings.ReplaceAll(other3.String(), "-", ""), strings.ReplaceAll(other5.String(), "-", ""), strings.Replace(other3.String(), "-", "", 2), strings.Replace(other4.String(), "-", "", 2), strings.Replace(other5.String(), "-", "", 2), } } func TestFormatUUID4(t *testing.T) { first4 := uuid.Must(uuid.NewRandom()) other4 := uuid.Must(uuid.NewRandom()) uuid4 := UUID4(first4.String()) str := other4.String() testStringFormat(t, &uuid4, "uuid4", str, validUUID4s(), invalidUUID4s(), ) // special case for zero UUID var uuidZero UUID4 err := uuidZero.UnmarshalJSON([]byte(jsonNull)) require.NoError(t, err) assert.EqualT(t, UUID4(""), uuidZero) } func validUUID5s() []string { other5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhereelse.com")) return []string{ other5.String(), strings.ReplaceAll(other5.String(), "-", ""), } } func invalidUUID5s() []string { other3 := uuid.NewMD5(uuid.NameSpaceURL, []byte("somewhere.com")) other4 := uuid.Must(uuid.NewRandom()) other5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhereelse.com")) return []string{ "not-a-uuid", other3.String(), other4.String(), strings.ReplaceAll(other3.String(), "-", ""), strings.ReplaceAll(other4.String(), "-", ""), strings.Replace(other3.String(), "-", "", 2), strings.Replace(other4.String(), "-", "", 2), strings.Replace(other5.String(), "-", "", 2), } } func TestFormatUUID5(t *testing.T) { first5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhere.com")) other5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhereelse.com")) uuid5 := UUID5(first5.String()) str := other5.String() testStringFormat(t, &uuid5, "uuid5", str, validUUID5s(), invalidUUID5s(), ) // special case for zero UUID var uuidZero UUID5 err := uuidZero.UnmarshalJSON([]byte(jsonNull)) require.NoError(t, err) assert.EqualT(t, UUID5(""), uuidZero) } func validUUID7s() []string { other7 := uuid.Must(uuid.NewV7()) return []string{ other7.String(), strings.ReplaceAll(other7.String(), "-", ""), } } func invalidUUID7s() []string { other3 := uuid.NewMD5(uuid.NameSpaceURL, []byte("somewhere.com")) other4 := uuid.Must(uuid.NewRandom()) other5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhereelse.com")) return []string{ "not-a-uuid", other3.String(), other4.String(), strings.ReplaceAll(other3.String(), "-", ""), strings.ReplaceAll(other4.String(), "-", ""), strings.Replace(other3.String(), "-", "", 2), strings.Replace(other4.String(), "-", "", 2), strings.Replace(other5.String(), "-", "", 2), } } func TestFormatUUID7(t *testing.T) { first7 := uuid.Must(uuid.NewV7()) str := first7.String() uuid7 := UUID7(str) testStringFormat(t, &uuid7, "uuid7", str, validUUID7s(), invalidUUID7s(), ) // special case for zero UUID var uuidZero UUID7 err := uuidZero.UnmarshalJSON([]byte(jsonNull)) require.NoError(t, err) assert.EqualT(t, UUID7(""), uuidZero) } func validUUIDs() []string { other3 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhereelse.com")) other4 := uuid.Must(uuid.NewRandom()) other5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhereelse.com")) other6 := uuid.Must(uuid.NewV6()) other7 := uuid.Must(uuid.NewV7()) microsoft := "0" + other4.String() + "f" return []string{ other3.String(), other4.String(), other5.String(), strings.ReplaceAll(other3.String(), "-", ""), strings.ReplaceAll(other4.String(), "-", ""), strings.ReplaceAll(other5.String(), "-", ""), other6.String(), other7.String(), microsoft, } } func invalidUUIDs() []string { other3 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhereelse.com")) other4 := uuid.Must(uuid.NewRandom()) other5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhereelse.com")) return []string{ "not-a-uuid", strings.Replace(other3.String(), "-", "", 2), strings.Replace(other4.String(), "-", "", 2), strings.Replace(other5.String(), "-", "", 2), } } func TestFormatUUID(t *testing.T) { first5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhere.com")) other5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhereelse.com")) uuid := UUID(first5.String()) str := other5.String() testStringFormat(t, &uuid, "uuid", str, validUUIDs(), invalidUUIDs(), ) // special case for zero UUID var uuidZero UUID err := uuidZero.UnmarshalJSON([]byte(jsonNull)) require.NoError(t, err) assert.EqualT(t, UUID(""), uuidZero) } func TestFormatISBN(t *testing.T) { isbn := ISBN("0321751043") str := string("0321751043") testStringFormat(t, &isbn, "isbn", str, []string{}, []string{"836217463"}) // bad checksum } func TestFormatISBN10(t *testing.T) { isbn10 := ISBN10("0321751043") str := string("0321751043") testStringFormat(t, &isbn10, "isbn10", str, []string{}, []string{"836217463"}) // bad checksum } func TestFormatISBN13(t *testing.T) { isbn13 := ISBN13("978-0321751041") str := string("978-0321751041") testStringFormat(t, &isbn13, "isbn13", str, []string{}, []string{"978-0321751042"}) // bad checksum } func TestFormatHexColor(t *testing.T) { hexColor := HexColor("#FFFFFF") str := string("#000000") testStringFormat(t, &hexColor, "hexcolor", str, []string{}, []string{"#fffffffz"}) } func TestFormatRGBColor(t *testing.T) { rgbColor := RGBColor("rgb(255,255,255)") str := string("rgb(0,0,0)") testStringFormat(t, &rgbColor, "rgbcolor", str, []string{}, []string{"rgb(300,0,0)"}) } func TestFormatSSN(t *testing.T) { ssn := SSN("111-11-1111") str := string("999 99 9999") testStringFormat(t, &ssn, "ssn", str, []string{}, []string{"999 99 999"}) } func TestFormatCreditCard(t *testing.T) { creditCard := CreditCard("4111-1111-1111-1111") str := string("4012-8888-8888-1881") testStringFormat(t, &creditCard, "creditcard", str, []string{}, []string{"9999-9999-9999-999"}) } func TestFormatPassword(t *testing.T) { password := Password("super secret stuff here") testStringFormat(t, &password, "password", "super secret!!!", []string{"even more secret"}, []string{}) } func TestFormatBase64(t *testing.T) { const b64 string = "This is a byte array with unprintable chars, but it also isn" str := base64.URLEncoding.EncodeToString([]byte(b64)) b := []byte(b64) expected := Base64(b) bj := []byte("\"" + str + "\"") var subj Base64 err := subj.UnmarshalText([]byte(str)) require.NoError(t, err) assert.Equal(t, expected, subj) b, err = subj.MarshalText() require.NoError(t, err) assert.Equal(t, []byte(str), b) var subj2 Base64 err = subj2.UnmarshalJSON(bj) require.NoError(t, err) assert.Equal(t, expected, subj2) assert.JSONMarshalAsT(t, string(bj), subj2) testValid(t, "byte", str) testInvalid(t, "byte", "ZWxpemFiZXRocG9zZXk") // missing pad char // Valuer interface sqlvalue, err := subj2.Value() require.NoError(t, err) sqlvalueAsString, ok := sqlvalue.(string) if assert.TrueTf(t, ok, "[%s]Value: expected driver value to be a string", "byte") { assert.Equal(t, str, sqlvalueAsString, "[%s]Value: expected %v and %v to be equal", "byte", sqlvalue, str) } // Scanner interface var subj3 Base64 err = subj3.Scan([]byte(str)) require.NoError(t, err) assert.EqualT(t, str, subj3.String()) var subj4 Base64 err = subj4.Scan(str) require.NoError(t, err) assert.EqualT(t, str, subj4.String()) err = subj4.Scan(123) require.Error(t, err) } type testableFormat interface { encoding.TextMarshaler encoding.TextUnmarshaler json.Marshaler json.Unmarshaler fmt.Stringer sql.Scanner driver.Valuer } func testStringFormat(t *testing.T, what testableFormat, format, with string, validSamples, invalidSamples []string) { t.Helper() // text encoding interface b := []byte(with) err := what.UnmarshalText(b) require.NoError(t, err) val := reflect.Indirect(reflect.ValueOf(what)) strVal := val.String() assert.EqualTf(t, with, strVal, "[%s]UnmarshalText: expected %v and %v to be value equal", format, strVal, with) b, err = what.MarshalText() require.NoError(t, err) assert.Equalf(t, []byte(with), b, "[%s]MarshalText: expected %v and %v to be value equal as []byte", format, string(b), with) // Stringer strVal = what.String() assert.Equalf(t, []byte(with), b, "[%s]String: expected %v and %v to be equal", format, strVal, with) // JSON encoding interface bj := []byte("\"" + with + "\"") err = what.UnmarshalJSON(bj) require.NoError(t, err) val = reflect.Indirect(reflect.ValueOf(what)) strVal = val.String() assert.EqualT(t, with, strVal, "[%s]UnmarshalJSON: expected %v and %v to be value equal", format, strVal, with) b, err = what.MarshalJSON() require.NoError(t, err) assert.Equalf(t, bj, b, "[%s]MarshalJSON: expected %v and %v to be value equal as []byte", format, string(b), with) // Scanner interface resetValue(t, format, what) err = what.Scan(with) require.NoError(t, err) val = reflect.Indirect(reflect.ValueOf(what)) strVal = val.String() assert.EqualT(t, with, strVal, "[%s]Scan: expected %v and %v to be value equal", format, strVal, with) err = what.Scan([]byte(with)) require.NoError(t, err) val = reflect.Indirect(reflect.ValueOf(what)) strVal = val.String() assert.EqualT(t, with, strVal, "[%s]Scan: expected %v and %v to be value equal", format, strVal, with) err = what.Scan(123) require.Error(t, err) // Valuer interface sqlvalue, err := what.Value() require.NoError(t, err) sqlvalueAsString, ok := sqlvalue.(string) if assert.TrueTf(t, ok, "[%s]Value: expected driver value to be a string", format) { assert.Equal(t, with, sqlvalueAsString, "[%s]Value: expected %v and %v to be equal", format, sqlvalue, with) } // validation with Registry for _, valid := range append(validSamples, with) { testValid(t, format, valid) } for _, invalid := range invalidSamples { testInvalid(t, format, invalid) } } func resetValue(t *testing.T, format string, what encoding.TextUnmarshaler) { t.Helper() err := what.UnmarshalText([]byte("reset value")) require.NoError(t, err) val := reflect.Indirect(reflect.ValueOf(what)) strVal := val.String() assert.EqualTf(t, "reset value", strVal, "[%s]UnmarshalText: expected %v and %v to be equal (reset value) ", format, strVal, "reset value") } func testValid(t *testing.T, name, value string) { t.Helper() ok := Default.Validates(name, value) if !ok { t.Errorf("expected %q of type %s to be valid", value, name) } } func testInvalid(t *testing.T, name, value string) { t.Helper() ok := Default.Validates(name, value) if ok { t.Errorf("expected %q of type %s to be invalid", value, name) } } func TestDeepCopyBase64(t *testing.T) { b64 := Base64("ZWxpemFiZXRocG9zZXk=") in := &b64 out := new(Base64) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *Base64 out3 := inNil.DeepCopy() assert.Nil(t, out3) } func TestDeepCopyURI(t *testing.T) { uri := URI("http://somewhere.com") in := &uri out := new(URI) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *URI out3 := inNil.DeepCopy() assert.Nil(t, out3) } func TestDeepCopyEmail(t *testing.T) { email := Email("somebody@somewhere.com") in := &email out := new(Email) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *Email out3 := inNil.DeepCopy() assert.Nil(t, out3) } func TestDeepCopyHostname(t *testing.T) { hostname := Hostname("somewhere.com") in := &hostname out := new(Hostname) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *Hostname out3 := inNil.DeepCopy() assert.Nil(t, out3) } func TestDeepCopyIPv4(t *testing.T) { ipv4 := IPv4("192.168.254.1") in := &ipv4 out := new(IPv4) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *IPv4 out3 := inNil.DeepCopy() assert.Nil(t, out3) } func TestDeepCopyIPv6(t *testing.T) { ipv6 := IPv6("::1") in := &ipv6 out := new(IPv6) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *IPv6 out3 := inNil.DeepCopy() assert.Nil(t, out3) } func TestDeepCopyCIDR(t *testing.T) { cidr := CIDR("192.0.2.1/24") in := &cidr out := new(CIDR) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *CIDR out3 := inNil.DeepCopy() assert.Nil(t, out3) } func TestDeepCopyMAC(t *testing.T) { mac := MAC("01:02:03:04:05:06") in := &mac out := new(MAC) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *MAC out3 := inNil.DeepCopy() assert.Nil(t, out3) } func TestDeepCopyUUID(t *testing.T) { first5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhere.com")) uuid := UUID(first5.String()) in := &uuid out := new(UUID) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *UUID out3 := inNil.DeepCopy() assert.Nil(t, out3) } func TestDeepCopyUUID3(t *testing.T) { first3 := uuid.NewMD5(uuid.NameSpaceURL, []byte("somewhere.com")) uuid3 := UUID3(first3.String()) in := &uuid3 out := new(UUID3) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *UUID3 out3 := inNil.DeepCopy() assert.Nil(t, out3) } func TestDeepCopyUUID4(t *testing.T) { first4 := uuid.Must(uuid.NewRandom()) uuid4 := UUID4(first4.String()) in := &uuid4 out := new(UUID4) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *UUID4 out3 := inNil.DeepCopy() assert.Nil(t, out3) } func TestDeepCopyUUID5(t *testing.T) { first5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhere.com")) uuid5 := UUID5(first5.String()) in := &uuid5 out := new(UUID5) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *UUID5 out3 := inNil.DeepCopy() assert.Nil(t, out3) } func TestDeepCopyUUID7(t *testing.T) { first7 := uuid.Must(uuid.NewV7()) uuid7 := UUID7(first7.String()) in := &uuid7 out := new(UUID7) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *UUID7 out3 := inNil.DeepCopy() assert.Nil(t, out3) } func TestDeepCopyISBN(t *testing.T) { isbn := ISBN("0321751043") in := &isbn out := new(ISBN) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *ISBN out3 := inNil.DeepCopy() assert.Nil(t, out3) } func TestDeepCopyISBN10(t *testing.T) { isbn10 := ISBN10("0321751043") in := &isbn10 out := new(ISBN10) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *ISBN10 out3 := inNil.DeepCopy() assert.Nil(t, out3) } func TestDeepCopyISBN13(t *testing.T) { isbn13 := ISBN13("978-0321751041") in := &isbn13 out := new(ISBN13) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *ISBN13 out3 := inNil.DeepCopy() assert.Nil(t, out3) } func TestDeepCopyCreditCard(t *testing.T) { creditCard := CreditCard("4111-1111-1111-1111") in := &creditCard out := new(CreditCard) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *CreditCard out3 := inNil.DeepCopy() assert.Nil(t, out3) } func TestDeepCopySSN(t *testing.T) { ssn := SSN("111-11-1111") in := &ssn out := new(SSN) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *SSN out3 := inNil.DeepCopy() assert.Nil(t, out3) } func TestDeepCopyHexColor(t *testing.T) { hexColor := HexColor("#FFFFFF") in := &hexColor out := new(HexColor) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *HexColor out3 := inNil.DeepCopy() assert.Nil(t, out3) } func TestDeepCopyRGBColor(t *testing.T) { rgbColor := RGBColor("rgb(255,255,255)") in := &rgbColor out := new(RGBColor) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *RGBColor out3 := inNil.DeepCopy() assert.Nil(t, out3) } func TestDeepCopyPassword(t *testing.T) { password := Password("super secret stuff here") in := &password out := new(Password) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *Password out3 := inNil.DeepCopy() assert.Nil(t, out3) } func BenchmarkIsUUID(b *testing.B) { const sampleSize = 100 rxUUID := regexp.MustCompile(UUIDPattern) rxUUID3 := regexp.MustCompile(UUID3Pattern) rxUUID4 := regexp.MustCompile(UUID4Pattern) rxUUID5 := regexp.MustCompile(UUID5Pattern) uuids := make([]string, 0, sampleSize) uuid3s := make([]string, 0, sampleSize) uuid4s := make([]string, 0, sampleSize) uuid5s := make([]string, 0, sampleSize) for range sampleSize { seed := []byte(uuid.Must(uuid.NewRandom()).String()) uuids = append(uuids, uuid.Must(uuid.NewRandom()).String()) uuid3s = append(uuid3s, uuid.NewMD5(uuid.NameSpaceURL, seed).String()) uuid4s = append(uuid4s, uuid.Must(uuid.NewRandom()).String()) uuid5s = append(uuid5s, uuid.NewSHA1(uuid.NameSpaceURL, seed).String()) } b.Run("IsUUID - google.uuid", benchmarkIs(uuids, IsUUID)) b.Run("IsUUID - regexp", benchmarkIs(uuids, func(id string) bool { return rxUUID.MatchString(id) })) b.Run("IsUUIDv3 - google.uuid", benchmarkIs(uuid3s, IsUUID3)) b.Run("IsUUIDv3 - regexp", benchmarkIs(uuid3s, func(id string) bool { return rxUUID3.MatchString(id) })) b.Run("IsUUIDv4 - google.uuid", benchmarkIs(uuid4s, IsUUID4)) b.Run("IsUUIDv4 - regexp", benchmarkIs(uuid4s, func(id string) bool { return rxUUID4.MatchString(id) })) b.Run("IsUUIDv5 - google.uuid", benchmarkIs(uuid5s, IsUUID5)) b.Run("IsUUIDv5 - regexp", benchmarkIs(uuid5s, func(id string) bool { return rxUUID5.MatchString(id) })) } func benchmarkIs(input []string, fn func(string) bool) func(*testing.B) { return func(b *testing.B) { var isTrue bool b.ReportAllocs() b.ResetTimer() for b.Loop() { isTrue = fn(input[b.N%len(input)]) } fmt.Fprintln(io.Discard, isTrue) } } func BenchmarkIsHostname(b *testing.B) { hostnames := []string{ "somewhere.com", "888.com", "a.com", "a.b.com", "a.b.c.com", "a.b.c.d.com", "a.b.c.d.e.com", "1.com", "1.2.com", "1.2.3.com", "1.2.3.4.com", "99.domain.com", "99.99.domain.com", "1wwworg.example.com", "1000wwworg.example.com", "xn--bcher-kva.example.com", "xn-80ak6aa92e.co", "xn-80ak6aa92e.com", "xn--ls8h.la", "☁→❄→☃→☀→☺→☂→☹→✝.ws", "www.example.onion", "www.example.ôlà", "ôlà.ôlà", "ôlà.ôlà.ôlà", "ex$ample", "localhost", "example", "x", "x-y", "a.b.c.dot", "www.example.org", "a.b.c.d.e.f.g.dot", "ex=ample.com", "", "www.example-hyphenated.org", "www.詹姆斯.org", //nolint:gosmopolitan // testing internationalized hostname validation "www.élégigôö.org", "www.詹姆斯.london", //nolint:gosmopolitan // testing internationalized hostname validation } rxHostname := regexp.MustCompile(HostnamePattern) b.Run("IsHostname - regexp", benchmarkIs(hostnames, func(str string) bool { // regexp-based version of IsHostname if !rxHostname.MatchString(str) { return false } const maxHostnameLength = 255 if len(str) > maxHostnameLength { return false } const maxNodeLength = 63 parts := strings.Split(str, ".") valid := true for _, p := range parts { if len(p) > maxNodeLength { valid = false } } return valid })) b.Run("IsHostname - idna", benchmarkIs(hostnames, IsHostname)) } go-openapi-strfmt-879971e/doc.go000066400000000000000000000002571517436663600165050ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 // Package strfmt contains custom string formats. package strfmt go-openapi-strfmt-879971e/docs/000077500000000000000000000000001517436663600163355ustar00rootroot00000000000000go-openapi-strfmt-879971e/docs/.gitkeep000066400000000000000000000000001517436663600177540ustar00rootroot00000000000000go-openapi-strfmt-879971e/duration.go000066400000000000000000000224221517436663600175630ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package strfmt import ( "database/sql/driver" "encoding/json" "fmt" "strings" "time" "unicode" ) func init() { //nolint:gochecknoinits // registers duration format in the default registry d := Duration(0) Default.Add("duration", &d, IsDuration) } const ( hoursInDay = 24 daysInWeek = 7 nanos = uint64(time.Nanosecond) micros = uint64(time.Microsecond) millis = uint64(time.Millisecond) seconds = uint64(time.Second) minutes = uint64(time.Minute) hours = uint64(time.Hour) days = uint64(hoursInDay * time.Hour) weeks = uint64(hoursInDay * daysInWeek * time.Hour) maxUint64 = uint64(1 << 63) ) // timeMultiplier holds all supported aliases for duration units, including their plural form. // //nolint:gochecknoglobals // package-level lookup tables for duration parsing var timeMultiplier = map[string]uint64{ "ns": nanos, "nano": nanos, "nanosecond": nanos, "nanoseconds": nanos, "nanos": nanos, "us": micros, "µs": micros, // U+00B5 = micro symbol "μs": micros, // U+03BC = Greek letter mu "micro": micros, "micros": micros, "microsecond": micros, "microseconds": micros, "ms": millis, "milli": millis, "millis": millis, "millisecond": millis, "milliseconds": millis, "s": seconds, "sec": seconds, "secs": seconds, "second": seconds, "seconds": seconds, "m": minutes, "min": minutes, "mins": minutes, "minute": minutes, "minutes": minutes, "h": hours, "hr": hours, "hrs": hours, "hour": hours, "hours": hours, "d": days, "day": days, "days": days, "w": weeks, "wk": weeks, "wks": weeks, "week": weeks, "weeks": weeks, } // IsDuration returns true if the provided string is a valid duration. func IsDuration(str string) bool { _, err := ParseDuration(str) return err == nil } // Duration represents a duration // // Duration stores a period of time as a nanosecond count, with the largest // representable duration being approximately 290 years. // // swagger:strfmt duration. type Duration time.Duration // MarshalText turns this instance into text. func (d Duration) MarshalText() ([]byte, error) { return []byte(time.Duration(d).String()), nil } // UnmarshalText hydrates this instance from text. func (d *Duration) UnmarshalText(data []byte) error { // validation is performed later on dd, err := ParseDuration(string(data)) if err != nil { return err } *d = Duration(dd) return nil } // ParseDuration parses a duration from a string // // It is similar to [time.ParseDuration] but support additional units like days and weeks, // additional abreviations for units and is more tolerant on the presence of blank spaces. // // A duration may be negative or fractional. // // # Differences with [time.ParseDuration] // // - more supported units and aliases (see below) // - sign followed by blank space is tolerated // - tolerates blanks between duration and unit (e.g. "300 ms") // // # Supported units // // Units may be specified using aliases or a plural form. // // - "ns", "nano", "nanosecond", "nanoseconds", "nanos" // - "us", "µs" (U+00B5 = micro symbol), "μs" (U+03BC = Greek letter mu), "micro", "micros", "microsecond", "microseconds" // - "ms", "milli", "millis", "millisecond", "milliseconds" // - "s", "sec", "secs", "second", "seconds" // - "m", "min", "mins", "minute", "minutes" // - "h", "hr", "hrs", "hour", "hours" // - "d", "day", "days" // - "w", "wk", "wks", "week", "weeks" // // NOTE: inspired by scala duration syntax. // // # Examples // // "300ms", "-1.5h", "2h45m", // ".5 week", // "2 minutes 45 seconds". // //nolint:gocognit,gocyclo,cyclop // complexity is only slightly above the usual level, may be tolerated as it mimicks the stdlib. func ParseDuration(s string) (time.Duration, error) { // NOTE: this code is largely inspired by the standard library. orig := s var d uint64 neg := false // Consume [-+]? if s != "" { c := s[0] if c == '-' || c == '+' { neg = c == '-' s = s[1:] } } // Consume space s = strings.TrimLeftFunc(s, unicode.IsSpace) // Special case: if all that is left is "0", this is zero. if s == "0" { return 0, nil } if s == "" { return 0, parseDurationError(orig, "empty duration") } for s != "" { var ( v, f uint64 // integers before, after decimal point scale float64 = 1 // value = v + f/scale ) s = strings.TrimLeftFunc(s, unicode.IsSpace) // The next character must be 0-9.] if s[0] != '.' && ('0' > s[0] || s[0] > '9') { return 0, parseDurationError(orig, fmt.Sprintf("expected a numerical value, but got %q", s[0])) } // Consume integer part [0-9]* pl := len(s) var ok bool v, s, ok = leadingInt(s) if !ok { return 0, parseDurationError(orig, "expected a leading integer part") } pre := pl != len(s) // whether we consumed anything before a period // Consume fractional part (\.[0-9]*)? post := false if s != "" && s[0] == '.' { s = s[1:] pl := len(s) f, scale, s = leadingFraction(s) post = pl != len(s) } if !pre && !post { // no digits (e.g. ".s" or "-.s") return 0, parseDurationError(orig, "expected digits") } // Consume space. s = strings.TrimLeftFunc(s, unicode.IsSpace) // Consume unit. i := 0 for ; i < len(s); i++ { c := s[i] if c == '.' || '0' <= c && c <= '9' || unicode.IsSpace(rune(c)) { break } } if i == 0 { return 0, parseDurationError(orig, "missing unit in duration") } u := s[:i] s = s[i:] unit, ok := timeMultiplier[u] if !ok { return 0, parseDurationError(orig, fmt.Sprintf("unknown unit %q in duration", u)) } if v > maxUint64/unit { // overflow return 0, parseDurationError(orig, "numerical overflow") } v *= unit if f > 0 { // float64 is needed to be nanosecond accurate for fractions of hours. // v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit) v += uint64(float64(f) * (float64(unit) / scale)) if v > maxUint64 { // overflow return 0, parseDurationError(orig, "numerical overflow") } } d += v if d > maxUint64 { return 0, parseDurationError(orig, "numerical overflow") } } if neg { return -time.Duration(d), nil } if d > maxUint64-1 { return 0, parseDurationError(orig, "numerical overflow") } return time.Duration(d), nil } // Scan reads a Duration value from database driver type. func (d *Duration) Scan(raw any) error { switch v := raw.(type) { // Proposal for enhancement: case []byte: // ? case int64: *d = Duration(v) case float64: *d = Duration(int64(v)) case nil: *d = Duration(0) default: return fmt.Errorf("cannot sql.Scan() strfmt.Duration from: %#v: %w", v, ErrFormat) } return nil } // Value converts Duration to a primitive value ready to be written to a database. func (d Duration) Value() (driver.Value, error) { return driver.Value(int64(d)), nil } // String converts this duration to a string. func (d Duration) String() string { return time.Duration(d).String() } // MarshalJSON returns the Duration as JSON. func (d Duration) MarshalJSON() ([]byte, error) { return json.Marshal(time.Duration(d).String()) } // UnmarshalJSON sets the Duration from JSON. func (d *Duration) UnmarshalJSON(data []byte) error { if string(data) == jsonNull { return nil } var dstr string if err := json.Unmarshal(data, &dstr); err != nil { return err } tt, err := ParseDuration(dstr) if err != nil { return err } *d = Duration(tt) return nil } // DeepCopyInto copies the receiver and writes its value into out. func (d *Duration) DeepCopyInto(out *Duration) { *out = *d } // DeepCopy copies the receiver into a new Duration. func (d *Duration) DeepCopy() *Duration { if d == nil { return nil } out := new(Duration) d.DeepCopyInto(out) return out } func parseDurationError(s, msg string) error { if msg == "" { return fmt.Errorf("invalid duration: %s: %w", s, ErrFormat) } return fmt.Errorf("invalid duration: %s: %s: %w", s, msg, ErrFormat) } // leadingInt consumes the leading [0-9]* from s. func leadingInt[bytes []byte | string](s bytes) (x uint64, rem bytes, ok bool) { //nolint:ireturn // false positive i := 0 for ; i < len(s); i++ { c := s[i] if c < '0' || c > '9' { break } if x > maxUint64/10 { // overflow return 0, rem, false } x = x*10 + uint64(c) - '0' if x > maxUint64 { // overflow return 0, rem, false } } return x, s[i:], true } // leadingFraction consumes the leading [0-9]* from s. // // // It is used only for fractions, so it does not return an error on overflow, // it just stops accumulating precision. func leadingFraction(s string) (x uint64, scale float64, rem string) { i := 0 scale = 1 overflow := false for ; i < len(s); i++ { c := s[i] if c < '0' || c > '9' { break } if overflow { continue } if x > (maxUint64-1)/10 { // It's possible for overflow to give a positive number, so take care. overflow = true continue } y := x*10 + uint64(c) - '0' if y > maxUint64 { overflow = true continue } x = y scale *= 10 } return x, scale, s[i:] } go-openapi-strfmt-879971e/duration_test.go000066400000000000000000000262001517436663600206200ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package strfmt import ( "fmt" "testing" "time" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" ) func TestDuration(t *testing.T) { pp := Duration(0) err := pp.UnmarshalText([]byte("0ms")) require.NoError(t, err) err = pp.UnmarshalText([]byte("yada")) require.Error(t, err) orig := "2ms" b := []byte(orig) bj := []byte("\"" + orig + "\"") err = pp.UnmarshalText(b) require.NoError(t, err) err = pp.UnmarshalText([]byte("three week")) require.Error(t, err) err = pp.UnmarshalText([]byte("9999999999999999999999999999999999999999999999999999999 weeks")) require.Error(t, err) txt, err := pp.MarshalText() require.NoError(t, err) assert.EqualT(t, orig, string(txt)) err = pp.UnmarshalJSON(bj) require.NoError(t, err) assert.EqualT(t, orig, pp.String()) err = pp.UnmarshalJSON([]byte("yada")) require.Error(t, err) err = pp.UnmarshalJSON([]byte(`"12 parsecs"`)) require.Error(t, err) err = pp.UnmarshalJSON([]byte(`"12 y"`)) require.Error(t, err) assert.JSONMarshalAsT(t, string(bj), pp) } func testDurationParser(t *testing.T, toParse string, expected time.Duration) { t.Helper() r, e := ParseDuration(toParse) require.NoError(t, e) assert.EqualT(t, expected, r) } func TestDurationParser_Failed(t *testing.T) { _, e := ParseDuration("45 wekk") require.Error(t, e) } func TestIsDuration_Failed(t *testing.T) { e := IsDuration("45 weeekks") assert.FalseT(t, e) } func testDurationSQLScanner(t *testing.T, dur time.Duration) { t.Helper() values := []any{int64(dur), float64(dur)} for _, value := range values { var result Duration err := result.Scan(value) require.NoError(t, err) assert.EqualT(t, dur, time.Duration(result)) // And the other way around resv, erv := result.Value() require.NoError(t, erv) assert.EqualValues(t, value, resv) } } func TestDurationScanner_Nil(t *testing.T) { var result Duration err := result.Scan(nil) require.NoError(t, err) assert.EqualValues(t, 0, time.Duration(result)) err = result.Scan("1 ms") require.Error(t, err) } func TestDurationParser(t *testing.T) { testcases := map[string]time.Duration{ // parse the short forms without spaces "1ns": 1 * time.Nanosecond, "1us": 1 * time.Microsecond, "1µs": 1 * time.Microsecond, "1ms": 1 * time.Millisecond, "1s": 1 * time.Second, "1m": 1 * time.Minute, "1h": 1 * time.Hour, "1hr": 1 * time.Hour, "1d": 24 * time.Hour, "1w": 7 * 24 * time.Hour, "1wk": 7 * 24 * time.Hour, // parse the long forms without spaces "1nanoseconds": 1 * time.Nanosecond, "1nanos": 1 * time.Nanosecond, "1microseconds": 1 * time.Microsecond, "1micros": 1 * time.Microsecond, "1millis": 1 * time.Millisecond, "1milliseconds": 1 * time.Millisecond, "1second": 1 * time.Second, "1sec": 1 * time.Second, "1min": 1 * time.Minute, "1minute": 1 * time.Minute, "1hour": 1 * time.Hour, "1day": 24 * time.Hour, "1week": 7 * 24 * time.Hour, // parse the short forms with spaces "1 ns": 1 * time.Nanosecond, "1 us": 1 * time.Microsecond, "1 µs": 1 * time.Microsecond, "1 ms": 1 * time.Millisecond, "1 s": 1 * time.Second, "1 m": 1 * time.Minute, "1 h": 1 * time.Hour, "1 hr": 1 * time.Hour, "1 d": 24 * time.Hour, "1 w": 7 * 24 * time.Hour, "1 wk": 7 * 24 * time.Hour, // parse the long forms without spaces "1 nanoseconds": 1 * time.Nanosecond, "1 nanos": 1 * time.Nanosecond, "1 microseconds": 1 * time.Microsecond, "1 micros": 1 * time.Microsecond, "1 millis": 1 * time.Millisecond, "1 milliseconds": 1 * time.Millisecond, "1 second": 1 * time.Second, "1 sec": 1 * time.Second, "1 min": 1 * time.Minute, "1 minute": 1 * time.Minute, "1 hour": 1 * time.Hour, "1 day": 24 * time.Hour, "1 week": 7 * 24 * time.Hour, // parse composite forms "1m45s": time.Minute + 45*time.Second, "1 m45 s": time.Minute + 45*time.Second, "1m 45s": time.Minute + 45*time.Second, "1 minute 45 seconds": time.Minute + 45*time.Second, } for str, dur := range testcases { t.Run(str, func(t *testing.T) { testDurationParser(t, str, dur) // negative duration testDurationParser(t, "-"+str, -dur) testDurationParser(t, "- "+str, -dur) testDurationSQLScanner(t, dur) }) } } // TestDurationParser_EdgeCases covers ParseDuration branches that the happy-path // tests don't exercise: the "0" shortcut, empty input left after a sign or // spaces, and inputs that have a decimal point but no digit on either side. func TestDurationParser_EdgeCases(t *testing.T) { t.Run("zero shortcut returns 0 with no error", func(t *testing.T) { for _, in := range []string{"0", "-0", "+0", "- 0", "+ 0"} { input := in t.Run(fmt.Sprintf("%q", input), func(t *testing.T) { t.Parallel() d, err := ParseDuration(input) require.NoError(t, err) assert.EqualT(t, time.Duration(0), d) }) } }) t.Run("empty payload after sign or spaces is rejected", func(t *testing.T) { for _, in := range []string{"", "-", "+", " ", "- ", "+ "} { input := in t.Run(fmt.Sprintf("%q", input), func(t *testing.T) { t.Parallel() _, err := ParseDuration(input) require.Error(t, err) }) } }) t.Run("decimal point without digits is rejected", func(t *testing.T) { // A leading '.' passes the first numeric check but produces neither an // integer nor a fractional part, exercising the "I dare you" branch. for _, in := range []string{".s", ".h", ".d", "-.s", "+.h", "1m .s"} { input := in t.Run(input, func(t *testing.T) { t.Parallel() _, err := ParseDuration(input) require.Error(t, err) }) } }) } // TestDurationParser_Overflow covers every numerical-overflow branch in // ParseDuration, leadingInt, and leadingFraction. // // The boundary values are derived from maxUint64 = 1<<63 (the magnitude of // math.MinInt64). The fractional cases hinge on (1<<63 - 1)/10 = 922337203685477580. func TestDurationParser_Overflow(t *testing.T) { overflows := []struct { name string input string }{ { name: "leadingInt overflow after multiply-add", // 19 digits where the last one pushes x past 1<<63 even though // x <= maxUint64/10 still held before the final multiply-add. input: "9223372036854775809ns", }, { name: "v*unit fits but adding fraction overflows", // 2562047*hours = 9223369200000000000 (under 1<<63), then // +0.9*hours = +3240000000000 pushes the sum past 1<<63. input: "2562047.9h", }, { name: "running total d exceeds maxUint64 across tokens", // Each token alone fits (9e18 < 1<<63 ~= 9.223e18), but the sum // (1.8e19) overflows in the in-loop d > maxUint64 check. input: "9000000000000ms 9000000000000ms", }, { name: "single positive value equals maxUint64", // 1<<63 ns parses through leadingInt successfully, then fails the // final d > maxUint64-1 check (only the negative form fits, as // time.Duration(math.MinInt64)). input: "9223372036854775808ns", }, } for _, tt := range overflows { tc := tt t.Run(tc.name, func(t *testing.T) { t.Parallel() d, err := ParseDuration(tc.input) require.Errorf(t, err, "parsed %q as %v, expected overflow error", tc.input, d) }) } } // TestDurationParser_FractionPrecisionLoss covers the three overflow-handling // branches in leadingFraction. Unlike integer overflow, fractional overflow is // not an error: per the helper's contract it stops accumulating precision and // returns the partial value, so the parser still produces a valid duration — // just truncated near the 18th fractional digit. func TestDurationParser_FractionPrecisionLoss(t *testing.T) { // All three inputs share the same first 18 fractional digits; precision // caps there regardless of which overflow branch fires, so the parsed // value is identical. const truncated = time.Duration(922337203) // 0.922337203 * 1s, rounded down cases := []struct { name string input string }{ { name: "y overflow on 19th digit", // 19th digit '9' makes y = x*10+9 > 1<<63. input: "0.9223372036854775809s", }, { name: "x exceeds threshold on 20th digit", // 19th digit '8' lands y exactly at 1<<63 (still accepted), // 20th digit then trips the x > (1<<63-1)/10 guard. input: "0.92233720368547758089s", }, { name: "trailing digits skipped once overflow is set", // 21st digit hits the early `if overflow` continue branch. input: "0.922337203685477580891s", }, } for _, tt := range cases { tc := tt t.Run(tc.name, func(t *testing.T) { t.Parallel() d, err := ParseDuration(tc.input) require.NoError(t, err) assert.EqualT(t, truncated, d) }) } } // TestDurationParser_NegativeMinDuration verifies that the smallest // representable duration parses successfully even though the equivalent // positive value overflows. func TestDurationParser_NegativeMinDuration(t *testing.T) { d, err := ParseDuration("-9223372036854775808ns") require.NoError(t, err) assert.EqualT(t, time.Duration(-1<<63), d) } func TestIsDuration_Caveats(t *testing.T) { // This works too e := IsDuration("45 weeks") assert.TrueT(t, e) // This no longer works e = IsDuration("45 weekz") assert.FalseT(t, e) // This works too e = IsDuration("12 hours") assert.TrueT(t, e) // This works too e = IsDuration("12 minutes") assert.TrueT(t, e) // This does not work e = IsDuration("12 phours") assert.FalseT(t, e) } func TestDeepCopyDuration(t *testing.T) { dur := Duration(42) in := &dur out := new(Duration) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *Duration out3 := inNil.DeepCopy() assert.Nil(t, out3) } func TestIssue169FractionalDuration(t *testing.T) { for _, tt := range []struct { Input string Expected string ExpectError bool }{ { Input: "1.5 h", Expected: "1h30m0s", }, { Input: "1.5 d", Expected: "36h0m0s", }, { Input: "3.14159 d", Expected: "75h23m53.376s", }, { Input: "- 3.14159 d", Expected: "-75h23m53.376s", }, { Input: "3.141.59 d", ExpectError: true, }, { Input: ".314159 d", Expected: "7h32m23.3376s", }, { Input: "314159. d", ExpectError: true, }, } { fractionalDuration := tt if fractionalDuration.ExpectError { t.Run(fmt.Sprintf("invalid fractional duration %s should NOT parse", fractionalDuration.Input), func(t *testing.T) { t.Parallel() require.FalseT(t, IsDuration(fractionalDuration.Input)) }) continue } t.Run(fmt.Sprintf("fractional duration %s should parse", fractionalDuration.Input), func(t *testing.T) { t.Parallel() require.TrueT(t, IsDuration(fractionalDuration.Input)) var d Duration require.NoError(t, d.UnmarshalText([]byte(fractionalDuration.Input))) require.EqualT(t, fractionalDuration.Expected, d.String()) dd, err := ParseDuration(fractionalDuration.Input) require.NoError(t, err) require.EqualT(t, fractionalDuration.Expected, dd.String()) }) } } go-openapi-strfmt-879971e/enable/000077500000000000000000000000001517436663600166335ustar00rootroot00000000000000go-openapi-strfmt-879971e/enable/mongodb/000077500000000000000000000000001517436663600202605ustar00rootroot00000000000000go-openapi-strfmt-879971e/enable/mongodb/go.mod000066400000000000000000000003071517436663600213660ustar00rootroot00000000000000module github.com/go-openapi/strfmt/enable/mongodb go 1.25.0 require ( github.com/go-openapi/strfmt v0.26.2 go.mongodb.org/mongo-driver/v2 v2.5.1 ) replace github.com/go-openapi/strfmt => ../.. go-openapi-strfmt-879971e/enable/mongodb/go.sum000066400000000000000000000010051517436663600214070ustar00rootroot00000000000000github.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.1 h1:j2U/Qp+wvueSpqitLCSZPT/+ZpVc1xzuwdHWwl7d8ro= go.mongodb.org/mongo-driver/v2 v2.5.1/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= go-openapi-strfmt-879971e/enable/mongodb/mongodb.go000066400000000000000000000025001517436663600222310ustar00rootroot00000000000000// 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 } } go-openapi-strfmt-879971e/errors.go000066400000000000000000000004701517436663600172510ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package strfmt type strfmtError string // ErrFormat is an error raised by the [strfmt] package. const ErrFormat strfmtError = "format error" func (e strfmtError) Error() string { return string(e) } go-openapi-strfmt-879971e/format.go000066400000000000000000000162621517436663600172330ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package strfmt import ( "encoding" "fmt" "reflect" "slices" "strings" "sync" "time" "github.com/go-openapi/errors" "github.com/go-viper/mapstructure/v2" ) // Default is the default formats registry. var Default = NewSeededFormats(nil, nil) //nolint:gochecknoglobals // package-level default registry, by design // Validator represents a validator for a string format. type Validator func(string) bool // NewFormats creates a new formats registry seeded with the values from the default. func NewFormats() Registry { //nolint:ireturn // factory function returns the Registry interface by design //nolint:forcetypeassert return NewSeededFormats(Default.(*defaultFormats).data, nil) } // NewSeededFormats creates a new formats registry. func NewSeededFormats(seeds []knownFormat, normalizer NameNormalizer) Registry { //nolint:ireturn // factory function returns the Registry interface by design if normalizer == nil { normalizer = DefaultNameNormalizer } // copy here, don't modify the original return &defaultFormats{ data: slices.Clone(seeds), normalizeName: normalizer, } } type knownFormat struct { Name string OrigName string Type reflect.Type Validator Validator } // NameNormalizer is a function that normalizes a format name. type NameNormalizer func(string) string // DefaultNameNormalizer removes all dashes. func DefaultNameNormalizer(name string) string { return strings.ReplaceAll(name, "-", "") } type defaultFormats struct { sync.Mutex data []knownFormat normalizeName NameNormalizer } // MapStructureHookFunc is a decode hook function for mapstructure. func (f *defaultFormats) MapStructureHookFunc() mapstructure.DecodeHookFunc { //nolint:ireturn // returns interface required by mapstructure return func(from reflect.Type, to reflect.Type, obj any) (any, error) { if from.Kind() != reflect.String { return obj, nil } data, ok := obj.(string) if !ok { return nil, fmt.Errorf("failed to cast %+v to string: %w", obj, ErrFormat) } for _, v := range f.data { tpe, _ := f.GetType(v.Name) if to == tpe { return decodeFormatFromString(v.Name, data) } } return data, nil } } // decodeFormatFromString decodes a string into the appropriate format type by name. func decodeFormatFromString(name, data string) (any, error) { //nolint:gocyclo,cyclop // flat switch over format names, no real complexity switch name { case "date": d, err := time.ParseInLocation(RFC3339FullDate, data, DefaultTimeLocation) if err != nil { return nil, err } return Date(d), nil case "datetime": if len(data) == 0 { return nil, fmt.Errorf("empty string is an invalid datetime format: %w", ErrFormat) } return ParseDateTime(data) case "duration": dur, err := ParseDuration(data) if err != nil { return nil, err } return Duration(dur), nil case "uri": return URI(data), nil case "email": return Email(data), nil case "uuid": return UUID(data), nil case "uuid3": return UUID3(data), nil case "uuid4": return UUID4(data), nil case "uuid5": return UUID5(data), nil case "uuid7": return UUID7(data), nil case "hostname": return Hostname(data), nil case "ipv4": return IPv4(data), nil case "ipv6": return IPv6(data), nil case "cidr": return CIDR(data), nil case "mac": return MAC(data), nil case "isbn": return ISBN(data), nil case "isbn10": return ISBN10(data), nil case "isbn13": return ISBN13(data), nil case "creditcard": return CreditCard(data), nil case "ssn": return SSN(data), nil case "hexcolor": return HexColor(data), nil case "rgbcolor": return RGBColor(data), nil case "byte": return Base64(data), nil case "password": return Password(data), nil case "ulid": ulid, err := ParseULID(data) if err != nil { return nil, err } return ulid, nil default: return nil, errors.InvalidTypeName(name) } } // Add adds a new format, return true if this was a new item instead of a replacement. func (f *defaultFormats) Add(name string, strfmt Format, validator Validator) bool { f.Lock() defer f.Unlock() nme := f.normalizeName(name) tpe := reflect.TypeOf(strfmt) if tpe.Kind() == reflect.Ptr { tpe = tpe.Elem() } for i := range f.data { v := &f.data[i] if v.Name == nme { v.Type = tpe v.Validator = validator return false } } // turns out it's new after all f.data = append(f.data, knownFormat{Name: nme, OrigName: name, Type: tpe, Validator: validator}) return true } // GetType gets the type for the specified name. func (f *defaultFormats) GetType(name string) (reflect.Type, bool) { f.Lock() defer f.Unlock() nme := f.normalizeName(name) for _, v := range f.data { if v.Name == nme { return v.Type, true } } return nil, false } // DelByName removes the format by the specified name, returns true when an item was actually removed. func (f *defaultFormats) DelByName(name string) bool { f.Lock() defer f.Unlock() nme := f.normalizeName(name) for i, v := range f.data { if v.Name == nme { f.data[i] = knownFormat{} // release f.data = append(f.data[:i], f.data[i+1:]...) return true } } return false } // DelByFormat removes the specified format, returns true when an item was actually removed. func (f *defaultFormats) DelByFormat(strfmt Format) bool { f.Lock() defer f.Unlock() tpe := reflect.TypeOf(strfmt) if tpe.Kind() == reflect.Ptr { tpe = tpe.Elem() } for i, v := range f.data { if v.Type == tpe { f.data[i] = knownFormat{} // release f.data = append(f.data[:i], f.data[i+1:]...) return true } } return false } // ContainsName returns true if this registry contains the specified name. func (f *defaultFormats) ContainsName(name string) bool { f.Lock() defer f.Unlock() nme := f.normalizeName(name) for _, v := range f.data { if v.Name == nme { return true } } return false } // ContainsFormat returns true if this registry contains the specified format. func (f *defaultFormats) ContainsFormat(strfmt Format) bool { f.Lock() defer f.Unlock() tpe := reflect.TypeOf(strfmt) if tpe.Kind() == reflect.Ptr { tpe = tpe.Elem() } for _, v := range f.data { if v.Type == tpe { return true } } return false } // Validates passed data against format. // // Note that the format name is automatically normalized, e.g. one may // use "date-time" to use the "datetime" format validator. func (f *defaultFormats) Validates(name, data string) bool { f.Lock() defer f.Unlock() nme := f.normalizeName(name) for _, v := range f.data { if v.Name == nme { return v.Validator(data) } } return false } // Parse a string into the appropriate format representation type. // // E.g. parsing a string a "date" will return a Date type. func (f *defaultFormats) Parse(name, data string) (any, error) { f.Lock() defer f.Unlock() nme := f.normalizeName(name) for _, v := range f.data { if v.Name == nme { nw := reflect.New(v.Type).Interface() if dec, ok := nw.(encoding.TextUnmarshaler); ok { if err := dec.UnmarshalText([]byte(data)); err != nil { return nil, err } return nw, nil } return nil, errors.InvalidTypeName(name) } } return nil, errors.InvalidTypeName(name) } go-openapi-strfmt-879971e/format_test.go000066400000000000000000000201211517436663600202570ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package strfmt import ( "strings" "testing" "time" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" "github.com/go-viper/mapstructure/v2" ) type testFormat string func (t testFormat) MarshalText() ([]byte, error) { return []byte(string(t)), nil } func (t *testFormat) UnmarshalText(b []byte) error { *t = testFormat(string(b)) return nil } func (t testFormat) String() string { return string(t) } func isTestFormat(s string) bool { return strings.HasPrefix(s, "tf") } type tf2 string func (t tf2) MarshalText() ([]byte, error) { return []byte(string(t)), nil } func (t *tf2) UnmarshalText(b []byte) error { *t = tf2(string(b)) return nil } func istf2(s string) bool { return strings.HasPrefix(s, "af") } func (t tf2) String() string { return string(t) } type bf string func (t bf) MarshalText() ([]byte, error) { return []byte(string(t)), nil } func (t *bf) UnmarshalText(b []byte) error { *t = bf(string(b)) return nil } func (t bf) String() string { return string(t) } func isbf(s string) bool { return strings.HasPrefix(s, "bf") } func istf3(s string) bool { return strings.HasPrefix(s, "ff") } func init() { //nolint:gochecknoinits // registers test format in the default registry for testing tf := testFormat("") Default.Add("test-format", &tf, isTestFormat) } func TestFormatRegistry(t *testing.T) { f2 := tf2("") f3 := bf("") registry := NewFormats() assert.TrueT(t, registry.ContainsName("test-format")) assert.TrueT(t, registry.ContainsName("testformat")) assert.FalseT(t, registry.ContainsName("ttt")) assert.TrueT(t, registry.Validates("testformat", "tfa")) assert.FalseT(t, registry.Validates("testformat", "ffa")) assert.TrueT(t, registry.Add("tf2", &f2, istf2)) assert.TrueT(t, registry.ContainsName("tf2")) assert.FalseT(t, registry.ContainsName("tfw")) assert.TrueT(t, registry.Validates("tf2", "afa")) assert.FalseT(t, registry.Add("tf2", &f3, isbf)) assert.TrueT(t, registry.ContainsName("tf2")) assert.FalseT(t, registry.ContainsName("tfw")) assert.TrueT(t, registry.Validates("tf2", "bfa")) assert.FalseT(t, registry.Validates("tf2", "afa")) assert.FalseT(t, registry.Add("tf2", &f2, istf2)) assert.TrueT(t, registry.Add("tf3", &f2, istf3)) assert.TrueT(t, registry.ContainsName("tf3")) assert.TrueT(t, registry.ContainsName("tf2")) assert.FalseT(t, registry.ContainsName("tfw")) assert.TrueT(t, registry.Validates("tf3", "ffa")) assert.TrueT(t, registry.DelByName("tf3")) assert.TrueT(t, registry.Add("tf3", &f2, istf3)) assert.TrueT(t, registry.DelByName("tf3")) assert.FalseT(t, registry.DelByName("unknown")) assert.FalseT(t, registry.Validates("unknown", "")) } type testStruct struct { D Date `json:"d"` DT DateTime `json:"dt"` Dur Duration `json:"dur,omitempty"` URI URI `json:"uri,omitempty"` Eml Email `json:"eml,omitempty"` UUID UUID `json:"uuid,omitempty"` UUID3 UUID3 `json:"uuid3,omitempty"` UUID4 UUID4 `json:"uuid4,omitempty"` UUID5 UUID5 `json:"uuid5,omitempty"` UUID7 UUID7 `json:"uuid7,omitempty"` Hn Hostname `json:"hn,omitempty"` Ipv4 IPv4 `json:"ipv4,omitempty"` Ipv6 IPv6 `json:"ipv6,omitempty"` Cidr CIDR `json:"cidr,omitempty"` Mac MAC `json:"mac,omitempty"` Isbn ISBN `json:"isbn,omitempty"` Isbn10 ISBN10 `json:"isbn10,omitempty"` Isbn13 ISBN13 `json:"isbn13,omitempty"` Creditcard CreditCard `json:"creditcard,omitempty"` Ssn SSN `json:"ssn,omitempty"` Hexcolor HexColor `json:"hexcolor,omitempty"` Rgbcolor RGBColor `json:"rgbcolor,omitempty"` B64 Base64 `json:"b64,omitempty"` Pw Password `json:"pw,omitempty"` ULID ULID `json:"ulid"` } func TestDecodeHook(t *testing.T) { registry := NewFormats() m := map[string]any{ "d": "2014-12-15", "dt": "2012-03-02T15:06:05.999999999Z", "dur": "5s", "uri": "http://www.dummy.com", "eml": "dummy@dummy.com", "uuid": "a8098c1a-f86e-11da-bd1a-00112444be1e", "uuid3": "bcd02e22-68f0-3046-a512-327cca9def8f", "uuid4": "025b0d74-00a2-4048-bf57-227c5111bb34", "uuid5": "886313e1-3b8a-5372-9b90-0c9aee199e5d", "uuid7": "019a15e6-cd5e-7204-b11b-12075f4c8a25", "hn": "somewhere.com", "ipv4": "192.168.254.1", "ipv6": "::1", "cidr": "192.0.2.1/24", "mac": "01:02:03:04:05:06", "isbn": "0321751043", "isbn10": "0321751043", "isbn13": "978-0321751041", "hexcolor": "#FFFFFF", "rgbcolor": "rgb(255,255,255)", "pw": "super secret stuff here", "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), DT: dt, Dur: Duration(dur), URI: URI("http://www.dummy.com"), Eml: Email("dummy@dummy.com"), UUID: UUID("a8098c1a-f86e-11da-bd1a-00112444be1e"), UUID3: UUID3("bcd02e22-68f0-3046-a512-327cca9def8f"), UUID4: UUID4("025b0d74-00a2-4048-bf57-227c5111bb34"), UUID5: UUID5("886313e1-3b8a-5372-9b90-0c9aee199e5d"), UUID7: UUID7("019a15e6-cd5e-7204-b11b-12075f4c8a25"), Hn: Hostname("somewhere.com"), Ipv4: IPv4("192.168.254.1"), Ipv6: IPv6("::1"), Cidr: CIDR("192.0.2.1/24"), Mac: MAC("01:02:03:04:05:06"), Isbn: ISBN("0321751043"), Isbn10: ISBN10("0321751043"), Isbn13: ISBN13("978-0321751041"), Creditcard: CreditCard("4111-1111-1111-1111"), Ssn: SSN("111-11-1111"), Hexcolor: HexColor("#FFFFFF"), Rgbcolor: RGBColor("rgb(255,255,255)"), B64: Base64("ZWxpemFiZXRocG9zZXk="), Pw: Password("super secret stuff here"), ULID: ulid, } test := new(testStruct) cfg := &mapstructure.DecoderConfig{ DecodeHook: registry.MapStructureHookFunc(), // weakly typed will pass if this passes WeaklyTypedInput: false, Result: test, } d, err := mapstructure.NewDecoder(cfg) require.NoError(t, err) err = d.Decode(m) require.NoError(t, err) assert.Equal(t, exp, test) } func TestDecodeDateTimeHook(t *testing.T) { testCases := []struct { Name string Input string }{ { "empty datetime", "", }, { "invalid non empty datetime", "2019-01-01abc", }, } registry := NewFormats() type layout struct { DateTime *DateTime `json:"datetime,omitempty"` } for i := range testCases { tc := testCases[i] t.Run(tc.Name, func(t *testing.T) { test := new(layout) cfg := &mapstructure.DecoderConfig{ DecodeHook: registry.MapStructureHookFunc(), WeaklyTypedInput: false, Result: test, } d, err := mapstructure.NewDecoder(cfg) require.NoError(t, err) input := make(map[string]any) input["datetime"] = tc.Input err = d.Decode(input) require.Error(t, err, "error expected got none") }) } } 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) require.NoError(t, err) input := make(map[string]any) input["ulid"] = tc.Input err = d.Decode(input) require.Error(t, err, "error expected got none") }) } } go-openapi-strfmt-879971e/go.mod000066400000000000000000000005001517436663600165060ustar00rootroot00000000000000module github.com/go-openapi/strfmt go 1.25.0 require ( github.com/go-openapi/errors v0.22.7 github.com/go-openapi/testify/v2 v2.4.2 github.com/go-viper/mapstructure/v2 v2.5.0 github.com/google/uuid v1.6.0 github.com/oklog/ulid/v2 v2.1.1 golang.org/x/net v0.53.0 ) require golang.org/x/text v0.36.0 // indirect go-openapi-strfmt-879971e/go.sum000066400000000000000000000024271517436663600165450ustar00rootroot00000000000000github.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.2 h1:tiByHpvE9uHrrKjOszax7ZvKB7QOgizBWGBLuq0ePx4= github.com/go-openapi/testify/v2 v2.4.2/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= 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/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= golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= go-openapi-strfmt-879971e/go.work000066400000000000000000000001041517436663600167110ustar00rootroot00000000000000use ( . ./enable/mongodb ./internal/testintegration ) go 1.25.0 go-openapi-strfmt-879971e/ifaces.go000066400000000000000000000015621517436663600171720ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package strfmt import ( "encoding" "reflect" "github.com/go-viper/mapstructure/v2" ) // Format represents a string format. // // All implementations of Format provide a string representation and text // marshaling/unmarshaling interface to be used by encoders (e.g. encoding/[json]). type Format interface { String() string encoding.TextMarshaler encoding.TextUnmarshaler } // Registry is a registry of string formats, with a validation method. type Registry interface { Add(name string, strfmt Format, validator Validator) bool DelByName(name string) bool GetType(name string) (reflect.Type, bool) ContainsName(name string) bool Validates(name, data string) bool Parse(name, data string) (any, error) MapStructureHookFunc() mapstructure.DecodeHookFunc } go-openapi-strfmt-879971e/internal/000077500000000000000000000000001517436663600172215ustar00rootroot00000000000000go-openapi-strfmt-879971e/internal/bsonlite/000077500000000000000000000000001517436663600210405ustar00rootroot00000000000000go-openapi-strfmt-879971e/internal/bsonlite/codec.go000066400000000000000000000045031517436663600224460ustar00rootroot00000000000000// 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) } go-openapi-strfmt-879971e/internal/bsonlite/lite.go000066400000000000000000000120221517436663600223210ustar00rootroot00000000000000// 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) } } go-openapi-strfmt-879971e/internal/testintegration/000077500000000000000000000000001517436663600224445ustar00rootroot00000000000000go-openapi-strfmt-879971e/internal/testintegration/doc.go000066400000000000000000000004311517436663600235360ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 // Package testintegration comes as an independent internal module // to test the serialization of format types against various database backends. package testintegration go-openapi-strfmt-879971e/internal/testintegration/go.mod000066400000000000000000000024141517436663600235530ustar00rootroot00000000000000module github.com/go-openapi/strfmt/internal/testintegration go 1.25.0 require ( github.com/go-openapi/strfmt v0.26.2 github.com/go-openapi/strfmt/enable/mongodb v0.25.0 github.com/go-openapi/testify/v2 v2.4.2 github.com/go-sql-driver/mysql v1.9.3 github.com/jackc/pgx/v5 v5.9.2 go.mongodb.org/mongo-driver/v2 v2.5.1 ) require ( filippo.io/edwards25519 v1.1.1 // indirect github.com/go-openapi/errors v0.22.7 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/klauspost/compress v1.17.6 // indirect github.com/oklog/ulid/v2 v2.1.1 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.2.0 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect golang.org/x/crypto v0.50.0 // indirect golang.org/x/net v0.53.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/text v0.36.0 // indirect ) replace ( github.com/go-openapi/strfmt => ../.. github.com/go-openapi/strfmt/enable/mongodb => ../../enable/mongodb ) go-openapi-strfmt-879971e/internal/testintegration/go.sum000066400000000000000000000167351517436663600236130ustar00rootroot00000000000000filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw= filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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.2 h1:tiByHpvE9uHrrKjOszax7ZvKB7QOgizBWGBLuq0ePx4= github.com/go-openapi/testify/v2 v2.4.2/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= 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/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= 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= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs= github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8= github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver/v2 v2.5.1 h1:j2U/Qp+wvueSpqitLCSZPT/+ZpVc1xzuwdHWwl7d8ro= go.mongodb.org/mongo-driver/v2 v2.5.1/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= go-openapi-strfmt-879971e/internal/testintegration/mariadb/000077500000000000000000000000001517436663600240435ustar00rootroot00000000000000go-openapi-strfmt-879971e/internal/testintegration/mariadb/mariadb_test.go000066400000000000000000000312111517436663600270260ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 //go:build testintegration // Package mariadb_test lives in its own sub-package so that tests which modify // strfmt globals (e.g. MarshalFormat, NormalizeTimeForMarshal) cannot interfere // with MongoDB integration tests running in a sibling package. package mariadb_test import ( "context" "database/sql" "encoding/base64" "fmt" "os" "strings" "testing" "time" "github.com/go-openapi/strfmt" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" _ "github.com/go-sql-driver/mysql" ) func mariadbDSN() string { if dsn := os.Getenv("MARIADB_DSN"); dsn != "" { return dsn } return "strfmt_test:strfmt_test@tcp(localhost:3306)/strfmt_integration_test?parseTime=true&loc=UTC" } func setupMariaDB(t *testing.T) *sql.DB { t.Helper() db, err := sql.Open("mysql", mariadbDSN()) require.NoError(t, err) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() require.NoError(t, db.PingContext(ctx)) t.Cleanup(func() { _ = db.Close() }) return db } // createTable creates a test table and registers cleanup. // cols is a list of "col_name TYPE" definitions. func createTable(t *testing.T, db *sql.DB, cols ...string) string { t.Helper() ctx := context.Background() table := "test_" + strings.ReplaceAll(t.Name(), "/", "_") ddl := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (id VARCHAR(64) PRIMARY KEY, %s)", table, strings.Join(cols, ", ")) _, err := db.ExecContext(ctx, ddl) require.NoError(t, err) t.Cleanup(func() { _, _ = db.ExecContext(context.Background(), "DROP TABLE IF EXISTS "+table) }) return table } // MariaDB time tests: these are the problematic types per issue #174. // The go-sql-driver/mysql has hard-coded handling for time.Time but not for // type redefinitions like strfmt.DateTime. The driver.Valuer interface returns // an RFC3339 string with "Z" suffix that MySQL/MariaDB rejects for DATETIME columns. func TestMariaDB_DateTime_AsString(t *testing.T) { // Workaround: store DateTime as VARCHAR, roundtrip via Value()/Scan() with string. db := setupMariaDB(t) ctx := context.Background() table := createTable(t, db, "value VARCHAR(64)") original := strfmt.DateTime(time.Date(2024, 6, 15, 12, 30, 45, 123000000, time.UTC)) // Insert using Value() — returns RFC3339 string, VARCHAR column accepts it. _, err := db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, value) VALUES (?, ?)", table), "dt1", original) require.NoError(t, err) var got strfmt.DateTime err = db.QueryRowContext(ctx, fmt.Sprintf("SELECT value FROM %s WHERE id = ?", table), "dt1").Scan(&got) require.NoError(t, err) assert.EqualT(t, time.Time(original).UTC().Truncate(time.Millisecond), time.Time(got).UTC().Truncate(time.Millisecond), ) } func TestMariaDB_DateTime_NativeDatetime_DefaultFormat(t *testing.T) { // The go-sql-driver/mysql has hard-coded handling for time.Time specifically. // Since strfmt.DateTime is a type redefinition (type DateTime time.Time), the // driver doesn't intercept it and falls through to the driver.Valuer interface. // DateTime.Value() returns an RFC3339 string like "2024-06-15T12:30:45.123Z". // MySQL/MariaDB rejects this format for DATETIME columns because it doesn't // understand the "Z" timezone suffix. // // See: https://github.com/go-openapi/strfmt/issues/174 db := setupMariaDB(t) ctx := context.Background() table := createTable(t, db, "value DATETIME(3)") original := strfmt.DateTime(time.Date(2024, 6, 15, 12, 30, 45, 123000000, time.UTC)) _, err := db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, value) VALUES (?, ?)", table), "dt1", original) require.Error(t, err) t.Logf("confirmed: default MarshalFormat rejected by MariaDB: %v", err) } func TestMariaDB_DateTime_NativeDatetime_LocalTimeFormat(t *testing.T) { // Workaround for MySQL/MariaDB DateTime incompatibility (issue #174): // https://github.com/go-openapi/strfmt/issues/174 // // Setting MarshalFormat to ISO8601LocalTime drops the "Z" timezone suffix, // producing "2024-06-15T12:30:45" which MySQL/MariaDB accepts for DATETIME // columns. NormalizeTimeForMarshal is set to UTC to ensure consistent // timezone normalization before the suffix is stripped. // // Trade-off: ISO8601LocalTime has second-only precision. For sub-second // precision, a custom format like "2006-01-02 15:04:05.000" could be used. // // NOTE: MarshalFormat is a package-level global — changing it affects all // strfmt.DateTime instances in the process. This is why the MariaDB tests // live in a separate sub-package from the MongoDB tests. savedFormat := strfmt.MarshalFormat savedNormalize := strfmt.NormalizeTimeForMarshal t.Cleanup(func() { strfmt.MarshalFormat = savedFormat strfmt.NormalizeTimeForMarshal = savedNormalize }) strfmt.MarshalFormat = strfmt.ISO8601LocalTime strfmt.NormalizeTimeForMarshal = func(t time.Time) time.Time { return t.UTC() } db := setupMariaDB(t) ctx := context.Background() table := createTable(t, db, "value DATETIME") original := strfmt.DateTime(time.Date(2024, 6, 15, 12, 30, 45, 0, time.UTC)) // With ISO8601LocalTime, Value() returns "2024-06-15T12:30:45" — no "Z", accepted by MySQL. _, err := db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, value) VALUES (?, ?)", table), "dt1", original) require.NoError(t, err) // With parseTime=true, the driver returns time.Time — Scan() handles this. var got strfmt.DateTime err = db.QueryRowContext(ctx, fmt.Sprintf("SELECT value FROM %s WHERE id = ?", table), "dt1").Scan(&got) require.NoError(t, err) assert.EqualT(t, time.Time(original).UTC().Truncate(time.Second), time.Time(got).UTC().Truncate(time.Second), ) } func TestMariaDB_Date(t *testing.T) { db := setupMariaDB(t) ctx := context.Background() table := createTable(t, db, "value DATE") original := strfmt.Date(time.Date(2024, 6, 15, 0, 0, 0, 0, time.UTC)) // Date.Value() returns "2024-06-15" which MySQL DATE columns accept. _, err := db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, value) VALUES (?, ?)", table), "d1", original) require.NoError(t, err) var got strfmt.Date err = db.QueryRowContext(ctx, fmt.Sprintf("SELECT value FROM %s WHERE id = ?", table), "d1").Scan(&got) require.NoError(t, err) assert.EqualT(t, original.String(), got.String()) } func TestMariaDB_Duration(t *testing.T) { // Duration.Value() returns int64 (nanoseconds), so use BIGINT column. db := setupMariaDB(t) ctx := context.Background() table := createTable(t, db, "value BIGINT") original := strfmt.Duration(42 * time.Second) _, err := db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, value) VALUES (?, ?)", table), "dur1", original) require.NoError(t, err) var got strfmt.Duration err = db.QueryRowContext(ctx, fmt.Sprintf("SELECT value FROM %s WHERE id = ?", table), "dur1").Scan(&got) require.NoError(t, err) assert.EqualT(t, original, got) } // stringRoundTrip is a helper for string-based strfmt types stored in VARCHAR columns. func stringRoundTrip[T interface { ~string fmt.Stringer }](t *testing.T, db *sql.DB, original T, got *T, ) { t.Helper() ctx := context.Background() table := createTable(t, db, "value TEXT") _, err := db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, value) VALUES (?, ?)", table), "v1", original) require.NoError(t, err) err = db.QueryRowContext(ctx, fmt.Sprintf("SELECT value FROM %s WHERE id = ?", table), "v1").Scan(got) require.NoError(t, err) assert.EqualT(t, original.String(), (*got).String()) } func TestMariaDB_URI(t *testing.T) { db := setupMariaDB(t) original := strfmt.URI("https://example.com/path?q=1") var got strfmt.URI stringRoundTrip(t, db, original, &got) } func TestMariaDB_Email(t *testing.T) { db := setupMariaDB(t) original := strfmt.Email("user@example.com") var got strfmt.Email stringRoundTrip(t, db, original, &got) } func TestMariaDB_Hostname(t *testing.T) { db := setupMariaDB(t) original := strfmt.Hostname("example.com") var got strfmt.Hostname stringRoundTrip(t, db, original, &got) } func TestMariaDB_IPv4(t *testing.T) { db := setupMariaDB(t) original := strfmt.IPv4("192.168.1.1") var got strfmt.IPv4 stringRoundTrip(t, db, original, &got) } func TestMariaDB_IPv6(t *testing.T) { db := setupMariaDB(t) original := strfmt.IPv6("::1") var got strfmt.IPv6 stringRoundTrip(t, db, original, &got) } func TestMariaDB_CIDR(t *testing.T) { db := setupMariaDB(t) original := strfmt.CIDR("192.168.1.0/24") var got strfmt.CIDR stringRoundTrip(t, db, original, &got) } func TestMariaDB_MAC(t *testing.T) { db := setupMariaDB(t) original := strfmt.MAC("01:02:03:04:05:06") var got strfmt.MAC stringRoundTrip(t, db, original, &got) } func TestMariaDB_UUID(t *testing.T) { db := setupMariaDB(t) original := strfmt.UUID("a8098c1a-f86e-11da-bd1a-00112444be1e") var got strfmt.UUID stringRoundTrip(t, db, original, &got) } func TestMariaDB_UUID3(t *testing.T) { db := setupMariaDB(t) original := strfmt.UUID3("bcd02ab7-6beb-3467-84c0-3bdbea962817") var got strfmt.UUID3 stringRoundTrip(t, db, original, &got) } func TestMariaDB_UUID4(t *testing.T) { db := setupMariaDB(t) original := strfmt.UUID4("025b0d74-00a2-4885-af46-084e7fbd0701") var got strfmt.UUID4 stringRoundTrip(t, db, original, &got) } func TestMariaDB_UUID5(t *testing.T) { db := setupMariaDB(t) original := strfmt.UUID5("886313e1-3b8a-5372-9b90-0c9aee199e5d") var got strfmt.UUID5 stringRoundTrip(t, db, original, &got) } func TestMariaDB_UUID7(t *testing.T) { db := setupMariaDB(t) original := strfmt.UUID7("01943ff8-3e9e-7be4-8921-de6a1e04d599") var got strfmt.UUID7 stringRoundTrip(t, db, original, &got) } func TestMariaDB_ISBN(t *testing.T) { db := setupMariaDB(t) original := strfmt.ISBN("0321751043") var got strfmt.ISBN stringRoundTrip(t, db, original, &got) } func TestMariaDB_ISBN10(t *testing.T) { db := setupMariaDB(t) original := strfmt.ISBN10("0321751043") var got strfmt.ISBN10 stringRoundTrip(t, db, original, &got) } func TestMariaDB_ISBN13(t *testing.T) { db := setupMariaDB(t) original := strfmt.ISBN13("978-0321751041") var got strfmt.ISBN13 stringRoundTrip(t, db, original, &got) } func TestMariaDB_CreditCard(t *testing.T) { db := setupMariaDB(t) original := strfmt.CreditCard("4111-1111-1111-1111") var got strfmt.CreditCard stringRoundTrip(t, db, original, &got) } func TestMariaDB_SSN(t *testing.T) { db := setupMariaDB(t) original := strfmt.SSN("111-11-1111") var got strfmt.SSN stringRoundTrip(t, db, original, &got) } func TestMariaDB_HexColor(t *testing.T) { db := setupMariaDB(t) original := strfmt.HexColor("#FFFFFF") var got strfmt.HexColor stringRoundTrip(t, db, original, &got) } func TestMariaDB_RGBColor(t *testing.T) { db := setupMariaDB(t) original := strfmt.RGBColor("rgb(255,255,255)") var got strfmt.RGBColor stringRoundTrip(t, db, original, &got) } func TestMariaDB_Password(t *testing.T) { db := setupMariaDB(t) original := strfmt.Password("super secret stuff here") var got strfmt.Password stringRoundTrip(t, db, original, &got) } func TestMariaDB_Base64(t *testing.T) { db := setupMariaDB(t) ctx := context.Background() table := createTable(t, db, "value TEXT") payload := []byte("hello world with special chars: éàü") original := strfmt.Base64(payload) _, err := db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, value) VALUES (?, ?)", table), "b64_1", original) require.NoError(t, err) var got strfmt.Base64 err = db.QueryRowContext(ctx, fmt.Sprintf("SELECT value FROM %s WHERE id = ?", table), "b64_1").Scan(&got) require.NoError(t, err) assert.EqualT(t, base64.StdEncoding.EncodeToString(original), base64.StdEncoding.EncodeToString(got)) } func TestMariaDB_ULID(t *testing.T) { db := setupMariaDB(t) ctx := context.Background() table := createTable(t, db, "value VARCHAR(64)") original, err := strfmt.ParseULID("01ARZ3NDEKTSV4RRFFQ69G5FAV") require.NoError(t, err) _, err = db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, value) VALUES (?, ?)", table), "ulid1", original) require.NoError(t, err) var got strfmt.ULID err = db.QueryRowContext(ctx, fmt.Sprintf("SELECT value FROM %s WHERE id = ?", table), "ulid1").Scan(&got) require.NoError(t, err) assert.EqualT(t, original.String(), got.String()) } func TestMariaDB_ObjectId(t *testing.T) { db := setupMariaDB(t) ctx := context.Background() table := createTable(t, db, "value VARCHAR(64)") original := strfmt.NewObjectId("507f1f77bcf86cd799439011") _, err := db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, value) VALUES (?, ?)", table), "oid1", original) require.NoError(t, err) var got strfmt.ObjectId err = db.QueryRowContext(ctx, fmt.Sprintf("SELECT value FROM %s WHERE id = ?", table), "oid1").Scan(&got) require.NoError(t, err) assert.EqualT(t, original, got) } go-openapi-strfmt-879971e/internal/testintegration/mongodb/000077500000000000000000000000001517436663600240715ustar00rootroot00000000000000go-openapi-strfmt-879971e/internal/testintegration/mongodb/mongo_test.go000066400000000000000000000007021517436663600265750ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 //go:build testintegration // Package mongodb_test runs MongoDB integration tests using the default // (bsonlite) codec — no enable/mongodb blank import. package mongodb_test import ( "testing" "github.com/go-openapi/strfmt/internal/testintegration/mongotest" ) func TestMongoDBLiteCodec(t *testing.T) { mongotest.RunAllTests(t) } go-openapi-strfmt-879971e/internal/testintegration/mongodb_enabled/000077500000000000000000000000001517436663600255435ustar00rootroot00000000000000go-openapi-strfmt-879971e/internal/testintegration/mongodb_enabled/mongo_test.go000066400000000000000000000007731517436663600302570ustar00rootroot00000000000000// 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) } go-openapi-strfmt-879971e/internal/testintegration/mongotest/000077500000000000000000000000001517436663600244635ustar00rootroot00000000000000go-openapi-strfmt-879971e/internal/testintegration/mongotest/mongotest.go000066400000000000000000000243261517436663600270400ustar00rootroot00000000000000// 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) }) } go-openapi-strfmt-879971e/internal/testintegration/postgresql/000077500000000000000000000000001517436663600246475ustar00rootroot00000000000000go-openapi-strfmt-879971e/internal/testintegration/postgresql/postgresql_test.go000066400000000000000000000243241517436663600304450ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 //go:build testintegration // Package postgresql_test lives in its own sub-package so that tests which // modify strfmt globals (e.g. MarshalFormat) cannot interfere with other // integration tests running in sibling packages. package postgresql_test import ( "context" "database/sql" "encoding/base64" "fmt" "os" "strings" "testing" "time" "github.com/go-openapi/strfmt" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" _ "github.com/jackc/pgx/v5/stdlib" ) func pgDSN() string { if dsn := os.Getenv("POSTGRESQL_DSN"); dsn != "" { return dsn } return "postgres://strfmt_test:strfmt_test@localhost:5432/strfmt_integration_test?sslmode=disable" } func setupPostgreSQL(t *testing.T) *sql.DB { t.Helper() db, err := sql.Open("pgx", pgDSN()) require.NoError(t, err) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() require.NoError(t, db.PingContext(ctx)) t.Cleanup(func() { _ = db.Close() }) return db } // createTable creates a test table and registers cleanup. // cols is a list of "col_name TYPE" definitions. func createTable(t *testing.T, db *sql.DB, cols ...string) string { t.Helper() ctx := context.Background() table := "test_" + strings.ToLower(strings.ReplaceAll(t.Name(), "/", "_")) ddl := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (id VARCHAR(64) PRIMARY KEY, %s)", table, strings.Join(cols, ", ")) _, err := db.ExecContext(ctx, ddl) require.NoError(t, err) t.Cleanup(func() { _, _ = db.ExecContext(context.Background(), "DROP TABLE IF EXISTS "+table) }) return table } // PostgreSQL handles RFC3339 timestamps with "Z" suffix natively — unlike // MySQL/MariaDB, no workaround is needed for DateTime (see issue #174). func TestPostgreSQL_DateTime_NativeDatetime(t *testing.T) { db := setupPostgreSQL(t) ctx := context.Background() table := createTable(t, db, "value TIMESTAMPTZ") original := strfmt.DateTime(time.Date(2024, 6, 15, 12, 30, 45, 123000000, time.UTC)) _, err := db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, value) VALUES ($1, $2)", table), "dt1", original) require.NoError(t, err) var got strfmt.DateTime err = db.QueryRowContext(ctx, fmt.Sprintf("SELECT value FROM %s WHERE id = $1", table), "dt1").Scan(&got) require.NoError(t, err) assert.EqualT(t, time.Time(original).UTC().Truncate(time.Millisecond), time.Time(got).UTC().Truncate(time.Millisecond), ) } func TestPostgreSQL_DateTime_AsString(t *testing.T) { db := setupPostgreSQL(t) ctx := context.Background() table := createTable(t, db, "value VARCHAR(64)") original := strfmt.DateTime(time.Date(2024, 6, 15, 12, 30, 45, 123000000, time.UTC)) _, err := db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, value) VALUES ($1, $2)", table), "dt1", original) require.NoError(t, err) var got strfmt.DateTime err = db.QueryRowContext(ctx, fmt.Sprintf("SELECT value FROM %s WHERE id = $1", table), "dt1").Scan(&got) require.NoError(t, err) assert.EqualT(t, time.Time(original).UTC().Truncate(time.Millisecond), time.Time(got).UTC().Truncate(time.Millisecond), ) } func TestPostgreSQL_Date(t *testing.T) { db := setupPostgreSQL(t) ctx := context.Background() table := createTable(t, db, "value DATE") original := strfmt.Date(time.Date(2024, 6, 15, 0, 0, 0, 0, time.UTC)) _, err := db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, value) VALUES ($1, $2)", table), "d1", original) require.NoError(t, err) var got strfmt.Date err = db.QueryRowContext(ctx, fmt.Sprintf("SELECT value FROM %s WHERE id = $1", table), "d1").Scan(&got) require.NoError(t, err) assert.EqualT(t, original.String(), got.String()) } func TestPostgreSQL_Duration(t *testing.T) { // Duration.Value() returns int64 (nanoseconds), so use BIGINT column. db := setupPostgreSQL(t) ctx := context.Background() table := createTable(t, db, "value BIGINT") original := strfmt.Duration(42 * time.Second) _, err := db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, value) VALUES ($1, $2)", table), "dur1", original) require.NoError(t, err) var got strfmt.Duration err = db.QueryRowContext(ctx, fmt.Sprintf("SELECT value FROM %s WHERE id = $1", table), "dur1").Scan(&got) require.NoError(t, err) assert.EqualT(t, original, got) } // stringRoundTrip is a helper for string-based strfmt types stored in TEXT columns. func stringRoundTrip[T interface { ~string fmt.Stringer }](t *testing.T, db *sql.DB, original T, got *T, ) { t.Helper() ctx := context.Background() table := createTable(t, db, "value TEXT") _, err := db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, value) VALUES ($1, $2)", table), "v1", original) require.NoError(t, err) err = db.QueryRowContext(ctx, fmt.Sprintf("SELECT value FROM %s WHERE id = $1", table), "v1").Scan(got) require.NoError(t, err) assert.EqualT(t, original.String(), (*got).String()) } func TestPostgreSQL_URI(t *testing.T) { db := setupPostgreSQL(t) original := strfmt.URI("https://example.com/path?q=1") var got strfmt.URI stringRoundTrip(t, db, original, &got) } func TestPostgreSQL_Email(t *testing.T) { db := setupPostgreSQL(t) original := strfmt.Email("user@example.com") var got strfmt.Email stringRoundTrip(t, db, original, &got) } func TestPostgreSQL_Hostname(t *testing.T) { db := setupPostgreSQL(t) original := strfmt.Hostname("example.com") var got strfmt.Hostname stringRoundTrip(t, db, original, &got) } func TestPostgreSQL_IPv4(t *testing.T) { db := setupPostgreSQL(t) original := strfmt.IPv4("192.168.1.1") var got strfmt.IPv4 stringRoundTrip(t, db, original, &got) } func TestPostgreSQL_IPv6(t *testing.T) { db := setupPostgreSQL(t) original := strfmt.IPv6("::1") var got strfmt.IPv6 stringRoundTrip(t, db, original, &got) } func TestPostgreSQL_CIDR(t *testing.T) { db := setupPostgreSQL(t) original := strfmt.CIDR("192.168.1.0/24") var got strfmt.CIDR stringRoundTrip(t, db, original, &got) } func TestPostgreSQL_MAC(t *testing.T) { db := setupPostgreSQL(t) original := strfmt.MAC("01:02:03:04:05:06") var got strfmt.MAC stringRoundTrip(t, db, original, &got) } func TestPostgreSQL_UUID(t *testing.T) { db := setupPostgreSQL(t) original := strfmt.UUID("a8098c1a-f86e-11da-bd1a-00112444be1e") var got strfmt.UUID stringRoundTrip(t, db, original, &got) } func TestPostgreSQL_UUID3(t *testing.T) { db := setupPostgreSQL(t) original := strfmt.UUID3("bcd02ab7-6beb-3467-84c0-3bdbea962817") var got strfmt.UUID3 stringRoundTrip(t, db, original, &got) } func TestPostgreSQL_UUID4(t *testing.T) { db := setupPostgreSQL(t) original := strfmt.UUID4("025b0d74-00a2-4885-af46-084e7fbd0701") var got strfmt.UUID4 stringRoundTrip(t, db, original, &got) } func TestPostgreSQL_UUID5(t *testing.T) { db := setupPostgreSQL(t) original := strfmt.UUID5("886313e1-3b8a-5372-9b90-0c9aee199e5d") var got strfmt.UUID5 stringRoundTrip(t, db, original, &got) } func TestPostgreSQL_UUID7(t *testing.T) { db := setupPostgreSQL(t) original := strfmt.UUID7("01943ff8-3e9e-7be4-8921-de6a1e04d599") var got strfmt.UUID7 stringRoundTrip(t, db, original, &got) } func TestPostgreSQL_ISBN(t *testing.T) { db := setupPostgreSQL(t) original := strfmt.ISBN("0321751043") var got strfmt.ISBN stringRoundTrip(t, db, original, &got) } func TestPostgreSQL_ISBN10(t *testing.T) { db := setupPostgreSQL(t) original := strfmt.ISBN10("0321751043") var got strfmt.ISBN10 stringRoundTrip(t, db, original, &got) } func TestPostgreSQL_ISBN13(t *testing.T) { db := setupPostgreSQL(t) original := strfmt.ISBN13("978-0321751041") var got strfmt.ISBN13 stringRoundTrip(t, db, original, &got) } func TestPostgreSQL_CreditCard(t *testing.T) { db := setupPostgreSQL(t) original := strfmt.CreditCard("4111-1111-1111-1111") var got strfmt.CreditCard stringRoundTrip(t, db, original, &got) } func TestPostgreSQL_SSN(t *testing.T) { db := setupPostgreSQL(t) original := strfmt.SSN("111-11-1111") var got strfmt.SSN stringRoundTrip(t, db, original, &got) } func TestPostgreSQL_HexColor(t *testing.T) { db := setupPostgreSQL(t) original := strfmt.HexColor("#FFFFFF") var got strfmt.HexColor stringRoundTrip(t, db, original, &got) } func TestPostgreSQL_RGBColor(t *testing.T) { db := setupPostgreSQL(t) original := strfmt.RGBColor("rgb(255,255,255)") var got strfmt.RGBColor stringRoundTrip(t, db, original, &got) } func TestPostgreSQL_Password(t *testing.T) { db := setupPostgreSQL(t) original := strfmt.Password("super secret stuff here") var got strfmt.Password stringRoundTrip(t, db, original, &got) } func TestPostgreSQL_Base64(t *testing.T) { db := setupPostgreSQL(t) ctx := context.Background() table := createTable(t, db, "value TEXT") payload := []byte("hello world with special chars: éàü") original := strfmt.Base64(payload) _, err := db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, value) VALUES ($1, $2)", table), "b64_1", original) require.NoError(t, err) var got strfmt.Base64 err = db.QueryRowContext(ctx, fmt.Sprintf("SELECT value FROM %s WHERE id = $1", table), "b64_1").Scan(&got) require.NoError(t, err) assert.EqualT(t, base64.StdEncoding.EncodeToString(original), base64.StdEncoding.EncodeToString(got)) } func TestPostgreSQL_ULID(t *testing.T) { db := setupPostgreSQL(t) ctx := context.Background() table := createTable(t, db, "value VARCHAR(64)") original, err := strfmt.ParseULID("01ARZ3NDEKTSV4RRFFQ69G5FAV") require.NoError(t, err) _, err = db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, value) VALUES ($1, $2)", table), "ulid1", original) require.NoError(t, err) var got strfmt.ULID err = db.QueryRowContext(ctx, fmt.Sprintf("SELECT value FROM %s WHERE id = $1", table), "ulid1").Scan(&got) require.NoError(t, err) assert.EqualT(t, original.String(), got.String()) } func TestPostgreSQL_ObjectId(t *testing.T) { db := setupPostgreSQL(t) ctx := context.Background() table := createTable(t, db, "value VARCHAR(64)") original := strfmt.NewObjectId("507f1f77bcf86cd799439011") _, err := db.ExecContext(ctx, fmt.Sprintf("INSERT INTO %s (id, value) VALUES ($1, $2)", table), "oid1", original) require.NoError(t, err) var got strfmt.ObjectId err = db.QueryRowContext(ctx, fmt.Sprintf("SELECT value FROM %s WHERE id = $1", table), "oid1").Scan(&got) require.NoError(t, err) assert.EqualT(t, original, got) } go-openapi-strfmt-879971e/mongo.go000066400000000000000000000350061517436663600170570ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package strfmt import ( "encoding/base64" "encoding/binary" "fmt" "time" "github.com/go-openapi/strfmt/internal/bsonlite" "github.com/oklog/ulid/v2" ) // 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 ( _ 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 ( millisec = 1000 microsec = 1_000_000 bsonDateTimeSize = 8 ) func (d Date) MarshalBSON() ([]byte, error) { return bsonlite.C.MarshalDoc(d.String()) } func (d *Date) UnmarshalBSON(data []byte) 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 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 bsonlite.C.MarshalDoc(b.String()) } // UnmarshalBSON document into this value. func (b *Base64) UnmarshalBSON(data []byte) 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 base64: %w", ErrFormat) } vb, err := base64.StdEncoding.DecodeString(s) if err != nil { return err } *b = Base64(vb) return nil } func (d Duration) MarshalBSON() ([]byte, error) { return bsonlite.C.MarshalDoc(d.String()) } func (d *Duration) UnmarshalBSON(data []byte) 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 value as Duration: %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) { 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 { v, err := bsonlite.C.UnmarshalDoc(data) if err != nil { return err } 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. 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. tNorm := NormalizeTimeForMarshal(time.Time(t)) i64 := tNorm.Unix()*millisec + int64(tNorm.Nanosecond())/microsec buf := make([]byte, bsonDateTimeSize) binary.LittleEndian.PutUint64(buf, uint64(i64)) //nolint:gosec // it's okay to handle negative int64 this way return bsonlite.TypeDateTime, buf, nil } // UnmarshalBSONValue unmarshals a BSON DateTime value into this [DateTime]. func (t *DateTime) UnmarshalBSONValue(tpe byte, data []byte) error { if tpe == bsonlite.TypeNull { *t = DateTime{} return nil } if len(data) != bsonDateTimeSize { return fmt.Errorf("bson date field length not exactly %d bytes: %w", bsonDateTimeSize, ErrFormat) } i64 := int64(binary.LittleEndian.Uint64(data)) //nolint:gosec // it's okay if we overflow and get a negative datetime *t = DateTime(time.Unix(i64/millisec, i64%millisec*microsec)) return nil } // MarshalBSON document from this value. func (u ULID) MarshalBSON() ([]byte, error) { return bsonlite.C.MarshalDoc(u.String()) } // UnmarshalBSON document into this value. func (u *ULID) UnmarshalBSON(data []byte) 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 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) } 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 bsonlite.C.MarshalDoc(u.String()) } // UnmarshalBSON document into this value. func (u *URI) UnmarshalBSON(data []byte) error { s, err := unmarshalBSONString(data, "uri") if err != nil { return err } *u = URI(s) return nil } // MarshalBSON document from this value. func (e Email) MarshalBSON() ([]byte, error) { return bsonlite.C.MarshalDoc(e.String()) } // UnmarshalBSON document into this value. func (e *Email) UnmarshalBSON(data []byte) error { s, err := unmarshalBSONString(data, "email") if err != nil { return err } *e = Email(s) return nil } // MarshalBSON document from this value. func (h Hostname) MarshalBSON() ([]byte, error) { return bsonlite.C.MarshalDoc(h.String()) } // UnmarshalBSON document into this value. func (h *Hostname) UnmarshalBSON(data []byte) error { s, err := unmarshalBSONString(data, "hostname") if err != nil { return err } *h = Hostname(s) return nil } // MarshalBSON document from this value. func (u IPv4) MarshalBSON() ([]byte, error) { return bsonlite.C.MarshalDoc(u.String()) } // UnmarshalBSON document into this value. func (u *IPv4) UnmarshalBSON(data []byte) error { s, err := unmarshalBSONString(data, "ipv4") if err != nil { return err } *u = IPv4(s) return nil } // MarshalBSON document from this value. func (u IPv6) MarshalBSON() ([]byte, error) { return bsonlite.C.MarshalDoc(u.String()) } // UnmarshalBSON document into this value. func (u *IPv6) UnmarshalBSON(data []byte) error { s, err := unmarshalBSONString(data, "ipv6") if err != nil { return err } *u = IPv6(s) return nil } // MarshalBSON document from this value. func (u CIDR) MarshalBSON() ([]byte, error) { return bsonlite.C.MarshalDoc(u.String()) } // UnmarshalBSON document into this value. func (u *CIDR) UnmarshalBSON(data []byte) error { s, err := unmarshalBSONString(data, "CIDR") if err != nil { return err } *u = CIDR(s) return nil } // MarshalBSON document from this value. func (u MAC) MarshalBSON() ([]byte, error) { return bsonlite.C.MarshalDoc(u.String()) } // UnmarshalBSON document into this value. func (u *MAC) UnmarshalBSON(data []byte) error { s, err := unmarshalBSONString(data, "MAC") if err != nil { return err } *u = MAC(s) return nil } // MarshalBSON document from this value. func (r Password) MarshalBSON() ([]byte, error) { return bsonlite.C.MarshalDoc(r.String()) } // UnmarshalBSON document into this value. func (r *Password) UnmarshalBSON(data []byte) error { s, err := unmarshalBSONString(data, "Password") if err != nil { return err } *r = Password(s) return nil } // MarshalBSON document from this value. func (u UUID) MarshalBSON() ([]byte, error) { return bsonlite.C.MarshalDoc(u.String()) } // UnmarshalBSON document into this value. func (u *UUID) UnmarshalBSON(data []byte) error { s, err := unmarshalBSONString(data, "UUID") if err != nil { return err } *u = UUID(s) return nil } // MarshalBSON document from this value. func (u UUID3) MarshalBSON() ([]byte, error) { return bsonlite.C.MarshalDoc(u.String()) } // UnmarshalBSON document into this value. func (u *UUID3) UnmarshalBSON(data []byte) error { s, err := unmarshalBSONString(data, "UUID3") if err != nil { return err } *u = UUID3(s) return nil } // MarshalBSON document from this value. func (u UUID4) MarshalBSON() ([]byte, error) { return bsonlite.C.MarshalDoc(u.String()) } // UnmarshalBSON document into this value. func (u *UUID4) UnmarshalBSON(data []byte) error { s, err := unmarshalBSONString(data, "UUID4") if err != nil { return err } *u = UUID4(s) return nil } // MarshalBSON document from this value. func (u UUID5) MarshalBSON() ([]byte, error) { return bsonlite.C.MarshalDoc(u.String()) } // UnmarshalBSON document into this value. func (u *UUID5) UnmarshalBSON(data []byte) error { s, err := unmarshalBSONString(data, "UUID5") if err != nil { return err } *u = UUID5(s) return nil } // MarshalBSON document from this value. func (u UUID7) MarshalBSON() ([]byte, error) { return bsonlite.C.MarshalDoc(u.String()) } // UnmarshalBSON document into this value. func (u *UUID7) UnmarshalBSON(data []byte) error { s, err := unmarshalBSONString(data, "UUID7") if err != nil { return err } *u = UUID7(s) return nil } // MarshalBSON document from this value. func (u ISBN) MarshalBSON() ([]byte, error) { return bsonlite.C.MarshalDoc(u.String()) } // UnmarshalBSON document into this value. func (u *ISBN) UnmarshalBSON(data []byte) error { s, err := unmarshalBSONString(data, "ISBN") if err != nil { return err } *u = ISBN(s) return nil } // MarshalBSON document from this value. func (u ISBN10) MarshalBSON() ([]byte, error) { return bsonlite.C.MarshalDoc(u.String()) } // UnmarshalBSON document into this value. func (u *ISBN10) UnmarshalBSON(data []byte) error { s, err := unmarshalBSONString(data, "ISBN10") if err != nil { return err } *u = ISBN10(s) return nil } // MarshalBSON document from this value. func (u ISBN13) MarshalBSON() ([]byte, error) { return bsonlite.C.MarshalDoc(u.String()) } // UnmarshalBSON document into this value. func (u *ISBN13) UnmarshalBSON(data []byte) error { s, err := unmarshalBSONString(data, "ISBN13") if err != nil { return err } *u = ISBN13(s) return nil } // MarshalBSON document from this value. func (u CreditCard) MarshalBSON() ([]byte, error) { return bsonlite.C.MarshalDoc(u.String()) } // UnmarshalBSON document into this value. func (u *CreditCard) UnmarshalBSON(data []byte) error { s, err := unmarshalBSONString(data, "CreditCard") if err != nil { return err } *u = CreditCard(s) return nil } // MarshalBSON document from this value. func (u SSN) MarshalBSON() ([]byte, error) { return bsonlite.C.MarshalDoc(u.String()) } // UnmarshalBSON document into this value. func (u *SSN) UnmarshalBSON(data []byte) error { s, err := unmarshalBSONString(data, "SSN") if err != nil { return err } *u = SSN(s) return nil } // MarshalBSON document from this value. func (h HexColor) MarshalBSON() ([]byte, error) { return bsonlite.C.MarshalDoc(h.String()) } // UnmarshalBSON document into this value. func (h *HexColor) UnmarshalBSON(data []byte) error { s, err := unmarshalBSONString(data, "HexColor") if err != nil { return err } *h = HexColor(s) return nil } // MarshalBSON document from this value. func (r RGBColor) MarshalBSON() ([]byte, error) { return bsonlite.C.MarshalDoc(r.String()) } // UnmarshalBSON document into this value. func (r *RGBColor) UnmarshalBSON(data []byte) error { s, err := unmarshalBSONString(data, "RGBColor") if err != nil { return err } *r = RGBColor(s) return nil } // MarshalBSON renders the object id as a BSON document. func (id ObjectId) MarshalBSON() ([]byte, error) { return bsonlite.C.MarshalDoc([12]byte(id)) } // UnmarshalBSON reads the objectId from a BSON document. func (id *ObjectId) UnmarshalBSON(data []byte) error { v, err := bsonlite.C.UnmarshalDoc(data) if err != nil { return err } 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 marshals the [ObjectId] as a raw BSON ObjectID value. func (id ObjectId) MarshalBSONValue() (byte, []byte, error) { oid := [12]byte(id) return bsonlite.TypeObjectID, oid[:], nil } // UnmarshalBSONValue unmarshals a raw BSON ObjectID value into this [ObjectId]. func (id *ObjectId) UnmarshalBSONValue(_ byte, data []byte) error { var oid [12]byte copy(oid[:], data) *id = ObjectId(oid) return nil } go-openapi-strfmt-879971e/mongo_test.go000066400000000000000000000170101517436663600201110ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package strfmt import ( "reflect" "testing" "time" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" "github.com/google/uuid" ) type testableBSONFormat interface { testableFormat bsonMarshaler bsonUnmarshaler } func TestBSONDate(t *testing.T) { dateOriginal := Date(time.Date(2014, 10, 10, 0, 0, 0, 0, time.UTC)) bsonData, err := dateOriginal.MarshalBSON() require.NoError(t, err) var dateCopy Date err = dateCopy.UnmarshalBSON(bsonData) require.NoError(t, err) assert.EqualT(t, dateOriginal, dateCopy) } func TestBSONBase64(t *testing.T) { const b64 string = "This is a byte array with unprintable chars, but it also isn" b := []byte(b64) subj := Base64(b) bsonData, err := subj.MarshalBSON() require.NoError(t, err) var b64Copy Base64 err = b64Copy.UnmarshalBSON(bsonData) require.NoError(t, err) assert.Equal(t, subj, b64Copy) } func TestBSONDuration(t *testing.T) { dur := Duration(42) bsonData, err := dur.MarshalBSON() require.NoError(t, err) var durCopy Duration err = durCopy.UnmarshalBSON(bsonData) require.NoError(t, err) assert.EqualT(t, dur, durCopy) } func TestBSONDateTime(t *testing.T) { for caseNum, example := range testCases { t.Logf("Case #%d", caseNum) dt := DateTime(example.time) bsonData, err := dt.MarshalBSON() require.NoError(t, err) var dtCopy DateTime 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()) } } func TestBSONULID(t *testing.T) { t.Parallel() t.Run("positive", func(t *testing.T) { t.Parallel() ulid, _ := ParseULID(testUlid) bsonData, err := ulid.MarshalBSON() require.NoError(t, err) var ulidUnmarshaled ULID err = ulidUnmarshaled.UnmarshalBSON(bsonData) require.NoError(t, err) assert.EqualT(t, ulid, ulidUnmarshaled) }) t.Run("negative", func(t *testing.T) { t.Parallel() uid := UUID("00000000-0000-0000-0000-000000000000") bsonData, err := uid.MarshalBSON() require.NoError(t, err) var ulidUnmarshaled ULID err = ulidUnmarshaled.UnmarshalBSON(bsonData) require.Error(t, err) }) } func TestFormatBSON(t *testing.T) { t.Run("with URI", func(t *testing.T) { t.Run("should bson.Marshal and bson.Unmarshal", func(t *testing.T) { uri := URI("http://somewhere.com") str := "http://somewhereelse.com" testBSONStringFormat(t, &uri, "uri", str, []string{}, []string{"somewhere.com"}) }) }) t.Run("with Email", func(t *testing.T) { email := Email("somebody@somewhere.com") str := string("somebodyelse@somewhere.com") testBSONStringFormat(t, &email, "email", str, validEmails(), []string{"somebody@somewhere@com"}) }) t.Run("with Hostname", func(t *testing.T) { hostname := Hostname("somewhere.com") str := string("somewhere.com") testBSONStringFormat(t, &hostname, "hostname", str, []string{}, invalidHostnames()) testBSONStringFormat(t, &hostname, "hostname", str, validHostnames(), []string{}) }) t.Run("with IPv4", func(t *testing.T) { ipv4 := IPv4("192.168.254.1") str := string("192.168.254.2") testBSONStringFormat(t, &ipv4, "ipv4", str, []string{}, []string{"198.168.254.2.2"}) }) t.Run("with IPv6", func(t *testing.T) { ipv6 := IPv6("::1") str := string("::2") testBSONStringFormat(t, &ipv6, "ipv6", str, []string{}, []string{"127.0.0.1"}) }) t.Run("with CIDR", func(t *testing.T) { cidr := CIDR("192.168.254.1/24") str := string("192.168.254.2/24") testBSONStringFormat(t, &cidr, "cidr", str, []string{"192.0.2.1/24", "2001:db8:a0b:12f0::1/32"}, []string{"198.168.254.2", "2001:db8:a0b:12f0::1"}) }) t.Run("with MAC", func(t *testing.T) { mac := MAC("01:02:03:04:05:06") str := string("06:05:04:03:02:01") testBSONStringFormat(t, &mac, "mac", str, []string{}, []string{"01:02:03:04:05"}) }) t.Run("with UUID3", func(t *testing.T) { first3 := uuid.NewMD5(uuid.NameSpaceURL, []byte("somewhere.com")) uuid3 := UUID3(first3.String()) str := first3.String() testBSONStringFormat(t, &uuid3, "uuid3", str, validUUID3s(), invalidUUID3s(), ) }) t.Run("with UUID4", func(t *testing.T) { first4 := uuid.Must(uuid.NewRandom()) other4 := uuid.Must(uuid.NewRandom()) uuid4 := UUID4(first4.String()) str := other4.String() testBSONStringFormat(t, &uuid4, "uuid4", str, validUUID4s(), invalidUUID4s(), ) }) t.Run("with UUID5", func(t *testing.T) { first5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhere.com")) other5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhereelse.com")) uuid5 := UUID5(first5.String()) str := other5.String() testBSONStringFormat(t, &uuid5, "uuid5", str, validUUID5s(), invalidUUID5s(), ) }) t.Run("with UUID7", func(t *testing.T) { first7 := uuid.Must(uuid.NewV7()) str := first7.String() uuid7 := UUID7(str) testBSONStringFormat(t, &uuid7, "uuid7", str, validUUID7s(), invalidUUID7s(), ) }) t.Run("with UUID", func(t *testing.T) { first5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhere.com")) other5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhereelse.com")) uuid := UUID(first5.String()) str := other5.String() testBSONStringFormat(t, &uuid, "uuid", str, validUUIDs(), invalidUUIDs(), ) }) t.Run("with ISBN", func(t *testing.T) { isbn := ISBN("0321751043") str := string("0321751043") testBSONStringFormat(t, &isbn, "isbn", str, []string{}, []string{"836217463"}) // bad checksum }) t.Run("with ISBN10", func(t *testing.T) { isbn10 := ISBN10("0321751043") str := string("0321751043") testBSONStringFormat(t, &isbn10, "isbn10", str, []string{}, []string{"836217463"}) // bad checksum }) t.Run("with ISBN13", func(t *testing.T) { isbn13 := ISBN13("978-0321751041") str := string("978-0321751041") testBSONStringFormat(t, &isbn13, "isbn13", str, []string{}, []string{"978-0321751042"}) // bad checksum }) t.Run("with HexColor", func(t *testing.T) { hexColor := HexColor("#FFFFFF") str := string("#000000") testBSONStringFormat(t, &hexColor, "hexcolor", str, []string{}, []string{"#fffffffz"}) }) t.Run("with RGBColor", func(t *testing.T) { rgbColor := RGBColor("rgb(255,255,255)") str := string("rgb(0,0,0)") testBSONStringFormat(t, &rgbColor, "rgbcolor", str, []string{}, []string{"rgb(300,0,0)"}) }) t.Run("with SSN", func(t *testing.T) { ssn := SSN("111-11-1111") str := string("999 99 9999") testBSONStringFormat(t, &ssn, "ssn", str, []string{}, []string{"999 99 999"}) }) t.Run("with CreditCard", func(t *testing.T) { creditCard := CreditCard("4111-1111-1111-1111") str := string("4012-8888-8888-1881") testBSONStringFormat(t, &creditCard, "creditcard", str, []string{}, []string{"9999-9999-9999-999"}) }) t.Run("with Password", func(t *testing.T) { password := Password("super secret stuff here") testBSONStringFormat(t, &password, "password", "super secret!!!", []string{"even more secret"}, []string{}) }) } func testBSONStringFormat(t *testing.T, what testableBSONFormat, format, with string, _, _ []string) { t.Helper() b := []byte(with) err := what.UnmarshalText(b) require.NoError(t, err) // bson encoding interface bsonData, err := what.MarshalBSON() require.NoError(t, err) resetValue(t, format, what) err = what.UnmarshalBSON(bsonData) require.NoError(t, err) val := reflect.Indirect(reflect.ValueOf(what)) strVal := val.String() assert.EqualT(t, with, strVal, "[%s]bson.Unmarshal: expected %v and %v to be equal (reset value) ", format, what, with) } go-openapi-strfmt-879971e/time.go000066400000000000000000000203651517436663600167000ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package strfmt import ( "database/sql/driver" "encoding/json" "fmt" "regexp" "strings" "time" ) // UnixZero sets the zero unix UTC timestamp we want to compare against. // // Unix 0 for an EST timezone is not equivalent to a UTC timezone. var UnixZero = time.Unix(0, 0).UTC() //nolint:gochecknoglobals // package-level sentinel value for unix epoch func init() { //nolint:gochecknoinits // registers datetime format in the default registry dt := DateTime{} Default.Add("datetime", &dt, IsDateTime) } // IsDateTime returns true when the string is a valid date-time. // // JSON datetime format consist of a date and a time separated by a "T", e.g. 2012-04-23T18:25:43.511Z. func IsDateTime(str string) bool { const ( minDateTimeLength = 4 minParts = 2 ) if len(str) < minDateTimeLength { return false } s := strings.Split(strings.ToLower(str), "t") if len(s) < minParts || !IsDate(s[0]) { return false } matches := rxDateTime.FindAllStringSubmatch(s[1], -1) if len(matches) == 0 || len(matches[0]) == 0 { return false } m := matches[0] res := m[1] <= "23" && m[2] <= "59" && m[3] <= "59" return res } const ( // RFC3339Millis represents a ISO8601 format to millis instead of to nanos. RFC3339Millis = "2006-01-02T15:04:05.000Z07:00" // RFC3339MillisNoColon represents a ISO8601 format to millis instead of to nanos. RFC3339MillisNoColon = "2006-01-02T15:04:05.000Z0700" // RFC3339Micro represents a ISO8601 format to micro instead of to nano. RFC3339Micro = "2006-01-02T15:04:05.000000Z07:00" // RFC3339MicroNoColon represents a ISO8601 format to micro instead of to nano. RFC3339MicroNoColon = "2006-01-02T15:04:05.000000Z0700" // ISO8601LocalTime represents a ISO8601 format to ISO8601 in local time (no timezone). ISO8601LocalTime = "2006-01-02T15:04:05" // ISO8601TimeWithReducedPrecision represents a ISO8601 format with reduced precision (dropped secs). ISO8601TimeWithReducedPrecision = "2006-01-02T15:04Z" // ISO8601TimeWithReducedPrecisionLocaltime represents a ISO8601 format with reduced precision and no timezone (dropped seconds + no timezone). ISO8601TimeWithReducedPrecisionLocaltime = "2006-01-02T15:04" // ISO8601TimeUniversalSortableDateTimePattern represents a ISO8601 universal sortable date time pattern. ISO8601TimeUniversalSortableDateTimePattern = "2006-01-02 15:04:05" // ISO8601TimeUniversalSortableDateTimePatternShortForm is the short form of [ISO8601TimeUniversalSortableDateTimePattern]. ISO8601TimeUniversalSortableDateTimePatternShortForm = "2006-01-02" // DateTimePattern pattern to match for the date-time format from http://tools.ietf.org/html/rfc3339#section-5.6 DateTimePattern = `^([0-9]{2}):([0-9]{2}):([0-9]{2})(.[0-9]+)?(z|([+-][0-9]{2}:[0-9]{2}))$` ) //nolint:gochecknoglobals // package-level configuration for datetime parsing and marshaling var ( rxDateTime = regexp.MustCompile(DateTimePattern) // DateTimeFormats is the collection of formats used by [ParseDateTime](). DateTimeFormats = []string{ RFC3339Micro, RFC3339MicroNoColon, RFC3339Millis, RFC3339MillisNoColon, time.RFC3339, time.RFC3339Nano, ISO8601LocalTime, ISO8601TimeWithReducedPrecision, ISO8601TimeWithReducedPrecisionLocaltime, ISO8601TimeUniversalSortableDateTimePattern, ISO8601TimeUniversalSortableDateTimePatternShortForm, } // MarshalFormat sets the time resolution format used for marshaling time (set to milliseconds). MarshalFormat = RFC3339Millis // NormalizeTimeForMarshal provides a normalization function on time before marshaling (e.g. [time.UTC]). // By default, the time value is not changed. NormalizeTimeForMarshal = func(t time.Time) time.Time { return t } // DefaultTimeLocation provides a location for a time when the time zone is not encoded in the string (ex: ISO8601 Local variants). DefaultTimeLocation = time.UTC ) // ParseDateTime parses a string that represents an ISO8601 time or a unix epoch. func ParseDateTime(data string) (DateTime, error) { if data == "" { return NewDateTime(), nil } var lastError error for _, layout := range DateTimeFormats { dd, err := time.ParseInLocation(layout, data, DefaultTimeLocation) if err != nil { lastError = err continue } return DateTime(dd), nil } return DateTime{}, lastError } // DateTime is a time but it serializes to ISO8601 format with millis. // // It knows how to read 3 different variations of a RFC3339 date time. // Most APIs we encounter want either millisecond or second precision times. // This just tries to make it worry-free. // // swagger:strfmt date-time. type DateTime time.Time // NewDateTime is a representation of the UNIX epoch (January 1, 1970 00:00:00 UTC) for the [DateTime] type. // // Notice that this is not the zero value of the [DateTime] type. // // You may use [DateTime.IsUnixZero] to check against this value. func NewDateTime() DateTime { return DateTime(time.Unix(0, 0).UTC()) } // MakeDateTime is a representation of the zero value of the [DateTime] type (January 1, year 1, 00:00:00 UTC). // // You may use [DateTime.IsZero] to check against this value. func MakeDateTime() DateTime { return DateTime(time.Time{}) } // String converts this time to a string. func (t DateTime) String() string { return NormalizeTimeForMarshal(time.Time(t)).Format(MarshalFormat) } // IsZero returns whether the date time is a zero value. func (t DateTime) IsZero() bool { return time.Time(t).IsZero() } // IsUnixZero returns whether the date time is equivalent to [time.Unix](0, 0).UTC(). func (t DateTime) IsUnixZero() bool { return time.Time(t).Equal(UnixZero) } // MarshalText implements the text marshaler interface. func (t DateTime) MarshalText() ([]byte, error) { return []byte(t.String()), nil } // UnmarshalText implements the text unmarshaler interface. func (t *DateTime) UnmarshalText(text []byte) error { tt, err := ParseDateTime(string(text)) if err != nil { return err } *t = tt return nil } // Scan scans a [DateTime] value from database driver type. func (t *DateTime) Scan(raw any) error { // Proposal for enhancement: case int64: and case float64: ? switch v := raw.(type) { case []byte: return t.UnmarshalText(v) case string: return t.UnmarshalText([]byte(v)) case time.Time: *t = DateTime(v) case nil: *t = DateTime{} default: return fmt.Errorf("cannot sql.Scan() strfmt.DateTime from: %#v: %w", v, ErrFormat) } return nil } // Value converts [DateTime] to a primitive value ready to written to a database. func (t DateTime) Value() (driver.Value, error) { return driver.Value(t.String()), nil } // MarshalJSON returns the [DateTime] as JSON. func (t DateTime) MarshalJSON() ([]byte, error) { return json.Marshal(NormalizeTimeForMarshal(time.Time(t)).Format(MarshalFormat)) } // UnmarshalJSON sets the [DateTime] from JSON. func (t *DateTime) UnmarshalJSON(data []byte) error { if string(data) == jsonNull { return nil } var tstr string if err := json.Unmarshal(data, &tstr); err != nil { return err } tt, err := ParseDateTime(tstr) if err != nil { return err } *t = tt return nil } // DeepCopyInto copies the receiver and writes its value into out. func (t *DateTime) DeepCopyInto(out *DateTime) { *out = *t } // DeepCopy copies the receiver into a new [DateTime]. func (t *DateTime) DeepCopy() *DateTime { if t == nil { return nil } out := new(DateTime) t.DeepCopyInto(out) return out } // GobEncode implements the gob.GobEncoder interface. func (t DateTime) GobEncode() ([]byte, error) { return t.MarshalBinary() } // GobDecode implements the gob.GobDecoder interface. func (t *DateTime) GobDecode(data []byte) error { return t.UnmarshalBinary(data) } // MarshalBinary implements the encoding.[encoding.BinaryMarshaler] interface. func (t DateTime) MarshalBinary() ([]byte, error) { return NormalizeTimeForMarshal(time.Time(t)).MarshalBinary() } // UnmarshalBinary implements the encoding.[encoding.BinaryUnmarshaler] interface. func (t *DateTime) UnmarshalBinary(data []byte) error { var original time.Time err := original.UnmarshalBinary(data) if err != nil { return err } *t = DateTime(original) return nil } // Equal checks if two [DateTime] instances are equal using [time.Time]'s Equal method. func (t DateTime) Equal(t2 DateTime) bool { return time.Time(t).Equal(time.Time(t2)) } go-openapi-strfmt-879971e/time_test.go000066400000000000000000000222241517436663600177330ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package strfmt import ( "bytes" "encoding/gob" "testing" "time" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" ) //nolint:gochecknoglobals // test fixtures var ( p, _ = time.Parse(time.RFC3339Nano, "2011-08-18T19:03:37.000000000+01:00") testCases = []struct { in []byte // externally sourced data -- to be unmarshaled time time.Time // its representation in time.Time str string // its marshaled representation utcStr string // the marshaled representation as utc }{ {[]byte("2014-12-15"), time.Date(2014, 12, 15, 0, 0, 0, 0, time.UTC), "2014-12-15T00:00:00.000Z", "2014-12-15T00:00:00.000Z"}, {[]byte("2014-12-15 08:00:00"), time.Date(2014, 12, 15, 8, 0, 0, 0, time.UTC), "2014-12-15T08:00:00.000Z", "2014-12-15T08:00:00.000Z"}, {[]byte("2014-12-15T08:00:00"), time.Date(2014, 12, 15, 8, 0, 0, 0, time.UTC), "2014-12-15T08:00:00.000Z", "2014-12-15T08:00:00.000Z"}, {[]byte("2014-12-15T08:00"), time.Date(2014, 12, 15, 8, 0, 0, 0, time.UTC), "2014-12-15T08:00:00.000Z", "2014-12-15T08:00:00.000Z"}, {[]byte("2014-12-15T08:00Z"), time.Date(2014, 12, 15, 8, 0, 0, 0, time.UTC), "2014-12-15T08:00:00.000Z", "2014-12-15T08:00:00.000Z"}, {[]byte("2018-01-28T23:54Z"), time.Date(2018, 0o1, 28, 23, 54, 0, 0, time.UTC), "2018-01-28T23:54:00.000Z", "2018-01-28T23:54:00.000Z"}, {[]byte("2014-12-15T08:00:00.000Z"), time.Date(2014, 12, 15, 8, 0, 0, 0, time.UTC), "2014-12-15T08:00:00.000Z", "2014-12-15T08:00:00.000Z"}, {[]byte("2011-08-18T19:03:37.123000000+01:00"), time.Date(2011, 8, 18, 19, 3, 37, 123*1e6, p.Location()), "2011-08-18T19:03:37.123+01:00", "2011-08-18T18:03:37.123Z"}, {[]byte("2011-08-18T19:03:37.123000+0100"), time.Date(2011, 8, 18, 19, 3, 37, 123*1e6, p.Location()), "2011-08-18T19:03:37.123+01:00", "2011-08-18T18:03:37.123Z"}, {[]byte("2011-08-18T19:03:37.123+0100"), time.Date(2011, 8, 18, 19, 3, 37, 123*1e6, p.Location()), "2011-08-18T19:03:37.123+01:00", "2011-08-18T18:03:37.123Z"}, {[]byte("2014-12-15T19:30:20Z"), time.Date(2014, 12, 15, 19, 30, 20, 0, time.UTC), "2014-12-15T19:30:20.000Z", "2014-12-15T19:30:20.000Z"}, {[]byte("0001-01-01T00:00:00Z"), time.Time{}.UTC(), "0001-01-01T00:00:00.000Z", "0001-01-01T00:00:00.000Z"}, {[]byte(""), time.Unix(0, 0).UTC(), "1970-01-01T00:00:00.000Z", "1970-01-01T00:00:00.000Z"}, {[]byte(nil), time.Unix(0, 0).UTC(), "1970-01-01T00:00:00.000Z", "1970-01-01T00:00:00.000Z"}, } ) func TestNewDateTime(t *testing.T) { assert.EqualValues(t, time.Unix(0, 0).UTC(), NewDateTime()) } func TestIsZero(t *testing.T) { t.Run("time.Unix(100,5) should not be zero", func(t *testing.T) { assert.FalseT(t, DateTime(time.Unix(100, 5)).IsZero()) }) t.Run("NewDateTime() should not be zero", func(t *testing.T) { // time.Unix(0,0) does not produce a true zero value struct, // so this is expected to fail. assert.FalseT(t, NewDateTime().IsZero()) }) t.Run("MakeDateTime() should be zero", func(t *testing.T) { assert.TrueT(t, MakeDateTime().IsZero()) }) t.Run("empty DateTime should be zero", func(t *testing.T) { dt := DateTime{} assert.TrueT(t, dt.IsZero()) }) } func TestIsUnixZero(t *testing.T) { dt := NewDateTime() assert.TrueT(t, dt.IsUnixZero()) assert.NotEqualT(t, dt.IsZero(), dt.IsUnixZero()) // Test configuring UnixZero estLocation := time.FixedZone("EST", int((-5 * time.Hour).Seconds())) estUnixZero := time.Unix(0, 0).In(estLocation) UnixZero = estUnixZero assert.TrueT(t, DateTime(estUnixZero).IsUnixZero()) } func TestParseDateTime_errorCases(t *testing.T) { _, err := ParseDateTime("yada") require.Error(t, err) } // TestParseDateTime tests the full cycle: // parsing -> marshaling -> unmarshaling / scanning. func TestParseDateTime_fullCycle(t *testing.T) { for caseNum, example := range testCases { t.Logf("Case #%d", caseNum) parsed, err := ParseDateTime(example.str) require.NoError(t, err) assert.EqualValues(t, example.time, parsed) mt, err := parsed.MarshalText() require.NoError(t, err) assert.Equal(t, []byte(example.str), mt) if example.str != "" { v := IsDateTime(example.str) assert.TrueT(t, v) } else { t.Logf("IsDateTime() skipped for empty testcases") } pp := NewDateTime() err = pp.UnmarshalText(mt) require.NoError(t, err) assert.EqualValues(t, example.time, pp) pp = NewDateTime() err = pp.Scan(mt) require.NoError(t, err) assert.EqualT(t, DateTime(example.time), pp) } } func TestDateTime_IsDateTime_errorCases(t *testing.T) { v := IsDateTime("zor") assert.FalseT(t, v) v = IsDateTime("zorg") assert.FalseT(t, v) v = IsDateTime("zorgTx") assert.FalseT(t, v) v = IsDateTime("1972-12-31Tx") assert.FalseT(t, v) v = IsDateTime("1972-12-31T24:40:00.000Z") assert.FalseT(t, v) v = IsDateTime("1972-12-31T23:63:00.000Z") assert.FalseT(t, v) v = IsDateTime("1972-12-31T23:59:60.000Z") assert.FalseT(t, v) } func TestDateTime_UnmarshalText_errorCases(t *testing.T) { pp := NewDateTime() err := pp.UnmarshalText([]byte("yada")) require.Error(t, err) err = pp.UnmarshalJSON([]byte("yada")) require.Error(t, err) } func TestDateTime_UnmarshalText(t *testing.T) { for caseNum, example := range testCases { t.Logf("Case #%d", caseNum) pp := NewDateTime() err := pp.UnmarshalText(example.in) require.NoError(t, err) assert.EqualValues(t, example.time, pp) // Other way around val, erv := pp.Value() require.NoError(t, erv) assert.EqualValues(t, example.str, val) } } func TestDateTime_UnmarshalJSON(t *testing.T) { for caseNum, example := range testCases { t.Logf("Case #%d", caseNum) assert.JSONUnmarshalAsT(t, DateTime(example.time), string(esc(example.in))) } // Check UnmarshalJSON failure with no lexed items pp := NewDateTime() err := pp.UnmarshalJSON([]byte("zorg emperor")) require.Error(t, err) // Check lexer failure err = pp.UnmarshalJSON([]byte(`"zorg emperor"`)) require.Error(t, err) // Check null case err = pp.UnmarshalJSON([]byte("null")) require.NoError(t, err) } func esc(v []byte) []byte { var buf bytes.Buffer buf.WriteByte('"') buf.Write(v) buf.WriteByte('"') return buf.Bytes() } func TestDateTime_MarshalText(t *testing.T) { for caseNum, example := range testCases { t.Logf("Case #%d", caseNum) dt := DateTime(example.time) mt, err := dt.MarshalText() require.NoError(t, err) assert.Equal(t, []byte(example.str), mt) } } func TestDateTime_MarshalJSON(t *testing.T) { for caseNum, example := range testCases { t.Logf("Case #%d", caseNum) dt := DateTime(example.time) assert.JSONMarshalAsT(t, string(esc([]byte(example.str))), dt) } } func TestDateTime_MarshalJSON_Override(t *testing.T) { oldNormalizeMarshal := NormalizeTimeForMarshal defer func() { NormalizeTimeForMarshal = oldNormalizeMarshal }() NormalizeTimeForMarshal = func(t time.Time) time.Time { return t.UTC() } for caseNum, example := range testCases { t.Logf("Case #%d", caseNum) dt := DateTime(example.time.UTC()) assert.JSONMarshalAsT(t, string(esc([]byte(example.utcStr))), dt) } } func TestDateTime_Scan(t *testing.T) { for caseNum, example := range testCases { t.Logf("Case #%d", caseNum) pp := NewDateTime() err := pp.Scan(example.in) require.NoError(t, err) assert.EqualT(t, DateTime(example.time), pp) pp = NewDateTime() err = pp.Scan(string(example.in)) require.NoError(t, err) assert.EqualT(t, DateTime(example.time), pp) pp = NewDateTime() err = pp.Scan(example.time) require.NoError(t, err) assert.EqualT(t, DateTime(example.time), pp) } } func TestDateTime_Scan_Failed(t *testing.T) { pp := NewDateTime() zero := NewDateTime() err := pp.Scan(nil) require.NoError(t, err) // Zero values differ... // assert.Equal(t, zero, pp) assert.EqualT(t, DateTime{}, pp) err = pp.Scan("") require.NoError(t, err) assert.EqualT(t, zero, pp) err = pp.Scan(int64(0)) require.Error(t, err) err = pp.Scan(float64(0)) require.Error(t, err) } func TestDeepCopyDateTime(t *testing.T) { p, err := ParseDateTime("2011-08-18T19:03:37.000000000+01:00") require.NoError(t, err) in := &p out := new(DateTime) in.DeepCopyInto(out) assert.Equal(t, in, out) out2 := in.DeepCopy() assert.Equal(t, in, out2) var inNil *DateTime out3 := inNil.DeepCopy() assert.Nil(t, out3) } func TestGobEncodingDateTime(t *testing.T) { now := time.Now() b := bytes.Buffer{} enc := gob.NewEncoder(&b) err := enc.Encode(DateTime(now)) require.NoError(t, err) assert.NotEmpty(t, b.Bytes()) var result DateTime dec := gob.NewDecoder(&b) err = dec.Decode(&result) require.NoError(t, err) assert.EqualT(t, now.Year(), time.Time(result).Year()) assert.EqualT(t, now.Month(), time.Time(result).Month()) assert.EqualT(t, now.Day(), time.Time(result).Day()) assert.EqualT(t, now.Hour(), time.Time(result).Hour()) assert.EqualT(t, now.Minute(), time.Time(result).Minute()) assert.EqualT(t, now.Second(), time.Time(result).Second()) } func TestDateTime_Equal(t *testing.T) { t.Parallel() dt1 := DateTime(time.Now()) dt2 := DateTime(time.Time(dt1).Add(time.Second)) //nolint:gocritic assert.TrueT(t, dt1.Equal(dt1), "DateTime instances should be equal") assert.FalseT(t, dt1.Equal(dt2), "DateTime instances should not be equal") } go-openapi-strfmt-879971e/ulid.go000066400000000000000000000116431517436663600166760ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package strfmt import ( cryptorand "crypto/rand" "database/sql/driver" "encoding/json" "fmt" "io" "sync" "github.com/oklog/ulid/v2" ) // ULID represents a [ulid] string format. // // # Reference // // https://github.com/ulid/spec // // # Implementation // // https://github.com/oklog/ulid // // swagger:strfmt ulid. type ULID struct { ulid.ULID } //nolint:gochecknoglobals // package-level ULID configuration and overridable scan/value functions var ( ulidEntropyPool = sync.Pool{ New: func() any { return cryptorand.Reader }, } // ULIDScanDefaultFunc is the default implementation for scanning a [ULID] from a database driver value. ULIDScanDefaultFunc = func(raw any) (ULID, error) { u := 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 is the default implementation for converting a [ULID] to a database driver value. 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() { //nolint:gochecknoinits // registers ulid format in the default registry 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() (ULID, error) { var u ULID obj := ulidEntropyPool.Get() entropy, ok := obj.(io.Reader) if !ok { return u, fmt.Errorf("failed to cast %+v to io.Reader: %w", obj, ErrFormat) } 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() any { 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 any) 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 } // 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.[encoding.BinaryMarshaler] interface. func (u ULID) MarshalBinary() ([]byte, error) { return u.ULID.MarshalBinary() } // UnmarshalBinary implements the encoding.[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 } go-openapi-strfmt-879971e/ulid_test.go000066400000000000000000000164531517436663600177410ustar00rootroot00000000000000// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 package strfmt import ( "bytes" "database/sql/driver" "encoding/gob" "fmt" "sync" "testing" "github.com/go-openapi/testify/v2/assert" "github.com/go-openapi/testify/v2/require" ) const ( testUlid = string("01EYXZVGBHG26MFTG4JWR4K558") testUlidAlt = string("01EYXZW663G7PYHVSQ8WTMDA67") ) //nolint:gochecknoglobals // test fixtures var ( testUlidOverrideMtx sync.Mutex testUlidOverrideValMtx sync.Mutex ) func TestFormatULID_Text(t *testing.T) { t.Parallel() t.Run("positive", func(t *testing.T) { t.Parallel() ulid, err := ParseULID(testUlid) require.NoError(t, err) res, err := ulid.MarshalText() require.NoError(t, err) assert.EqualT(t, testUlid, string(res)) ulid2, _ := ParseULID(testUlidAlt) require.NoError(t, err) what := []byte(testUlid) err = ulid2.UnmarshalText(what) require.NoError(t, err) assert.EqualT(t, testUlid, ulid2.String()) }) t.Run("negative", func(t *testing.T) { t.Parallel() ulid, err := ParseULID(testUlid) require.NoError(t, err) what := []byte("00000000-0000-0000-0000-000000000000") err = ulid.UnmarshalText(what) require.Error(t, err) }) } func TestFormatULID_JSON(t *testing.T) { t.Parallel() t.Run("positive", func(t *testing.T) { t.Parallel() ulid, err := ParseULID(testUlid) require.NoError(t, err) whatStr := fmt.Sprintf(`"%s"`, testUlidAlt) what := []byte(whatStr) err = ulid.UnmarshalJSON(what) require.NoError(t, err) assert.EqualT(t, testUlidAlt, ulid.String()) assert.JSONMarshalAsT(t, whatStr, ulid) }) t.Run("null", func(t *testing.T) { t.Parallel() ulid, err := ParseULID(testUlid) require.NoError(t, err) err = ulid.UnmarshalJSON([]byte("null")) require.NoError(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")) require.Error(t, err) // Check lexer failure err = ulid.UnmarshalJSON([]byte(`"zorg emperor"`)) require.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) require.NoError(t, err) err = ulid.Scan(srcUlid) require.NoError(t, err) assert.EqualT(t, srcUlid, ulid.String()) ulid, _ = ParseULID(testUlid) err = ulid.Scan([]byte(srcUlid)) require.NoError(t, err) assert.EqualT(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() require.NoError(t, err) err = ulid.Scan(nil) require.NoError(t, err) assert.EqualT(t, zero, ulid) err = ulid.Scan("") require.NoError(t, err) assert.EqualT(t, zero, ulid) err = ulid.Scan(int64(0)) require.Error(t, err) err = ulid.Scan(float64(0)) require.Error(t, err) }) t.Run("db.Value", func(t *testing.T) { t.Parallel() testUlidOverrideValMtx.Lock() defer testUlidOverrideValMtx.Unlock() ulid, err := ParseULID(testUlid) require.NoError(t, err) val, err := ulid.Value() require.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) require.NoError(t, err) ulid2, err := ParseULID(testUlidAlt) require.NoError(t, err) ULIDScanOverrideFunc = func(raw any) (ULID, error) { u := 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: %w", raw, ErrFormat) } return u, fmt.Errorf("cannot sql.Scan() strfmt.ULID from: %#v: %w", raw, ErrFormat) } // get underlying binary implementation which is actually [16]byte bytes := [16]byte(ulid.ULID) err = ulid2.Scan(bytes) require.NoError(t, err) assert.EqualT(t, ulid2, ulid) assert.EqualT(t, ulid2.String(), ulid.String()) // check other default cases became unreachable err = ulid2.Scan(testUlid) require.Error(t, err) // return default Scan method ULIDScanOverrideFunc = ULIDScanDefaultFunc err = ulid2.Scan(testUlid) require.NoError(t, err) assert.EqualT(t, testUlid, ulid2.String()) }) t.Run("override.Value", func(t *testing.T) { t.Parallel() testUlidOverrideValMtx.Lock() defer testUlidOverrideValMtx.Unlock() ulid, err := ParseULID(testUlid) require.NoError(t, err) ulid2, err := ParseULID(testUlid) require.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() require.NoError(t, err) assert.EqualValues(t, exp, val) // return default Value method ULIDValueOverrideFunc = ULIDValueDefaultFunc val, err = ulid.Value() require.NoError(t, err) assert.EqualValues(t, testUlid, val) }) } func TestFormatULID_DeepCopy(t *testing.T) { ulid, err := ParseULID(testUlid) require.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) require.NoError(t, err) b := bytes.Buffer{} enc := gob.NewEncoder(&b) err = enc.Encode(ulid) require.NoError(t, err) assert.NotEmpty(t, b.Bytes()) var result ULID dec := gob.NewDecoder(&b) err = dec.Decode(&result) require.NoError(t, err) assert.EqualT(t, ulid, result) assert.EqualT(t, ulid.String(), result.String()) } func TestFormatULID_NewULID_and_Equal(t *testing.T) { t.Parallel() ulid1, err := NewULID() require.NoError(t, err) ulid2, err := NewULID() require.NoError(t, err) //nolint:gocritic assert.TrueT(t, ulid1.Equal(ulid1), "ULID instances should be equal") assert.FalseT(t, ulid1.Equal(ulid2), "ULID instances should not be equal") ulidZero := NewULIDZero() ulidZero2 := NewULIDZero() assert.TrueT(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.TrueT(t, IsULID(tc.ulid)) } else { assert.FalseT(t, IsULID(tc.ulid)) } }) } }