Repository: marshmallow-code/apispec
Pull request #409 (2019-03-13 21:29:46)

MarshmallowPlugin: Resolve Schema in response headers
TODO

- [x] Tests
- [x] Doc ?


Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 99166fe..b64ccaf 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,13 @@
 Changelog
 ---------
 
+1.1.0 (unreleased)
+++++++++++++++++++
+
+Features:
+
+- Resolve ``Schema`` classes in response headers (:pr:`409`).
+
 1.0.0 (2019-02-08)
 ++++++++++++++++++
 
diff --git a/src/apispec/ext/marshmallow/__init__.py b/src/apispec/ext/marshmallow/__init__.py
index 5aa480d..283dc19 100644
--- a/src/apispec/ext/marshmallow/__init__.py
+++ b/src/apispec/ext/marshmallow/__init__.py
@@ -1,7 +1,10 @@
 # -*- coding: utf-8 -*-
 """Marshmallow plugin for apispec. Allows passing a marshmallow
-`Schema` to `APISpec.definition <apispec.APISpec.definition>`
-and `APISpec.path <apispec.APISpec.path>` (for responses).
+`Schema` to `spec.components.schema <apispec.core.Components.schema>`,
+`spec.components.parameter <apispec.core.Components.parameter>`,
+`spec.components.response <apispec.core.Components.response>`
+(for response and headers schemas) and
+`spec.path <apispec.APISpec.path>` (for responses and response headers).
 
 Requires marshmallow>=2.15.2.
 
@@ -108,10 +111,11 @@ class MarshmallowPlugin(BasePlugin):
         :param APISpec spec: `APISpec` containing refs.
         :param dict data: the parameter or response dictionary that may contain a schema
         """
-        if self.openapi_version.major < 3:
-            if "schema" in data:
-                data["schema"] = self.openapi.resolve_schema_dict(data["schema"])
-        else:
+        # OAS 2 component or OAS 3 header
+        if "schema" in data:
+            data["schema"] = self.openapi.resolve_schema_dict(data["schema"])
+        # OAS 3 component except header
+        if self.openapi_version.major >= 3:
             if "content" in data:
                 for content_type in data["content"]:
                     schema = data["content"][content_type]["schema"]
@@ -178,6 +182,9 @@ class MarshmallowPlugin(BasePlugin):
             Schema class or instance.
         """
         self.resolve_schema(response)
+        if "headers" in response:
+            for header in response["headers"].values():
+                self.resolve_schema(header)
         return response
 
     def operation_helper(self, operations, **kwargs):
@@ -193,6 +200,9 @@ class MarshmallowPlugin(BasePlugin):
                     self.resolve_schema_in_request_body(operation["requestBody"])
             for response in operation.get("responses", {}).values():
                 self.resolve_schema(response)
+                if "headers" in response:
+                    for header in response["headers"].values():
+                        self.resolve_schema(header)
 
     def warn_if_schema_already_in_spec(self, schema_key):
         """Method to warn the user if the schema has already been added to the


Test patch:
diff --git a/tests/test_ext_marshmallow.py b/tests/test_ext_marshmallow.py
index ce1083d..0b1b328 100644
--- a/tests/test_ext_marshmallow.py
+++ b/tests/test_ext_marshmallow.py
@@ -197,7 +197,6 @@ class TestComponentParameterHelper:
             reference = parameter["schema"]
         else:
             reference = parameter["content"]["application/json"]["schema"]
-
         assert reference == {"$ref": ref_path(spec) + "Pet"}
 
         resolved_schema = spec.components._schemas["Pet"]
@@ -219,7 +218,19 @@ class TestComponentResponseHelper:
             reference = response["schema"]
         else:
             reference = response["content"]["application/json"]["schema"]
+        assert reference == {"$ref": ref_path(spec) + "Pet"}
+
+        resolved_schema = spec.components._schemas["Pet"]
+        assert resolved_schema["properties"]["id"]["type"] == "integer"
+        assert resolved_schema["properties"]["name"]["type"] == "string"
+        assert resolved_schema["properties"]["password"]["type"] == "string"
 
