1OpenAPI Client and Server Code Generator 2---------------------------------------- 3 4This package contains a set of utilities for generating Go boilerplate code for 5services based on 6[OpenAPI 3.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md) 7API definitions. When working with services, it's important to have an API 8contract which servers and clients both implement to minimize the chances of 9incompatibilities. It's tedious to generate Go models which precisely correspond to 10OpenAPI specifications, so let our code generator do that work for you, so that 11you can focus on implementing the business logic for your service. 12 13We have chosen to use [Echo](https://github.com/labstack/echo) as 14our default HTTP routing engine, due to its speed and simplicity for the generated 15stubs, and [Chi](https://github.com/go-chi/chi) is also supported as an alternative. 16 17This package tries to be too simple rather than too generic, so we've made some 18design decisions in favor of simplicity, knowing that we can't generate strongly 19typed Go code for all possible OpenAPI Schemas. 20 21## Overview 22 23We're going to use the OpenAPI example of the 24[Expanded Petstore](https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v3.0/petstore-expanded.yaml) 25in the descriptions below, please have a look at it. 26 27In order to create a Go server to serve this exact schema, you would have to 28write a lot of boilerplate code to perform all the marshalling and unmarshalling 29into objects which match the OpenAPI 3.0 definition. The code generator in this 30directory does a lot of that for you. You would run it like so: 31 32 go get github.com/deepmap/oapi-codegen/cmd/oapi-codegen 33 oapi-codegen petstore-expanded.yaml > petstore.gen.go 34 35Let's go through that `petstore.gen.go` file to show you everything which was 36generated. 37 38 39## Generated Server Boilerplate 40 41The `/components/schemas` section in OpenAPI defines reusable objects, so Go 42types are generated for these. The Pet Store example defines `Error`, `Pet`, 43`Pets` and `NewPet`, so we do the same in Go: 44```go 45// Type definition for component schema "Error" 46type Error struct { 47 Code int32 `json:"code"` 48 Message string `json:"message"` 49} 50 51// Type definition for component schema "NewPet" 52type NewPet struct { 53 Name string `json:"name"` 54 Tag *string `json:"tag,omitempty"` 55} 56 57// Type definition for component schema "Pet" 58type Pet struct { 59 // Embedded struct due to allOf(#/components/schemas/NewPet) 60 NewPet 61 // Embedded fields due to inline allOf schema 62 Id int64 `json:"id"` 63} 64 65// Type definition for component schema "Pets" 66type Pets []Pet 67``` 68 69It's best to define objects under `/components` field in the schema, since 70those will be turned into named Go types. If you use inline types in your 71handler definitions, we will generate inline, anonymous Go types, but those 72are more tedious to deal with since you will have to redeclare them at every 73point of use. 74 75For each element in the `paths` map in OpenAPI, we will generate a Go handler 76function in an interface object. Here is the generated Go interface for our 77Echo server. 78 79```go 80type ServerInterface interface { 81 // (GET /pets) 82 FindPets(ctx echo.Context, params FindPetsParams) error 83 // (POST /pets) 84 AddPet(ctx echo.Context) error 85 // (DELETE /pets/{id}) 86 DeletePet(ctx echo.Context, id int64) error 87 // (GET /pets/{id}) 88 FindPetById(ctx echo.Context, id int64) error 89} 90``` 91 92These are the functions which you will implement yourself in order to create 93a server conforming to the API specification. Normally, all the arguments and 94parameters are stored on the `echo.Context` in handlers, so we do the tedious 95work of of unmarshaling the JSON automatically, simply passing values into 96your handlers. 97 98Notice that `FindPetById` takes a parameter `id int64`. All path arguments 99will be passed as arguments to your function, since they are mandatory. 100 101Remaining arguments can be passed in headers, query arguments or cookies. Those 102will be written to a `params` object. Look at the `FindPets` function above, it 103takes as input `FindPetsParams`, which is defined as follows: 104 ```go 105// Parameters object for FindPets 106type FindPetsParams struct { 107 Tags *[]string `json:"tags,omitempty"` 108 Limit *int32 `json:"limit,omitempty"` 109} 110``` 111 112The HTTP query parameter `limit` turns into a Go field named `Limit`. It is 113passed by pointer, since it is an optional parameter. If the parameter is 114specified, the pointer will be non-`nil`, and you can read its value. 115 116If you changed the OpenAPI specification to make the parameter required, the 117`FindPetsParams` structure will contain the type by value: 118```go 119type FindPetsParams struct { 120 Tags *[]string `json:"tags,omitempty"` 121 Limit int32 `json:"limit"` 122} 123``` 124 125### Registering handlers 126There are a few ways of registering your http handler based on the type of server generated i.e. `-generate server` or `-generate chi-server` 127 128<details><summary><code>Echo</code></summary> 129 130Code generated using `-generate server`. 131 132The usage of `Echo` is out of scope of this doc, but once you have an 133echo instance, we generate a utility function to help you associate your handlers 134with this autogenerated code. For the pet store, it looks like this: 135```go 136func RegisterHandlers(router codegen.EchoRouter, si ServerInterface) { 137 wrapper := ServerInterfaceWrapper{ 138 Handler: si, 139 } 140 router.GET("/pets", wrapper.FindPets) 141 router.POST("/pets", wrapper.AddPet) 142 router.DELETE("/pets/:id", wrapper.DeletePet) 143 router.GET("/pets/:id", wrapper.FindPetById) 144} 145``` 146 147The wrapper functions referenced above contain generated code which pulls 148parameters off the `Echo` request context, and unmarshals them into Go objects. 149 150You would register the generated handlers as follows: 151```go 152func SetupHandler() { 153 var myApi PetStoreImpl // This implements the pet store interface 154 e := echo.New() 155 petstore.RegisterHandlers(e, &myApi) 156 ... 157} 158``` 159 160</summary></details> 161 162<details><summary><code>Chi</code></summary> 163 164Code generated using `-generate chi-server`. 165 166```go 167type PetStoreImpl struct {} 168func (*PetStoreImpl) GetPets(r *http.Request, w *http.ResponseWriter) { 169 // Implement me 170} 171 172func SetupHandler() { 173 var myApi PetStoreImpl 174 175 r := chi.Router() 176 r.Mount("/", Handler(&myApi)) 177} 178``` 179</summary></details> 180 181<details><summary><code>net/http</code></summary> 182 183[Chi](https://github.com/go-chi/chi) is 100% compatible with `net/http` allowing the following with code generated using `-generate chi-server`. 184 185```go 186type PetStoreImpl struct {} 187func (*PetStoreImpl) GetPets(r *http.Request, w *http.ResponseWriter) { 188 // Implement me 189} 190 191func SetupHandler() { 192 var myApi PetStoreImpl 193 194 http.Handle("/", Handler(&myApi)) 195} 196``` 197</summary></details> 198 199#### Additional Properties in type definitions 200 201[OpenAPI Schemas](https://swagger.io/specification/#schemaObject) implicitly 202accept `additionalProperties`, meaning that any fields provided, but not explicitly 203defined via properties on the schema are accepted as input, and propagated. When 204unspecified, the `additionalProperties` field is assumed to be `true`. 205 206Additional properties are tricky to support in Go with typing, and require 207lots of boilerplate code, so in this library, we assume that `additionalProperties` 208defaults to `false` and we don't generate this boilerplate. If you would like 209an object to accept `additionalProperties`, specify a schema for `additionalProperties`. 210 211Say we declared `NewPet` above like so: 212```yaml 213 NewPet: 214 required: 215 - name 216 properties: 217 name: 218 type: string 219 tag: 220 type: string 221 additionalProperties: 222 type: string 223``` 224 225The Go code for `NewPet` would now look like this: 226```go 227// NewPet defines model for NewPet. 228type NewPet struct { 229 Name string `json:"name"` 230 Tag *string `json:"tag,omitempty"` 231 AdditionalProperties map[string]string `json:"-"` 232} 233``` 234 235The additionalProperties, of type `string` become `map[string]string`, which maps 236field names to instances of the `additionalProperties` schema. 237```go 238// Getter for additional properties for NewPet. Returns the specified 239// element and whether it was found 240func (a NewPet) Get(fieldName string) (value string, found bool) {...} 241 242// Setter for additional properties for NewPet 243func (a *NewPet) Set(fieldName string, value string) {...} 244 245// Override default JSON handling for NewPet to handle additionalProperties 246func (a *NewPet) UnmarshalJSON(b []byte) error {...} 247 248// Override default JSON handling for NewPet to handle additionalProperties 249func (a NewPet) MarshalJSON() ([]byte, error) {...}w 250``` 251 252There are many special cases for `additionalProperties`, such as having to 253define types for inner fields which themselves support additionalProperties, and 254all of them are tested via the `internal/test/components` schemas and tests. Please 255look through those tests for more usage examples. 256 257## Generated Client Boilerplate 258 259Once your server is up and running, you probably want to make requests to it. If 260you're going to do those requests from your Go code, we also generate a client 261which is conformant with your schema to help in marshaling objects to JSON. It 262uses the same types and similar function signatures to your request handlers. 263 264The interface for the pet store looks like this: 265 266```go 267// The interface specification for the client above. 268type ClientInterface interface { 269 270 // FindPets request 271 FindPets(ctx context.Context, params *FindPetsParams, reqEditors ...RequestEditorFn) (*http.Response, error) 272 273 // AddPet request with JSON body 274 AddPet(ctx context.Context, body NewPet, reqEditors ...RequestEditorFn) (*http.Response, error) 275 276 // DeletePet request 277 DeletePet(ctx context.Context, id int64, reqEditors ...RequestEditorFn) (*http.Response, error) 278 279 // FindPetById request 280 FindPetById(ctx context.Context, id int64, reqEditors ...RequestEditorFn) (*http.Response, error) 281} 282``` 283 284A Client object which implements the above interface is also generated: 285 286```go 287// Client which conforms to the OpenAPI3 specification for this service. 288type Client struct { 289 // The endpoint of the server conforming to this interface, with scheme, 290 // https://api.deepmap.com for example. 291 Server string 292 293 // HTTP client with any customized settings, such as certificate chains. 294 Client http.Client 295 296 // A callback for modifying requests which are generated before sending over 297 // the network. 298 RequestEditors []func(ctx context.Context, req *http.Request) error 299} 300``` 301 302Each operation in your OpenAPI spec will result in a client function which 303takes the same arguments. It's difficult to handle any arbitrary body that 304Swagger supports, so we've done some special casing for bodies, and you may get 305more than one function for an operation with a request body. 306 3071) If you have more than one request body type, meaning more than one media 308 type, you will have a generic handler of this form: 309 310 AddPet(ctx context.Context, contentType string, body io.Reader) 311 3122) If you have only a JSON request body, you will get: 313 314 AddPet(ctx context.Context, body NewPet) 315 3163) If you have multiple request body types, which include a JSON type you will 317 get two functions. We've chosen to give the JSON version a shorter name, as 318 we work with JSON and don't want to wear out our keyboards. 319 320 AddPet(ctx context.Context, body NewPet) 321 AddPetWithBody(ctx context.Context, contentType string, body io.Reader) 322 323The Client object above is fairly flexible, since you can pass in your own 324`http.Client` and a request editing callback. You can use that callback to add 325headers. In our middleware stack, we annotate the context with additional 326information such as the request ID and function tracing information, and we 327use the callback to propagate that information into the request headers. Still, we 328can't foresee all possible usages, so those functions call through to helper 329functions which create requests. In the case of the pet store, we have: 330 331```go 332// Request generator for FindPets 333func NewFindPetsRequest(server string, params *FindPetsParams) (*http.Request, error) {...} 334 335// Request generator for AddPet with JSON body 336func NewAddPetRequest(server string, body NewPet) (*http.Request, error) {...} 337 338// Request generator for AddPet with non-JSON body 339func NewAddPetRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) {...} 340 341// Request generator for DeletePet 342func NewDeletePetRequest(server string, id int64) (*http.Request, error) {...} 343 344// Request generator for FindPetById 345func NewFindPetByIdRequest(server string, id int64) (*http.Request, error) {...} 346``` 347 348You can call these functions to build an `http.Request` from Go objects, which 349will correspond to your request schema. They map one-to-one to the functions on 350the client, except that we always generate the generic non-JSON body handler. 351 352There are some caveats to using this code. 353- exploded, form style query arguments, which are the default argument format 354 in OpenAPI 3.0 are undecidable. Say that I have two objects, one composed of 355 the fields `(name=bob, id=5)` and another which has `(name=shoe, color=brown)`. 356 The first parameter is named `person` and the second is named `item`. The 357 default marshaling style for query args would result in 358 `/path/?name=bob,id=5&name=shoe,color=brown`. In order to tell what belongs 359 to which object, we'd have to look at all the parameters and try to deduce it, 360 but we're lazy, so we didn't. Don't use exploded form style arguments if 361 you're passing around objects which have similar field names. If you 362 used unexploded form parameters, you'd have 363 `/path/?person=name,bob,id,5&item=name,shoe,color,brown`, which an be 364 parsed unambiguously. 365 366- Parameters can be defined via `schema` or via `content`. Use the `content` form 367 for anything other than trivial objects, they can marshal to arbitrary JSON 368 structures. When you send them as cookie (`in: cookie`) arguments, we will 369 URL encode them, since JSON delimiters aren't allowed in cookies. 370 371## Using SecurityProviders 372 373If you generate client-code, you can use some default-provided security providers 374which help you to use the various OpenAPI 3 Authentication mechanism. 375 376 377``` 378 import ( 379 "github.com/deepmap/oapi-codegen/pkg/securityprovider" 380 ) 381 382 func CreateSampleProviders() error { 383 // Example BasicAuth 384 // See: https://swagger.io/docs/specification/authentication/basic-authentication/ 385 basicAuthProvider, basicAuthProviderErr := securityprovider.NewSecurityProviderBasicAuth("MY_USER", "MY_PASS") 386 if basicAuthProviderErr != nil { 387 panic(basicAuthProviderErr) 388 } 389 390 // Example BearerToken 391 // See: https://swagger.io/docs/specification/authentication/bearer-authentication/ 392 bearerTokenProvider, bearerTokenProviderErr := securityprovider.NewSecurityProviderBearerToken("MY_TOKEN") 393 if bearerTokenProviderErr != nil { 394 panic(bearerTokenProviderErr) 395 } 396 397 // Example ApiKey provider 398 // See: https://swagger.io/docs/specification/authentication/api-keys/ 399 apiKeyProvider, apiKeyProviderErr := securityprovider.NewSecurityProviderApiKey("query", "myApiKeyParam", "MY_API_KEY") 400 if apiKeyProviderErr != nil { 401 panic(apiKeyProviderErr) 402 } 403 404 // Example providing your own provider using an anonymous function wrapping in the 405 // InterceptoFn adapter. The behaviour between the InterceptorFn and the Interceptor interface 406 // are the same as http.HandlerFunc and http.Handler. 407 customProvider := func(req *http.Request, ctx context.Context) error { 408 // Just log the request header, nothing else. 409 log.Println(req.Header) 410 return nil 411 } 412 413 // Exhaustive list of some defaults you can use to initialize a Client. 414 // If you need to override the underlying httpClient, you can use the option 415 // 416 // WithHTTPClient(httpClient *http.Client) 417 // 418 client, clientErr := NewClient("https://api.deepmap.com", []ClientOption{ 419 WithBaseURL("https://api.deepmap.com"), 420 WithRequestEditorFn(apiKeyProvider.Edit), 421 }..., 422 ) 423 424 return nil 425 } 426``` 427 428## Extensions 429 430`oapi-codegen` supports the following extended properties: 431 432- `x-go-type`: specifies Go type name. It allows you to specify the type name for a schema, and 433 will override any default value. This extended property isn't supported in all parts of 434 OpenAPI, so please refer to the spec as to where it's allowed. Swagger validation tools will 435 flag incorrect usage of this property. 436 437## Using `oapi-codegen` 438 439The default options for `oapi-codegen` will generate everything; client, server, 440type definitions and embedded swagger spec, but you can generate subsets of 441those via the `-generate` flag. It defaults to `types,client,server,spec`, but 442you can specify any combination of those. 443 444- `types`: generate all type definitions for all types in the OpenAPI spec. This 445 will be everything under `#components`, as well as request parameter, request 446 body, and response type objects. 447- `server`: generate the Echo server boilerplate. `server` requires the types in the 448 same package to compile. 449- `chi-server`: generate the Chi server boilerplate. This code is dependent on 450 that produced by the `types` target. 451- `client`: generate the client boilerplate. It, too, requires the types to be 452 present in its package. 453- `spec`: embed the OpenAPI spec into the generated code as a gzipped blob. This 454- `skip-fmt`: skip running `goimports` on the generated code. This is useful for debugging 455 the generated file in case the spec contains weird strings. 456- `skip-prune`: skip pruning unused components from the spec prior to generating 457 the code. 458- `import-mapping`: specifies a map of references external OpenAPI specs to go 459 Go include paths. Please see below. 460 461So, for example, if you would like to produce only the server code, you could 462run `oapi-generate -generate types,server`. You could generate `types` and 463`server` into separate files, but both are required for the server code. 464 465`oapi-codegen` can filter paths base on their tags in the openapi definition. 466Use either `-include-tags` or `-exclude-tags` followed by a comma-separated list 467of tags. For instance, to generate a server that serves all paths except those 468tagged with `auth` or `admin`, use the argument, `-exclude-tags="auth,admin"`. 469To generate a server that only handles `admin` paths, use the argument 470`-include-tags="admin"`. When neither of these arguments is present, all paths 471are generated. 472 473`oapi-codegen` can filter schemas based on the option `--exclude-schemas`, which is 474a comma separated list of schema names. For instance, `--exclude-schemas=Pet,NewPet` 475will exclude from generation schemas `Pet` and `NewPet`. This allow to have a 476in the same package a manually defined structure or interface and refer to it 477in the openapi spec. 478 479Since `go generate` commands must be a single line, all the options above can make 480them pretty unwieldy, so you can specify all of the options in a configuration 481file via the `--config` option. Please see the test under 482[`/internal/test/externalref/`](https://github.com/deepmap/oapi-codegen/blob/master/internal/test/externalref/externalref.cfg.yaml) 483for an example. The structure of the file is as follows: 484 485```yaml 486output: 487 externalref.gen.go 488package: externalref 489generate: 490 - types 491 - skip-prune 492import-mapping: 493 ./packageA/spec.yaml: github.com/deepmap/oapi-codegen/internal/test/externalref/packageA 494 ./packageB/spec.yaml: github.com/deepmap/oapi-codegen/internal/test/externalref/packageB 495``` 496 497Have a look at [`cmd/oapi-codegen/oapi-codegen.go`](https://github.com/deepmap/oapi-codegen/blob/master/cmd/oapi-codegen/oapi-codegen.go#L48) 498to see all the fields on the configuration structure. 499 500### Import Mappings 501 502OpenAPI specifications may contain references to other OpenAPI specifications, 503and we need some additional information in order to be able to generate correct 504Go code. 505 506An external reference looks like this: 507 508 $ref: ./some_spec.yaml#/components/schemas/Type 509 510We assume that you have already generated the boilerplate code for `./some_spec.yaml` 511using `oapi-codegen`, and you have a package which contains the generated code, 512let's call it `github.com/deepmap/some-package`. You need to tell `oapi-codegen` that 513`some_spec.yaml` corresponds to this package, and you would do it by specifying 514this command line argument: 515 516 -import-mapping=./some_spec.yaml:github.com/deepmap/some-package 517 518This tells us that in order to resolve references generated from `some_spec.yaml` we 519need to import `github.com/deepmap/some-package`. You may specify multiple mappings 520by comma separating them in the form `key1:value1,key2:value2`. 521 522## What's missing or incomplete 523 524This code is still young, and not complete, since we're filling it in as we 525need it. We've not yet implemented several things: 526 527- `oneOf`, `anyOf` are not supported with strong Go typing. This schema: 528 529 schema: 530 oneOf: 531 - $ref: '#/components/schemas/Cat' 532 - $ref: '#/components/schemas/Dog' 533 534 will result in a Go type of `interface{}`. It will be up to you 535 to validate whether it conforms to `Cat` and/or `Dog`, depending on the 536 keyword. It's not clear if we can do anything much better here given the 537 limits of Go typing. 538 539 `allOf` is supported, by taking the union of all the fields in all the 540 component schemas. This is the most useful of these operations, and is 541 commonly used to merge objects with an identifier, as in the 542 `petstore-expanded` example. 543 544- `patternProperties` isn't yet supported and will exit with an error. Pattern 545 properties were defined in JSONSchema, and the `kin-openapi` Swagger object 546 knows how to parse them, but they're not part of OpenAPI 3.0, so we've left 547 them out, as support is very complicated. 548 549 550## Making changes to code generation 551 552The code generator uses a tool to inline all the template definitions into 553code, so that we don't have to deal with the location of the template files. 554When you update any of the files under the `templates/` directory, you will 555need to regenerate the template inlines: 556 557 go generate ./pkg/codegen/templates 558 559All this command does is inline the files ending in `.tmpl` into the specified 560Go file. 561 562Afterwards you should run `go generate ./...`, and the templates will be updated 563 accordingly. 564 565Alternatively, you can provide custom templates to override built-in ones using 566the `-templates` flag specifying a path to a directory containing templates 567files. These files **must** be named identically to built-in template files 568(see `pkg/codegen/templates/*.tmpl` in the source code), and will be interpreted 569on-the-fly at run time. Example: 570 571 $ ls -1 my-templates/ 572 client.tmpl 573 typedef.tmpl 574 $ oapi-codegen \ 575 -templates my-templates/ \ 576 -generate types,client \ 577 petstore-expanded.yaml 578