swaggest / openapi-go

OpenAPI structures for Go
https://pkg.go.dev/github.com/swaggest/openapi-go/openapi3
MIT License
248 stars 23 forks source link

req/resp struct using generic could cause semantic error in openapi output #71

Closed radaiming closed 1 year ago

radaiming commented 1 year ago

Describe the bug If req/resp struct using generic, like type req[T any] struct {}, generated openapi output will contains schema with name like Req[Blabla], and Swagger Editor complains it as semantic error

To Reproduce Take the example code in README, I modify req/resp to use generic:

package main

import (
    "fmt"
    "github.com/swaggest/openapi-go/openapi3"
    "log"
    "net/http"
    "time"
)

func handleError(err error) {
    if err != nil {
        panic(err)
    }
}

func main() {
    reflector := openapi3.Reflector{}
    reflector.Spec = &openapi3.Spec{Openapi: "3.0.3"}
    reflector.Spec.Info.
        WithTitle("Things API").
        WithVersion("1.2.3").
        WithDescription("Put something here")

    type req[T any] struct {
        ID     string `path:"id" example:"XXX-XXXXX"`
        Locale string `query:"locale" pattern:"^[a-z]{2}-[A-Z]{2}$"`
        Title  string `json:"string"`
        Amount uint   `json:"amount"`
        Items  []struct {
            Count uint   `json:"count"`
            Name  string `json:"name"`
        } `json:"items"`
    }

    type resp[T any] struct {
        ID     string `json:"id" example:"XXX-XXXXX"`
        Amount uint   `json:"amount"`
        Items  []struct {
            Count uint   `json:"count"`
            Name  string `json:"name"`
        } `json:"items"`
        UpdatedAt time.Time `json:"updated_at"`
    }

    putOp := openapi3.Operation{}

    handleError(reflector.SetRequest(&putOp, new(req[time.Time]), http.MethodPut))
    handleError(reflector.SetJSONResponse(&putOp, new(resp[time.Time]), http.StatusOK))
    handleError(reflector.SetJSONResponse(&putOp, new([]resp[time.Time]), http.StatusConflict))
    handleError(reflector.Spec.AddOperation(http.MethodPut, "/things/{id}", putOp))

    getOp := openapi3.Operation{}

    handleError(reflector.SetRequest(&getOp, new(req[time.Time]), http.MethodGet))
    handleError(reflector.SetJSONResponse(&getOp, new(resp[time.Time]), http.StatusOK))
    handleError(reflector.Spec.AddOperation(http.MethodGet, "/things/{id}", getOp))

    schema, err := reflector.Spec.MarshalYAML()
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(string(schema))
}

which will output:

openapi: 3.0.3
info:
  description: Put something here
  title: Things API
  version: 1.2.3
paths:
  /things/{id}:
    get:
      parameters:
      - in: query
        name: locale
        schema:
          pattern: ^[a-z]{2}-[A-Z]{2}$
          type: string
      - in: path
        name: id
        required: true
        schema:
          example: XXX-XXXXX
          type: string
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Resp[TimeTime]'
          description: OK
    put:
      parameters:
      - in: query
        name: locale
        schema:
          pattern: ^[a-z]{2}-[A-Z]{2}$
          type: string
      - in: path
        name: id
        required: true
        schema:
          example: XXX-XXXXX
          type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Req[TimeTime]'
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Resp[TimeTime]'
          description: OK
        "409":
          content:
            application/json:
              schema:
                items:
                  $ref: '#/components/schemas/Resp[TimeTime]'
                type: array
          description: Conflict
components:
  schemas:
    Req[TimeTime]:
      properties:
        amount:
          minimum: 0
          type: integer
        items:
          items:
            properties:
              count:
                minimum: 0
                type: integer
              name:
                type: string
            type: object
          nullable: true
          type: array
        string:
          type: string
      type: object
    Resp[TimeTime]:
      properties:
        amount:
          minimum: 0
          type: integer
        id:
          example: XXX-XXXXX
          type: string
        items:
          items:
            properties:
              count:
                minimum: 0
                type: integer
              name:
                type: string
            type: object
          nullable: true
          type: array
        updated_at:
          format: date-time
          type: string
      type: object

Then paste it to https://editor.swagger.io/, it will complain Component names can only contain the characters A-Z a-z 0-9 - . _, although req/resp displayed correctly

Expected behavior Output schema name should not contain bracket

Additional context Nothing else and thanks for your library ❤️

vearutop commented 1 year ago

Thank you for reporting this! 👍 Please try new v0.2.35.

radaiming commented 1 year ago

It works, thanks!

Dennis-Zhang-SH commented 1 year ago

Generic parameters result in same struct, for example:

type Resp struct {
    Code int `description:"code"`
    Data any `description:"data"`
}

once if I initialize this struct with one specific type, each time would result in same struct.

vearutop commented 1 year ago

@Dennis-Zhang-SH could you share some code to reproduce the issue?