• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..03-May-2022-

cmd/oapi-codegen/H14-Apr-2021-

examples/petstore-expanded/H14-Apr-2021-

internal/test/H14-Apr-2021-

pkg/H14-Apr-2021-

tools/H14-Apr-2021-

.editorconfigH A D14-Apr-2021173

.gitattributesH A D14-Apr-202128

.gitignoreH A D14-Apr-202124

.travis.ymlH A D14-Apr-2021310

LICENSEH A D14-Apr-202111.1 KiB

MakefileH A D14-Apr-2021255

README.mdH A D14-Apr-202122.6 KiB

go.modH A D14-Apr-2021857

go.sumH A D14-Apr-20219.3 KiB

README.md

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