swaggest / openapi-go

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

Incorrect "null" type (should use nullable property) #129

Open RPGillespie6 opened 3 weeks ago

RPGillespie6 commented 3 weeks ago

Run the following code:

package main

import (
    "fmt"

    "github.com/swaggest/openapi-go"
    "github.com/swaggest/openapi-go/openapi31"
)

func main() {
    reflector := openapi31.NewReflector()
    type Foo struct {
        Bar string `json:"bar"`
    }

    type Example struct {
        MyArray []*Foo `json:"myArray"`
    }

    // Operations
    var oc openapi.OperationContext
    var err error

    oc, err = reflector.NewOperationContext("POST", "/api/example")
    if err != nil {
        panic(err)
    }

    var body0 *Example
    oc.AddReqStructure(body0)

    err = reflector.AddOperation(oc)
    if err != nil {
        panic(err)
    }

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

    // Send YAML to stdout
    fmt.Println(string(schema))
}

Expected result:

openapi: 3.1.0
info:
  title: ""
  version: ""
paths:
  /api/example:
    post:
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Example'
      responses:
        "204":
          description: No Content
components:
  schemas:
    Example:
      properties:
        myArray:
          items:
            $ref: '#/components/schemas/Foo'
          type: array
          nullable: true
      type: object
    Foo:
      properties:
        bar:
          type: string
      type: object

Actual result:

openapi: 3.1.0
info:
  title: ""
  version: ""
paths:
  /api/example:
    post:
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Example'
      responses:
        "204":
          description: No Content
components:
  schemas:
    Example:
      properties:
        myArray:
          items:
            $ref: '#/components/schemas/Foo'
          type:
          - array
          - "null"
      type: object
    Foo:
      properties:
        bar:
          type: string
      type: object

See: https://swagger.io/docs/specification/data-models/data-types/#null

RPGillespie6 commented 3 weeks ago

I was able to workaround the issue with:

func removeSchemaNulls(schema map[string]any) error {
    if _, ok := schema["$ref"].(string); ok {
        return nil
    }

    typeArray, ok := schema["type"].([]any)
    if ok {
        fmt.Println("ding ding ding, ", typeArray)
        actualType := ""
        for _, t := range typeArray {
            tStr := t.(string)
            if tStr != "null" {
                actualType = tStr
                break
            }
        }

        if actualType == "" {
            return fmt.Errorf("missing non-null type: %v", schema["type"])
        }

        schema["type"] = actualType
        schema["nullable"] = true
    }

    componentType, ok := schema["type"].(string)
    if !ok {
        return fmt.Errorf("invalid type: %v", componentType)
    }

    switch componentType {
    case "object":
        return objectRemoveSchemaNulls(schema)
    case "array":
        return arrayRemoveSchemaNulls(schema)
    default:
        return nil
    }
}

func objectRemoveSchemaNulls(schema map[string]any) error {
    properties, ok := schema["properties"].(map[string]any)
    if !ok {
        properties = map[string]any{}
    }

    for property, propItem := range properties {
        propSchema, ok := propItem.(map[string]any)
        if !ok {
            return fmt.Errorf("invalid property schema: %v", propItem)
        }

        err := removeSchemaNulls(propSchema)
        if err != nil {
            return fmt.Errorf("%s: %v", property, err)
        }
    }

    return nil
}

func arrayRemoveSchemaNulls(schema map[string]any) error {
    items, ok := schema["items"].(map[string]any)
    if !ok {
        return fmt.Errorf("missing items: %v", schema["items"])
    }

    err := removeSchemaNulls(items)
    if err != nil {
        return err
    }

    return nil
}