diff --git a/src/apispec/ext/marshmallow/field_converter.py b/src/apispec/ext/marshmallow/field_converter.py index 63fbe967..1a40b9a9 100644 --- a/src/apispec/ext/marshmallow/field_converter.py +++ b/src/apispec/ext/marshmallow/field_converter.py @@ -306,6 +306,11 @@ def field2nullable(self, field: marshmallow.fields.Field, ret) -> dict: {"type": "object", "nullable": True}, {"$ref": ret.pop("$ref")}, ] + elif "allOf" in ret: + attributes["anyOf"] = [ + *ret.pop("allOf"), + {"type": "object", "nullable": True}, + ] else: attributes["nullable"] = True else: diff --git a/tests/test_ext_marshmallow_field.py b/tests/test_ext_marshmallow_field.py index 5e5250b0..eca4a10c 100644 --- a/tests/test_ext_marshmallow_field.py +++ b/tests/test_ext_marshmallow_field.py @@ -222,6 +222,42 @@ class Child(Schema): } +@pytest.mark.parametrize("spec_fixture", ("2.0", "3.0.0", "3.1.0"), indirect=True) +def test_nested_nullable_with_metadata(spec_fixture): + # Regression test for https://github.com/marshmallow-code/apispec/issues/955 + class Child(Schema): + name = fields.Str() + + field = fields.Nested( + Child, + allow_none=True, + metadata={"description": "foo"}, + ) + res = spec_fixture.openapi.field2property(field) + version = spec_fixture.openapi.openapi_version + if version.major < 3: + assert res == { + "allOf": [ + {"$ref": "#/definitions/Child"}, + ], + "x-nullable": True, + "description": "foo", + } + elif version.major == 3 and version.minor < 1: + assert res == { + "anyOf": [ + {"$ref": "#/components/schemas/Child"}, + {"type": "object", "nullable": True}, + ], + "description": "foo", + } + else: + assert res == { + "anyOf": [{"$ref": "#/components/schemas/Child"}, {"type": "null"}], + "description": "foo", + } + + @pytest.mark.parametrize("spec_fixture", ("2.0", "3.0.0", "3.1.0"), indirect=True) def test_nullable_pluck(spec_fixture): class Example(Schema):