apispec icon indicating copy to clipboard operation
apispec copied to clipboard

Fix nested nullable

Open Bangertm opened this issue 2 years ago • 3 comments

This fixes #833. field2nullable executes after other attribute functions including user added attribute functions and now has logic for handling cases with a reference object and an allOf object.

Bangertm avatar Jun 23 '23 23:06 Bangertm

@lafrech After adding the case for executing field2nullable after user attribute functions I'm a little on the fence about whether it is needed or not. The only tests I can come up with are a bit contrived, but the change is minor so I'm inclined to go with it.

I'm not super happy with the tests I wrote here. Not sure if there's an obvious way to improve them. They are functional, but parallel to other tests without any elegant parameterization.

Bangertm avatar Jun 23 '23 23:06 Bangertm

This works for me! Closing #841 in favour of this one.

jmgoncalves avatar Jun 30 '23 14:06 jmgoncalves

This change looks good for OpenAPI v3.1.0 but it doesn't look so good for OpenAPI v3.0.2/OpenAPI v3.0.3

Code
from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin
from marshmallow import Schema, fields
import json


class Child(Schema):
    name = fields.Str(metadata={"example": "Child-name"})


class Parent(Schema):
    name = fields.Str(metadata={"example": "Parent-name"})
    firstChild = fields.Nested(Child, allow_none=True)


class GrandParent(Schema):
    name = fields.Str(metadata={"example": "GrandParent-name"})
    firstChild = fields.Nested(Parent, allow_none=True)


spec = APISpec(
    title="Swagger Petstore",
    version="1.0.0",
    # openapi_version="3.0.2",
    openapi_version="3.0.3",
    # openapi_version="3.1.0",
    plugins=[MarshmallowPlugin()],
)


spec.path(
    path='/family/one',
    operations={
        'get': {
           'description': 'A family of one',
           'responses': {
               '200': {
                   'description': 'Success response',
                   'content': {
                       'application/json': {
                           "schema": GrandParent,
                       },
                   },
               },
           },
        },
    },
)

print(json.dumps(spec.to_dict(), indent=2))
Old spec
{
  "paths": {
    "/family/one": {
      "get": {
        "description": "A family of one",
        "responses": {
          "200": {
            "description": "Success response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GrandParent"
                }
              }
            }
          }
        }
      }
    }
  },
  "info": {
    "title": "Swagger Petstore",
    "version": "1.0.0"
  },
  "openapi": "3.0.3",
  "components": {
    "schemas": {
      "Child": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "example": "Child-name"
          }
        }
      },
      "Parent": {
        "type": "object",
        "properties": {
          "firstChild": {
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/Child"
              }
            ]
          },
          "name": {
            "type": "string",
            "example": "Parent-name"
          }
        }
      },
      "GrandParent": {
        "type": "object",
        "properties": {
          "firstChild": {
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/Parent"
              }
            ]
          },
          "name": {
            "type": "string",
            "example": "GrandParent-name"
          }
        }
      }
    }
  }
}
New spec
{
  "paths": {
    "/family/one": {
      "get": {
        "description": "A family of one",
        "responses": {
          "200": {
            "description": "Success response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GrandParent"
                }
              }
            }
          }
        }
      }
    }
  },
  "info": {
    "title": "Swagger Petstore",
    "version": "1.0.0"
  },
  "openapi": "3.0.3",
  "components": {
    "schemas": {
      "Child": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "example": "Child-name"
          }
        }
      },
      "Parent": {
        "type": "object",
        "properties": {
          "firstChild": {
            "$ref": "#/components/schemas/Child",
            "nullable": true
          },
          "name": {
            "type": "string",
            "example": "Parent-name"
          }
        }
      },
      "GrandParent": {
        "type": "object",
        "properties": {
          "firstChild": {
            "$ref": "#/components/schemas/Parent",
            "nullable": true
          },
          "name": {
            "type": "string",
            "example": "GrandParent-name"
          }
        }
      }
    }
  }
}

Diff between old and new spec:

diff --git a/spec_3.0.3.json b/spec_3.0.3_new.json
index 5d6f68f..5d442f3 100644
--- a/spec_3.0.3.json
+++ b/spec_3.0.3_new.json
@@ -38,12 +38,8 @@
         "type": "object",
         "properties": {
           "firstChild": {
-            "nullable": true,
-            "allOf": [
-              {
-                "$ref": "#/components/schemas/Child"
-              }
-            ]
+            "$ref": "#/components/schemas/Child",
+            "nullable": true
           },
           "name": {
             "type": "string",
@@ -55,12 +51,8 @@
         "type": "object",
         "properties": {
           "firstChild": {
-            "nullable": true,
-            "allOf": [
-              {
-                "$ref": "#/components/schemas/Parent"
-              }
-            ]
+            "$ref": "#/components/schemas/Parent",
+            "nullable": true
           },
           "name": {
             "type": "string",

Rendering the spec with https://editor-next.swagger.io/ and/or https://editor.swagger.io/

Render of old spec: spec_3 0 3-editor-next

(Note: https://editor-next.swagger.io/ complains about the allOf usage but it does appear to do the right thing; https://editor.swagger.io/ does not complain about it. See https://github.com/swagger-api/swagger-editor/issues/4026 )

Render of new spec: spec_3 0 3_new-editor-next

The nullable: true annotation is missing.

bram-tv avatar Jul 01 '23 13:07 bram-tv