Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 32 additions & 20 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,37 +19,44 @@ jobs:
# Quality Gate
# ========================================
quality:
name: 🛡️ Quality Gate
name: 🛡️ Quality Gate / ${{ matrix.module }}
runs-on: ubuntu-latest
timeout-minutes: 10

strategy:
fail-fast: false
matrix:
module:
- .
- adapter/chiopenapi
- adapter/echoopenapi
- adapter/echov5openapi
- adapter/fiberopenapi
- adapter/fiberv3openapi
- adapter/ginopenapi
- adapter/httpopenapi
- adapter/irisopenapi
- adapter/muxopenapi

steps:
- name: 📥 Checkout
uses: actions/checkout@v4

- name: 🐹 Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.25.x' # Use latest supported toolchain for quality checks
check-latest: true # Always grab the newest patch release
cache: true # Handles ~/.cache/go-build and ~/go/pkg/mod

- name: 📁 Cache Tools
uses: actions/cache@v4
with:
path: |
~/.cache/golangci-lint
~/go/bin
key: ${{ runner.os }}-tools-${{ env.CACHE_VERSION }}-${{ hashFiles('tools/tools.go', 'Makefile') }}
restore-keys: |
${{ runner.os }}-tools-${{ env.CACHE_VERSION }}-
${{ runner.os }}-tools-
go-version: '1.25.x'
check-latest: true
cache: true

- name: 🔧 Install Tools
run: make install-tools
- name: ✅ Sync & Tidy
run: make sync tidy

- name: ✅ Run Quality Checks
run: make sync tidy lint
- name: 🔍 Lint
uses: golangci/golangci-lint-action@v9
with:
version: v2.12.2
working-directory: ${{ matrix.module }}

# ========================================
# Test Matrix
Expand Down Expand Up @@ -119,7 +126,12 @@ jobs:
- name: Run Gosec Security Scanner
uses: securego/gosec@v2.26.1
with:
args: './...'
args: '-no-fail -fmt sarif -out results.sarif ./...'

- name: Upload SARIF to GitHub
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: results.sarif

# ========================================
# Final Status Check
Expand Down
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.5.1] - Unreleased

### Added
- Logger debug feature for tracing spec generation behavior.
- `InterceptSchema` and `InterceptProp` reflector hooks for customizing schema and property generation.
- `RequiredPropByValidateTag` hook for marking fields required based on `validate` struct tags.
- `ParentType` field on `InterceptPropParams` for richer hook context.
- Automatic content-type detection from struct type when not explicitly set.
- `encoding.TextMarshaler`/`TextUnmarshaler` support: types implementing both interfaces (without `json.Marshaler`) are reflected as `type: string`.
- `EmbedReferencer` interface and `refer:"true"` struct tag: embedded structs opt into `allOf $ref` instead of field inlining.
- `ParameterTagMapping` now accepts `openapi.ParameterInBody` and `openapi.ParameterInForm` to override the struct tag used for JSON and form request body field names (defaults: `json` and `form`).

### Changed
- Schema component names now always include the Go package name as a prefix (e.g., `models.User` → `ModelsUser`). This eliminates cross-package naming collisions without requiring caller-package detection. Use `InterceptDefName` or `StripDefNamePrefix` to remove the prefix if desired.
- `DefNameCallerPkg` field removed from `ReflectorConfig`; the caller-package detection mechanism in `NewGenerator` is removed.
- Package name sanitization handles multi-segment names (e.g., `spec_test` → `SpecTest`); unexported type names are title-cased when a package prefix is prepended.

### Fixed
- `uint8`/`uint16` reflected as `int32` format; `uint`/`uint32`/`uint64`/`uintptr` reflected as `int64` format.
- `InterceptSchema`/`InterceptProp` hook error propagation and correctness.
- `RefSchema` pre-hook: assign `StructSchema` fields onto existing pointer so pre-hook customizations (extensions, description) survive to the post-hook.
- `ApplyNullable` (OAS 3.1+): merge `"null"` into an existing `[]string` type slice instead of silently skipping.
- `Required` field deduplication to prevent duplicates when both `required` struct tag and `RequiredPropByValidateTag` are used simultaneously.
- `parentSnapshot` restore on `ErrSkipProperty` in post-hooks to roll back parent schema mutations (`AllOf`, `AnyOf`, `OneOf`, extensions).
- Lint violations and YAML tag reading in `MarshalYAML`.

## [0.5.0] - 2026-05-11

### Added
Expand Down Expand Up @@ -78,6 +104,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Updated adapter dependency versions.