+    @pytest.mark.parametrize("schema", [PetSchema, PetSchema()])
+    def test_can_use_schema_in_response_header(self, spec, schema):
+        resp = {"headers": {"PetHeader": {"schema": schema}}}
+        spec.components.response("GetPetOk", resp)
+        response = get_responses(spec)["GetPetOk"]
+        reference = response["headers"]["PetHeader"]["schema"]
         assert reference == {"$ref": ref_path(spec) + "Pet"}
 
         resolved_schema = spec.components._schemas["Pet"]
@@ -283,6 +294,7 @@ class TestOperationHelper:
                         200: {
                             "schema": pet_schema,
                             "description": "successful operation",
+                            "headers": {"PetHeader": {"schema": pet_schema}},
                         }
                     }
                 }
@@ -291,10 +303,19 @@ class TestOperationHelper:
         get = get_paths(spec_fixture.spec)["/pet"]["get"]
         if isinstance(pet_schema, Schema) and pet_schema.many is True:
             assert get["responses"][200]["schema"]["type"] == "array"
-            reference = get["responses"][200]["schema"]["items"]
+            schema_reference = get["responses"][200]["schema"]["items"]
+            assert (
+                get["responses"][200]["headers"]["PetHeader"]["schema"]["type"]
+                == "array"
+            )
+            header_reference = get["responses"][200]["headers"]["PetHeader"]["schema"][
+                "items"
+            ]
         else:
-            reference = get["responses"][200]["schema"]
-        assert reference == {"$ref": ref_path(spec_fixture.spec) + "Pet"}
+            schema_reference = get["responses"][200]["schema"]
+            header_reference = get["responses"][200]["headers"]["PetHeader"]["schema"]
+        assert schema_reference == {"$ref": ref_path(spec_fixture.spec) + "Pet"}
+        assert header_reference == {"$ref": ref_path(spec_fixture.spec) + "Pet"}
         assert len(spec_fixture.spec.components._schemas) == 1
         resolved_schema = spec_fixture.spec.components._schemas["Pet"]
         assert resolved_schema == spec_fixture.openapi.schema2jsonschema(PetSchema)
@@ -314,6 +335,7 @@ class TestOperationHelper:
                         200: {
                             "content": {"application/json": {"schema": pet_schema}},
                             "description": "successful operation",
+                            "headers": {"PetHeader": {"schema": pet_schema}},
                         }
                     }
                 }
@@ -325,13 +347,24 @@ class TestOperationHelper:
                 get["responses"][200]["content"]["application/json"]["schema"]["type"]
                 == "array"
             )
-            reference = get["responses"][200]["content"]["application/json"]["schema"][
+            schema_reference = get["responses"][200]["content"]["application/json"][
+                "schema"
+            ]["items"]
+            assert (
+                get["responses"][200]["headers"]["PetHeader"]["schema"]["type"]
+                == "array"
+            )
+            header_reference = get["responses"][200]["headers"]["PetHeader"]["schema"][
                 "items"
             ]
         else:
-            reference = get["responses"][200]["content"]["application/json"]["schema"]
+            schema_reference = get["responses"][200]["content"]["application/json"][
+                "schema"
+            ]
+            header_reference = get["responses"][200]["headers"]["PetHeader"]["schema"]
 
-        assert reference == {"$ref": ref_path(spec_fixture.spec) + "Pet"}
+        assert schema_reference == {"$ref": ref_path(spec_fixture.spec) + "Pet"}
+        assert header_reference == {"$ref": ref_path(spec_fixture.spec) + "Pet"}
         assert len(spec_fixture.spec.components._schemas) == 1
         resolved_schema = spec_fixture.spec.components._schemas["Pet"]
         assert resolved_schema == spec_fixture.openapi.schema2jsonschema(PetSchema)