Skip to content
Open
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
123 changes: 123 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ With `oapi-codegen`, there are a few [Key Design Decisions](#key-design-decision
- [<code>additionalProperties</code> with an object](#additionalproperties-with-an-object)
- [Globally skipping the &quot;optional pointer&quot;](#globally-skipping-the-optional-pointer)
- [Changing the names of generated types](#changing-the-names-of-generated-types)
- [Changing the names of generated components](#changing-the-names-of-generated-components)
- [The prefix](#the-prefix)
- [Which names can be renamed](#which-names-can-be-renamed)
- [Collisions](#collisions)
- [Examples](#examples)
- [Blog posts](#blog-posts)
- [Frequently Asked Questions (FAQs)](#frequently-asked-questions-faqs)
Expand Down Expand Up @@ -1120,6 +1124,9 @@ From here, `oapi-codegen` will generate multiple Go files, all within the same p

Check out [the import-mapping/samepackage example](examples/import-mapping/samepackage) for the full code.

> [!NOTE]
> The self mapping shares a package between parts of *one* specification. If instead you want two unrelated specifications in one package, each needs its own set of generated component names, or `ServerInterface`, `Client`, `GetSwagger` and the rest will be declared twice. Give each generation run a distinct [`output-options.component-names.prefix`](#changing-the-names-of-generated-components).

### Using multiple packages, with one OpenAPI spec per package

To get `oapi-codegen`'s multi-package support working, we need to set up our directory structure:
Expand Down Expand Up @@ -2294,6 +2301,122 @@ type ClientInterface interface {

For more details of what the resulting code looks like, check out [the test cases](internal/test/options/name_normalizer/).

## Changing the names of generated components

The section above is about names that come from your OpenAPI specification. This section is about the other kind: the fixed identifiers that `oapi-codegen` emits regardless of the spec -- `ServerInterface`, `Client`, `GetSwagger`, `RequiredParamError`, and around fifty others. The `output-options.component-names` configuration renames them.

There are three reasons you might want to:

1. **Your spec collides with one of them.** A `components/schemas/Client` and a generated client struct are both `Client`. Renaming the component is often easier than renaming the schema.
2. **You want two specs in one Go package.** Every fixed name would be declared twice -- including the unexported ones (`swaggerSpec`, `rawSpec`, `decodeSpec`), which you cannot reach with `x-go-name`. Giving each generation run a `prefix` is a one-line fix.
3. **House style**, or clearer godoc for a published SDK.

```yaml
output-options:
component-names:
prefix: PetStore
client: API
server-interface: Server
strict-server-interface: StrictServer
handler: Mux
register-handlers: MountRoutes
unimplemented: NotImplementedYet
get-swagger: LoadSwagger
get-spec: LoadSpec
get-spec-json: LoadSpecJSON
middleware-func: Middleware
errors:
required-param-error: MissingQueryParam
required-header-error: MissingHeader
invalid-param-format-error: BadParamFormat
too-many-values-for-param-error: RepeatedParam
unmarshaling-param-error: BadParamJSON
unescaped-cookie-param-error: BadCookieEncoding
echo:
router: Router
stdhttp:
serve-mux: Router
fiber:
handler-middleware-func: HandlerMiddleware
```

Every key is optional, and every name must be a valid Go identifier. Whether a name is exported is your choice -- `oapi-codegen` will not second-guess it.

### The prefix

`prefix` is prepended to every resolved name. It is independent of the individual overrides: it applies to a name you renamed just as it applies to one you left alone. So the configuration above generates `PetStoreAPI`, not `API`, and `PetStoreServerInterfaceWrapper` even though nothing renamed the wrapper.

Unexported names -- `swaggerSpec`, `rawSpec`, `decodeSpec`, `decodeSpecCached`, `strictHandler` -- take the prefix with its first letter lowered, so they stay unexported: `petStoreSwaggerSpec`. This is what lets two generation runs share a Go package; see [`internal/test/naming/componentnames`](internal/test/naming/componentnames) for a worked example of exactly that.

The prefix must start with a letter.

### Which names can be renamed

Renaming a **root** renames everything derived from it, so you rarely need to name each member of a family:

| Root | Default | Names derived from it |
|---|---|---|
| `client` | `Client` | `ClientInterface`, `ClientOption`, `NewClient`, `ClientWithResponses`, `ClientWithResponsesInterface`, `NewClientWithResponses` |
| `server-interface` | `ServerInterface` | `ServerInterfaceWrapper` |
| `handler` | `Handler` | `HandlerFromMux`, `HandlerFromMuxWithBaseURL`, `HandlerWithOptions` |
| `register-handlers` | `RegisterHandlers` | `RegisterHandlersWithBaseURL`, `RegisterHandlersWithOptions`, `RegisterHandlersOptions` |
| `strict-server-interface` | `StrictServerInterface` | -- |
| `middleware-func` | `MiddlewareFunc` | -- |
| `unimplemented` | `Unimplemented` | -- |
| `get-swagger` / `get-spec` / `get-spec-json` | `GetSwagger` / `GetSpec` / `GetSpecJSON` | -- |
| `errors.*` | the six parameter-binding error types | -- |
| `echo.router` | `EchoRouter` | -- |
| `stdhttp.serve-mux` | `ServeMux` | -- |
| `fiber.handler-middleware-func` | `HandlerMiddlewareFunc` | -- |

The remaining fixed names have no key of their own and are renamed by `prefix` alone: `RequestEditorFn`, `HttpRequestDoer`, `WithHTTPClient`, `WithRequestEditorFn`, `WithBaseURL`, `StrictHandlerFunc`, `StrictMiddlewareFunc`, `NewStrictHandler`, `NewStrictHandlerWithOptions`, `strictHandler`, `StrictHTTPServerOptions`, `StrictGinServerOptions`, `PathToRawSpec`, `swaggerSpec`, `rawSpec`, `decodeSpec`, `decodeSpecCached`, and the per-framework `ChiServerOptions` / `GorillaServerOptions` / `StdHTTPServerOptions` / `GinServerOptions` / `FiberServerOptions` / `IrisServerOptions` structs. The webhook and callback initiator and receiver names (`WebhookInitiator`, `CallbackReceiverInterface`, ...) take the prefix in front of their existing `Webhook` / `Callback` prefix.

Adding a key later is easy; removing one is not, so the set starts small. If you need one that isn't here, please open an issue.

Two notes:

- `output-options.client-type-name` is **deprecated** in favour of `component-names.client`. It still works, and still does exactly what it always did: override the name of the client struct, leaving `ClientInterface`, `NewClient` and the rest of the family at their defaults.

The two are orthogonal rather than competing. `component-names.client` is the family *root* -- it is what the family derives from, and, absent the deprecated knob, the struct's name too. `client-type-name` is a struct-name *override*, applied after derivation. Setting both is therefore legitimate, though it produces mixed naming:

```yaml
output-options:
client-type-name: George
component-names:
client: APIClient
```

```go
type George struct { ... }
func NewAPIClient(server string, opts ...APIClientOption) (*George, error)
type APIClientInterface interface { ... }
```

which is useful while migrating -- your callers keep referring to the old struct name -- but rarely what you want to end up with, so `oapi-codegen` warns when both are set. A `client-type-name` that collides with a name derived from the root (`client-type-name: APIClientInterface` alongside `client: APIClient`) is a configuration error.
- `output-options.response-type-suffix` is a different mechanism and is unaffected: it is a suffix applied to spec-derived response type names, not a component name.

If you override the built-in templates, nothing changes for you: your templates keep working untouched. To honour renames in your own templates, interpolate the same fields, e.g. `{{names.ServerInterface}}` (or `{{opts.OutputOptions.ComponentNames.ServerInterface}}`).

> [!IMPORTANT]
> When one spec `$ref`s another through [import-mapping](#splitting-large-openapi-specs-across-multiple-packages-aka-import-mapping-or-external-references), all the specs involved **must be generated with the same `component-names` settings, `prefix` included**. The generated code calls across package boundaries by name -- `<package>.PathToRawSpec`, when resolving external references in the embedded spec -- and each configuration names those functions from its own settings. If the settings disagree, the referencing package will call a name the referenced package does not export, and the generated code will fail to compile with an "undefined" error.
>
> This is a requirement rather than a limitation. Import-mapping exists to split the generated boilerplate of what is conceptually one monolithic spec across several Go packages: the pieces are a single API, so they should be generated consistently, and `component-names` -- the prefix especially -- is part of that consistency. If you genuinely need individual names to diverge across the split, the per-schema [`x-go-name` and `x-go-type` extensions](#openapi-extensions) remain the escape hatch; identical settings everywhere is the supported and expected configuration.

### Collisions

Two components resolving to the same identifier is a configuration error, reported before any code is generated.

So is a schema resolving to the name of a component that the same configuration declares:

```
type name 'Client' collides with the generated client component, which is declared by this
configuration. Either use x-go-name on the schema to rename the type, or use
output-options.component-names (its `prefix` renames every component at once) to rename the
component
```

Only the names a configuration actually emits take part: a schema called `Client` in a `models`-only configuration is fine, because that configuration declares no client.

## Examples

The [examples directory](examples) contains some additional cases which are useful examples for how to use `oapi-codegen`, including how you'd take the Petstore API and implement it with `oapi-codegen`.
Expand Down
117 changes: 116 additions & 1 deletion configuration-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,122 @@
},
"client-type-name": {
"type": "string",
"description": "Override the default generated client type with the value"
"description": "Deprecated: use `output-options.component-names.client`, the family root, which renames the whole client family instead of only the client struct. Override the default generated client type with the value. The two are orthogonal: this is a struct-name override applied after derivation, so setting both names the struct after this key while the rest of the family derives from `component-names.client`"
},
"component-names": {
"type": "object",
"additionalProperties": false,
"description": "ComponentNames customizes the fixed, spec-independent package-level identifiers that are generated (ServerInterface, Client, GetSwagger, the parameter-binding error types, ...). Resolution order is: default, then the explicit override below, then `prefix` prepended to the result. Renaming a root renames the names derived from it",
"properties": {
"prefix": {
"type": "string",
"description": "Prepended to every resolved component name, defaulted or overridden alike. Must be a valid Go identifier starting with a letter. Unexported names (swaggerSpec, rawSpec, decodeSpec, decodeSpecCached, strictHandler) take the prefix with its first letter lowered so they stay unexported. This is what allows two generation runs to share one Go package"
},
"client": {
"type": "string",
"description": "The generated HTTP client struct (default `Client`). Root of the client family: ClientInterface, ClientOption, NewClient, ClientWithResponses, ClientWithResponsesInterface and NewClientWithResponses derive from it"
},
"server-interface": {
"type": "string",
"description": "The generated server interface (default `ServerInterface`). `ServerInterfaceWrapper` derives from it"
},
"middleware-func": {
"type": "string",
"description": "The per-framework middleware type (default `MiddlewareFunc`). Not emitted by the echo generators, which use echo.MiddlewareFunc"
},
"handler": {
"type": "string",
"description": "Root of the net/http-family (chi, gorilla, std-http) handler constructors (default `Handler`). HandlerFromMux, HandlerFromMuxWithBaseURL and HandlerWithOptions derive from it"
},
"unimplemented": {
"type": "string",
"description": "The chi-only stub server implementation (default `Unimplemented`)"
},
"register-handlers": {
"type": "string",
"description": "Root of the route-registration family emitted by echo, gin, fiber and iris (default `RegisterHandlers`). RegisterHandlersWithBaseURL, RegisterHandlersWithOptions and RegisterHandlersOptions derive from it"
},
"strict-server-interface": {
"type": "string",
"description": "The strict-mode server interface (default `StrictServerInterface`)"
},
"get-swagger": {
"type": "string",
"description": "The deprecated embedded-spec accessor (default `GetSwagger`)"
},
"get-spec": {
"type": "string",
"description": "The embedded-spec accessor (default `GetSpec`)"
},
"get-spec-json": {
"type": "string",
"description": "The raw embedded-spec accessor (default `GetSpecJSON`)"
},
"errors": {
"type": "object",
"additionalProperties": false,
"description": "The parameter-binding error types handed to ErrorHandlerFunc by the net/http-family (chi, gorilla, std-http) server wrappers",
"properties": {
"required-param-error": {
"type": "string",
"description": "Default `RequiredParamError`"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use ie

Suggested change
"description": "Default `RequiredParamError`"
"default": "RequiredParamError"

Or does this mean "the default for the RequiredParamError's value?

},
"required-header-error": {
"type": "string",
"description": "Default `RequiredHeaderError`"
},
"invalid-param-format-error": {
"type": "string",
"description": "Default `InvalidParamFormatError`"
},
"too-many-values-for-param-error": {
"type": "string",
"description": "Default `TooManyValuesForParamError`"
},
"unmarshaling-param-error": {
"type": "string",
"description": "Default `UnmarshalingParamError`"
},
"unescaped-cookie-param-error": {
"type": "string",
"description": "Default `UnescapedCookieParamError`"
}
}
},
"echo": {
"type": "object",
"additionalProperties": false,
"description": "echo-specific generated types",
"properties": {
"router": {
"type": "string",
"description": "The router interface accepted by RegisterHandlers (default `EchoRouter`)"
}
}
},
"stdhttp": {
"type": "object",
"additionalProperties": false,
"description": "std-http-specific generated types",
"properties": {
"serve-mux": {
"type": "string",
"description": "The interface abstracting http.ServeMux (default `ServeMux`)"
}
}
},
"fiber": {
"type": "object",
"additionalProperties": false,
"description": "fiber-specific generated types",
"properties": {
"handler-middleware-func": {
"type": "string",
"description": "The per-handler middleware type supplied via FiberServerOptions.HandlerMiddlewares (default `HandlerMiddlewareFunc`)"
}
}
}
}
},
"additional-initialisms": {
"type": "array",
Expand Down
2 changes: 1 addition & 1 deletion examples/custom-client-type/custom-client-type.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions examples/custom-client-type/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,11 @@ package customclienttype

// This is an example of how to add a prefix to the name of the generated Client struct
// See https://github.com/oapi-codegen/oapi-codegen/issues/785 for why this might be necessary
//
// NOTE that `client-type-name` is deprecated in favour of
// `output-options.component-names.client`, which renames the whole client
// family (ClientInterface, NewClient, ClientWithResponses, ...) rather than
// only the Client struct. This example is kept as-is to pin the behaviour of
// the deprecated option.

//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml api.yaml
2 changes: 1 addition & 1 deletion examples/minimal-server/iris/api/ping.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion internal/test/events/webhooks/iris/webhooks.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading