wI2L / fizz

:lemon: Gin wrapper with OpenAPI 3 spec generation
https://pkg.go.dev/github.com/wI2L/fizz
MIT License
214 stars 52 forks source link

Inline requestBody schema results in uglier client code #47

Closed pablito-perez closed 3 years ago

pablito-perez commented 3 years ago

Fizz currently inlines a request's body schema in the operation's definition. When the resulting OpenAPI spec is fed into a code generator such as openapi-generator, this produces input types with generic names which are hard to use.

I'll illustrate the issue with the following example code:

func main() {
  router := fizz.New()
  router.POST("/foo",
    []fizz.OperationOption{},
    tonic.Handler(foo, http.StatusCreated))
  ...
  etc
  ...
}

type FooInput struct {
    Name string `json:"name"`
}

type FooOutput struct {
    Message string `json:"message"`
}

func foo(c *gin.Context, in *FooInput) (*FooOutput, error) {
    return &FooOutput{"OK"}, nil
}

Before

This server results in the following openAPI schema:

paths:
  /foo:
    post:
      operationId: foo
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FooOutput'
components:
  schemas:
    FooOutput:
      type: object
      properties:
        message:
          type: string

Which in turn, produces the following client code (running openapi-generator generate -i spec.yaml -g go):

package openapi

// InlineObject struct for InlineObject
type InlineObject struct {
    Name string `json:"name,omitempty"`
}

type DefaultApiService service

// FooOpts Optional parameters for the method 'Foo'
type FooOpts struct {
  InlineObject optional.Interface
}

func (a *DefaultApiService) Foo(ctx _context.Context, localVarOptionals *FooOpts) (FooOutput, *_nethttp.Response, error) {
  ...
  etc
  ...
}

After

A more desirable behaviour would be to generate a named schema for the request body, such as:

paths:
  /foo:
    post:
      operationId: foo
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FooInput'
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FooOutput'
components:
  schemas:
    FooInput:
      type: object
      properties:
        name:
          type: string
    FooOutput:
      type: object
      properties:
        message:
          type: string

Which results in the following generated code:

package openapi

// FooInput struct for FooInput
type FooInput struct {
    Name string `json:"name,omitempty"`
}

type DefaultApiService service

// FooOpts Optional parameters for the method 'Foo'
type FooOpts struct {
    FooInput optional.Interface
}

func (a *DefaultApiService) Foo(ctx _context.Context, localVarOptionals *FooOpts) (FooOutput, *_nethttp.Response, error) {
  ...
  etc
  ...
}
wI2L commented 3 years ago

Hey Pablo, @nomonamo

Totally up to this. That was planned when I first wrote the library back in 2018, but it went by the wayside due to lack of time. Thanks for the contibution, I'm going to review the PR asap.

wI2L commented 3 years ago

Fixed in https://github.com/wI2L/fizz/pull/48.