diff --git a/.claude/.gitignore b/.claude/.gitignore new file mode 100644 index 0000000..dfce199 --- /dev/null +++ b/.claude/.gitignore @@ -0,0 +1,4 @@ +commands/ +hooks/ +plans/ +skills/ diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 0000000..5cd1d54 --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1,93 @@ +# 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. + diff --git a/.claude/rules/comments.md b/.claude/rules/comments.md new file mode 100644 index 0000000..4045d5a --- /dev/null +++ b/.claude/rules/comments.md @@ -0,0 +1,27 @@ +--- +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] ... +``` diff --git a/.claude/rules/contributions.md b/.claude/rules/contributions.md new file mode 100644 index 0000000..58027b9 --- /dev/null +++ b/.claude/rules/contributions.md @@ -0,0 +1,52 @@ +--- +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 ./... +``` diff --git a/.claude/rules/go-conventions.md b/.claude/rules/go-conventions.md new file mode 100644 index 0000000..9c2c924 --- /dev/null +++ b/.claude/rules/go-conventions.md @@ -0,0 +1,11 @@ +--- +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`) diff --git a/.claude/rules/linting.md b/.claude/rules/linting.md new file mode 100644 index 0000000..a4456d4 --- /dev/null +++ b/.claude/rules/linting.md @@ -0,0 +1,17 @@ +--- +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. diff --git a/.claude/rules/testing.md b/.claude/rules/testing.md new file mode 100644 index 0000000..6974aba --- /dev/null +++ b/.claude/rules/testing.md @@ -0,0 +1,47 @@ +--- +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. diff --git a/.github/copilot b/.github/copilot new file mode 120000 index 0000000..5269483 --- /dev/null +++ b/.github/copilot @@ -0,0 +1 @@ +../.claude/rules \ No newline at end of file diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..bc77d3a --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,73 @@ +# 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. diff --git a/.gitignore b/.gitignore index 885dc27..d8f4186 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,3 @@ .idea .env .mcp.json -.claude/ diff --git a/AGENTS.md b/AGENTS.md new file mode 120000 index 0000000..02dd134 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +.github/copilot-instructions.md \ No newline at end of file