[0.5.1]: https://github.com/oaswrap/spec/compare/v0.5.0...HEAD
[0.5.0]: https://github.com/oaswrap/spec/compare/v0.4.2...v0.5.0
[0.4.2]: https://github.com/oaswrap/spec/compare/v0.4.1...v0.4.2
[0.4.1]: https://github.com/oaswrap/spec/compare/v0.4.0...v0.4.1
Expand Down
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ Code-first, framework-agnostic OpenAPI 3.x spec builder for Go. Generate docs fr
- `InterceptSchema` hook for type-level schema customization and override.
- `InterceptProp` hook for field-level property filtering and modification.
- `RequiredPropByValidateTag` option to derive `required` from `validate` struct tags.
- `encoding.TextMarshaler`/`TextUnmarshaler` types automatically reflected as `type: string`.
- `EmbedReferencer` interface and `refer:"true"` tag for embedded struct `allOf $ref` instead of field inlining.

---

Expand Down Expand Up @@ -438,6 +440,7 @@ type SearchRequest struct {
| `map[string]T` | `type: object`, `additionalProperties: T` |
| Structs | `type: object`, `properties` |
| Named structs (component mode) | `#/components/schemas/{TypeName}` reference |
| `encoding.TextMarshaler` + `TextUnmarshaler` (no `json.Marshaler`) | `type: string` |
| Pointers | Nullable schema behavior |

Custom types can expose their own schema when tags are not expressive enough:
Expand All @@ -455,6 +458,36 @@ func (*Slug) OpenAPISchema(version string) *openapi.Schema {

For static schemas, implement `OpenAPISchema() *openapi.Schema` instead. Field tags are still applied on top of custom schemas.

### Embedded struct references

By default, anonymous embedded struct fields are inlined into the parent schema. To emit an `allOf $ref` instead, use the `refer:"true"` tag or implement `openapi.EmbedReferencer` on the embedded type:

```go
// Via struct tag
type Request struct {
Base `refer:"true"`
Name string `json:"name"`
}

// Via interface (useful when you own the embedded type)
type Base struct {
ID int `json:"id"`
}

func (Base) ReferEmbedded() {}
```

Both produce:
```yaml
Request:
type: object
properties:
name:
type: string
allOf:
- $ref: '#/components/schemas/Base'
```

---

## Reflector Configuration
Expand Down Expand Up @@ -491,7 +524,7 @@ r := spec.NewRouter(
| `InlineRefs(inline...)` | Inline schemas instead of using component references for named structs. |
| `StripDefNamePrefix(prefixes...)` | Strip prefixes from generated component names. |
| `TypeMapping(src, dst)` | Reflect `src` as if it were `dst`. |
| `ParameterTagMapping(in, sourceTag)` | Add a custom tag for a parameter location while keeping the default tag. |
| `ParameterTagMapping(in, sourceTag)` | Override the struct tag for a parameter location or body. Use `openapi.ParameterInBody` to replace `json` tag, `openapi.ParameterInForm` to replace `form` tag. |
| `InterceptDefName(fn)` | Customize schema component names. |
| `InterceptSchema(fn)` | Hook called before and after each type is reflected. Pre-call (`Processed=false`): return `stop=true` to override schema entirely. Post-call (`Processed=true`): modify the built schema. |
| `InterceptProp(fn)` | Hook called before and after each struct field is reflected. Return `openapi.ErrSkipProperty` to exclude the field. |
Expand Down Expand Up @@ -585,3 +618,9 @@ Issues and pull requests are welcome. Please check existing issues and discussio
## License

[MIT](LICENSE)

---

## Acknowledgements

- [swaggest/openapi-go](https://github.com/swaggest/openapi-go) and [swaggest/jsonschema-go](https://github.com/swaggest/jsonschema-go) — parts of this library's reflection design are inspired by their work.
16 changes: 16 additions & 0 deletions adapter/chiopenapi/testdata/petstore.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,22 @@ paths:
schema:
type: integer
format: int32
requestBody:
content:
application/x-www-form-urlencoded:
schema:
type: object
required:
- name
properties:
name:
type: string
status:
type: string
enum:
- available
- pending
- sold
responses:
'200':
description: OK
Expand Down
16 changes: 16 additions & 0 deletions adapter/echoopenapi/testdata/petstore.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,22 @@ paths:
schema:
type: integer
format: int32
requestBody:
content:
application/x-www-form-urlencoded:
schema:
type: object
required:
- name
properties:
name:
type: string
status:
type: string
enum:
- available
- pending
- sold
responses:
'200':
description: OK
Expand Down
16 changes: 16 additions & 0 deletions adapter/echov5openapi/testdata/petstore.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,22 @@ paths:
schema:
type: integer
format: int32
requestBody:
content:
application/x-www-form-urlencoded:
schema:
type: object
required:
- name
properties:
name:
type: string
status:
type: string
enum:
- available
- pending
- sold
responses:
'200':
description: OK
Expand Down
16 changes: 16 additions & 0 deletions adapter/fiberopenapi/testdata/petstore.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,22 @@ paths:
schema:
type: integer
format: int32
requestBody:
content:
application/x-www-form-urlencoded:
schema:
type: object
required:
- name
properties:
name:
type: string
status:
type: string
enum:
- available
- pending
- sold
responses:
'200':
description: OK
Expand Down
Loading
Loading