Repository: marshmallow-code/apispec

Issue:
Consider coercing response codes to strings
The OpenAPI specification [allows responses of the form 2XX, 4XX, etc](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#patterned-fields-1). Using this library, it's possible in a docstring to write something like this:


 ```yaml
responses:
    200:
        content:
            application/json:
                schema: GoodResponseSchema
    4XX:
        content:
            application/json:
                schema: RequestErrorSchema
```

This causes the following error:

```
File "/Users/zach/.pyenv/versions/3.6.5/lib/python3.6/json/encoder.py", line 353, in _iterencode_dict
    items = sorted(dct.items(), key=lambda kv: kv[0])
TypeError: '<' not supported between instances of 'str' and 'int'
```

Which happens because the `200` key is stored as an `int`, and the `4XX` key is stored as a string. A preprocessing step to make them all strings would fix that problem.

Discussion:

Patch:
diff --git a/src/apispec/core.py b/src/apispec/core.py
index 33304a8..1b7a105 100644
--- a/src/apispec/core.py
+++ b/src/apispec/core.py
@@ -45,7 +45,7 @@ def clean_operations(operations, openapi_major_version):
         Otherwise, it is assumed to be a reference name as string and the corresponding $ref
         string is returned.
 
-        :param str obj_type: 'parameter' or 'response'
+        :param str obj_type: "parameter" or "response"
         :param dict|str obj: parameter or response in dict form or as ref_id string
         :param int openapi_major_version: The major version of the OpenAPI standard
         """
@@ -67,10 +67,12 @@ def clean_operations(operations, openapi_major_version):
                 get_ref("parameter", p, openapi_major_version) for p in parameters
             ]
         if "responses" in operation:
+            responses = OrderedDict()
             for code, response in iteritems(operation["responses"]):
-                operation["responses"][code] = get_ref(
+                responses[str(code)] = get_ref(
                     "response", response, openapi_major_version
                 )
+            operation["responses"] = responses
 
 
 class Components(object):


Test patch:
diff --git a/tests/test_ext_marshmallow.py b/tests/test_ext_marshmallow.py
index c1ffee1..e734e52 100644
--- a/tests/test_ext_marshmallow.py
+++ b/tests/test_ext_marshmallow.py
@@ -306,24 +306,24 @@ 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"
-            schema_reference = get["responses"][200]["schema"]["items"]
+            assert get["responses"]["200"]["schema"]["type"] == "array"
+            schema_reference = get["responses"]["200"]["schema"]["items"]
             assert (
-                get["responses"][200]["headers"]["PetHeader"]["schema"]["type"]
+                get["responses"]["200"]["headers"]["PetHeader"]["schema"]["type"]
                 == "array"
             )
-            header_reference = get["responses"][200]["headers"]["PetHeader"]["schema"][
-                "items"
-            ]
+            header_reference = get["responses"]["200"]["headers"]["PetHeader"][
+                "schema"
+            ]["items"]
         else:
-            schema_reference = get["responses"][200]["schema"]
-            header_reference = get["responses"][200]["headers"]["PetHeader"]["schema"]
+            schema_reference = get["responses"]["200"]["schema"]
+            header_reference = get["responses"]["200"]["headers"]["PetHeader"]["schema"]
         assert schema_reference == build_ref(spec_fixture.spec, "schema", "Pet")
         assert header_reference == build_ref(spec_fixture.spec, "schema", "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)
-        assert get["responses"][200]["description"] == "successful operation"
+        assert get["responses"]["200"]["description"] == "successful operation"
 
     @pytest.mark.parametrize(
         "pet_schema",
@@ -348,31 +348,31 @@ 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]["content"]["application/json"]["schema"]["type"]
+                get["responses"]["200"]["content"]["application/json"]["schema"]["type"]
                 == "array"
             )
-            schema_reference = get["responses"][200]["content"]["application/json"][
+            schema_reference = get["responses"]["200"]["content"]["application/json"][
                 "schema"
             ]["items"]
             assert (
-                get["responses"][200]["headers"]["PetHeader"]["schema"]["type"]
+                get["responses"]["200"]["headers"]["PetHeader"]["schema"]["type"]
                 == "array"
             )
-            header_reference = get["responses"][200]["headers"]["PetHeader"]["schema"][
-                "items"
-            ]
+            header_reference = get["responses"]["200"]["headers"]["PetHeader"][
+                "schema"
+            ]["items"]
         else:
-            schema_reference = get["responses"][200]["content"]["application/json"][
+            schema_reference = get["responses"]["200"]["content"]["application/json"][
                 "schema"
             ]
-            header_reference = get["responses"][200]["headers"]["PetHeader"]["schema"]
+            header_reference = get["responses"]["200"]["headers"]["PetHeader"]["schema"]
 
         assert schema_reference == build_ref(spec_fixture.spec, "schema", "Pet")
         assert header_reference == build_ref(spec_fixture.spec, "schema", "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)
-        assert get["responses"][200]["description"] == "successful operation"
+        assert get["responses"]["200"]["description"] == "successful operation"
 
     @pytest.mark.parametrize("spec_fixture", ("2.0",), indirect=True)
     def test_schema_expand_parameters_v2(self, spec_fixture):
@@ -447,7 +447,7 @@ class TestOperationHelper:
             path="/pet", operations={"get": {"responses": {200: {"schema": PetSchema}}}}
         )
         get = get_paths(spec_fixture.spec)["/pet"]["get"]
-        assert get["responses"][200]["schema"] == build_ref(
+        assert get["responses"]["200"]["schema"] == build_ref(
             spec_fixture.spec, "schema", "Pet"
         )
 
@@ -465,7 +465,7 @@ class TestOperationHelper:
             },
         )
         get = get_paths(spec_fixture.spec)["/pet"]["get"]
-        assert get["responses"][200]["content"]["application/json"][
+        assert get["responses"]["200"]["content"]["application/json"][
             "schema"
         ] == build_ref(spec_fixture.spec, "schema", "Pet")
 
@@ -484,7 +484,7 @@ class TestOperationHelper:
             path="/pet", operations={"get": {"responses": {200: {"schema": PetSchema}}}}
         )
         get = get_paths(spec)["/pet"]["get"]
-        assert get["responses"][200]["schema"] == build_ref(spec, "schema", "Pet")
+        assert get["responses"]["200"]["schema"] == build_ref(spec, "schema", "Pet")
 
     def test_schema_uses_ref_if_available_name_resolver_returns_none_v3(self):
         def resolver(schema):
@@ -508,7 +508,7 @@ class TestOperationHelper:
             },
         )
         get = get_paths(spec)["/pet"]["get"]
-        assert get["responses"][200]["content"]["application/json"][
+        assert get["responses"]["200"]["content"]["application/json"][
             "schema"
         ] == build_ref(spec, "schema", "Pet")
 
@@ -577,7 +577,7 @@ class TestOperationHelper:
             "items": build_ref(spec_fixture.spec, "schema", "Pet"),
         }
         assert get["parameters"][0]["schema"] == resolved_schema
-        assert get["responses"][200]["schema"] == resolved_schema
+        assert get["responses"]["200"]["schema"] == resolved_schema
 
     @pytest.mark.parametrize("spec_fixture", ("3.0.0",), indirect=True)
     def test_schema_array_uses_ref_if_available_v3(self, spec_fixture):
@@ -616,7 +616,9 @@ class TestOperationHelper:
         }
         request_schema = get["parameters"][0]["content"]["application/json"]["schema"]
         assert request_schema == resolved_schema
-        response_schema = get["responses"][200]["content"]["application/json"]["schema"]
+        response_schema = get["responses"]["200"]["content"]["application/json"][
+            "schema"
+        ]
         assert response_schema == resolved_schema
 
     @pytest.mark.parametrize("spec_fixture", ("2.0",), indirect=True)
@@ -641,7 +643,7 @@ class TestOperationHelper:
             },
         )
         get = get_paths(spec_fixture.spec)["/parents"]["get"]
-        assert get["responses"][200]["schema"] == {
+        assert get["responses"]["200"]["schema"] == {
             "type": "object",
             "properties": {
                 "mother": build_ref(spec_fixture.spec, "schema", "Pet"),
@@ -675,7 +677,7 @@ class TestOperationHelper:
             },
         )
         get = get_paths(spec_fixture.spec)["/parents"]["get"]
-        assert get["responses"][200]["content"]["application/json"]["schema"] == {
+        assert get["responses"]["200"]["content"]["application/json"]["schema"] == {
             "type": "object",
             "properties": {
                 "mother": build_ref(spec_fixture.spec, "schema", "Pet"),