Repository: marshmallow-code/apispec
Pull request #637 (2021-02-09 23:42:01)

Allow registration of headers
Contributes to #245.

This is an OAS 3 feature only.

lafrech: Yes, neither did I. Thanks for reviewing.

Patch:
diff --git a/src/apispec/core.py b/src/apispec/core.py
index eab32b3..13861ef 100644
--- a/src/apispec/core.py
+++ b/src/apispec/core.py
@@ -32,6 +32,7 @@ class Components:
         self.schemas = {}
         self.responses = {}
         self.parameters = {}
+        self.headers = {}
         self.examples = {}
         self.security_schemes = {}
 
@@ -40,6 +41,7 @@ class Components:
             "schema": self.schemas,
             "response": self.responses,
             "parameter": self.parameters,
+            "header": self.headers,
             "example": self.examples,
             "security_scheme": self.security_schemes,
         }
@@ -112,7 +114,7 @@ class Components:
     def parameter(self, component_id, location, component=None, **kwargs):
         """ Add a parameter which can be referenced.
 
-        :param str param_id: identifier by which parameter may be referenced.
+        :param str component_id: identifier by which parameter may be referenced.
         :param str location: location of the parameter.
         :param dict component: parameter fields.
         :param kwargs: plugin-specific arguments
@@ -141,6 +143,21 @@ class Components:
         self.parameters[component_id] = ret
         return self
 
+    def header(self, component_id, component):
+        """ Add a header which can be referenced.
+
+        :param str component_id: identifier by which header may be referenced.
+        :param dict component: header fields.
+
+        https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#headerObject
+        """
+        if component_id in self.headers:
+            raise DuplicateComponentNameError(
+                f'Another header with name "{component_id}" is already registered.'
+            )
+        self.headers[component_id] = component
+        return self
+
     def example(self, name, component, **kwargs):
         """Add an example which can be referenced
 
diff --git a/src/apispec/utils.py b/src/apispec/utils.py
index a778e0c..aa38f92 100644
--- a/src/apispec/utils.py
+++ b/src/apispec/utils.py
@@ -20,6 +20,7 @@ COMPONENT_SUBSECTIONS = {
         "schema": "schemas",
         "response": "responses",
         "parameter": "parameters",
+        "header": "headers",
         "example": "examples",
         "security_scheme": "securitySchemes",
     },


Test patch:
diff --git a/tests/test_core.py b/tests/test_core.py
index 922c739..66b9b8c 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -17,6 +17,7 @@ from .utils import (
     get_examples,
     get_paths,
     get_parameters,
+    get_headers,
     get_responses,
     get_security_schemes,
     build_ref,
@@ -202,6 +203,8 @@ class TestComponents:
             spec.components.response("test_response")
 
     def test_parameter(self, spec):
+        # Note: this is an OpenAPI v2 parameter header
+        # but is does the job for the test even for OpenAPI v3
         parameter = {"format": "int64", "type": "integer"}
         spec.components.parameter("PetId", "path", parameter)
         params = get_parameters(spec)
@@ -227,6 +230,33 @@ class TestComponents:
         ):
             spec.components.parameter("test_parameter", "path")
 
+    # Referenced headers are only supported in OAS 3.x
+    @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
+    def test_header(self, spec):
+        header = {"schema": {"type": "string"}}
+        spec.components.header("test_header", header.copy())
+        headers = get_headers(spec)
+        assert headers["test_header"] == header
+
+    # Referenced headers are only supported in OAS 3.x
+    @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
+    def test_header_is_chainable(self, spec):
+        header = {"schema": {"type": "string"}}
+        spec.components.header("header1", header).header("header2", header)
+        headers = get_headers(spec)
+        assert "header1" in headers
+        assert "header2" in headers
+
+    # Referenced headers are only supported in OAS 3.x
+    @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
+    def test_header_duplicate_name(self, spec):
+        spec.components.header("test_header", {"schema": {"type": "string"}})
+        with pytest.raises(
+            DuplicateComponentNameError,
+            match='Another header with name "test_header" is already registered.',
+        ):
+            spec.components.header("test_header", {"schema": {"type": "integer"}})
+
     # Referenced examples are only supported in OAS 3.x
     @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
     def test_example(self, spec):
diff --git a/tests/utils.py b/tests/utils.py
index 79b7976..7e3d404 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -21,6 +21,12 @@ def get_parameters(spec):
     return spec.to_dict()["components"]["parameters"]
 
 
+def get_headers(spec):
+    if spec.openapi_version.major < 3:
+        return spec.to_dict()["headers"]
+    return spec.to_dict()["components"]["headers"]
+
+
 def get_examples(spec):
     return spec.to_dict()["components"]["examples"]