From 5a2c6f04134a82ea68c470ac9929e3d388ba8850 Mon Sep 17 00:00:00 2001
From: p1c2u <maciag.artur@gmail.com>
Date: Sat, 17 Dec 2022 07:10:53 +0000
Subject: [PATCH] AdditionalProperties free form nullable fix

---
 openapi_core/schema/schemas.py                |  4 ++--
 .../unmarshalling/schemas/unmarshallers.py    | 19 +++++++++++--------
 tests/integration/validation/test_petstore.py |  2 ++
 3 files changed, 15 insertions(+), 10 deletions(-)

diff --git a/openapi_core/schema/schemas.py b/openapi_core/schema/schemas.py
index 9cdc2e92..fd6631b1 100644
--- a/openapi_core/schema/schemas.py
+++ b/openapi_core/schema/schemas.py
@@ -4,7 +4,7 @@
 from openapi_core.spec import Spec
 
 
-def get_all_properties(schema: Spec) -> Dict[str, Any]:
+def get_schema_properties(schema: Spec) -> Dict[str, Any]:
     properties = schema.get("properties", {})
     properties_dict = dict(list(properties.items()))
 
@@ -12,7 +12,7 @@ def get_all_properties(schema: Spec) -> Dict[str, Any]:
         return properties_dict
 
     for subschema in schema / "allOf":
-        subschema_props = get_all_properties(subschema)
+        subschema_props = get_schema_properties(subschema)
         properties_dict.update(subschema_props)
 
     return properties_dict
diff --git a/openapi_core/unmarshalling/schemas/unmarshallers.py b/openapi_core/unmarshalling/schemas/unmarshallers.py
index 941e28cb..f0d95f11 100644
--- a/openapi_core/unmarshalling/schemas/unmarshallers.py
+++ b/openapi_core/unmarshalling/schemas/unmarshallers.py
@@ -20,7 +20,7 @@
 from openapi_schema_validator._types import is_string
 
 from openapi_core.extensions.models.factories import ModelPathFactory
-from openapi_core.schema.schemas import get_all_properties
+from openapi_core.schema.schemas import get_schema_properties
 from openapi_core.spec import Spec
 from openapi_core.unmarshalling.schemas.datatypes import FormattersDict
 from openapi_core.unmarshalling.schemas.enums import UnmarshalContext
@@ -268,7 +268,9 @@ def _unmarshal_properties(self, value: Any) -> Any:
             else:
                 properties.update(any_of_properties)
 
-        for prop_name, prop in get_all_properties(self.schema).items():
+        # unmarshal schema properties
+        schema_properties = get_schema_properties(self.schema)
+        for prop_name, prop in schema_properties.items():
             read_only = prop.getkey("readOnly", False)
             if self.context == UnmarshalContext.REQUEST and read_only:
                 continue
@@ -286,23 +288,24 @@ def _unmarshal_properties(self, value: Any) -> Any:
                 prop_value
             )
 
+        # unmarshal additional properties
         additional_properties = self.schema.getkey(
             "additionalProperties", True
         )
         if additional_properties is not False:
             # free-form object
             if additional_properties is True:
-                additional_prop_schema = Spec.from_dict({})
+                unmarshal_func = lambda x: x
             # defined schema
             else:
                 additional_prop_schema = self.schema / "additionalProperties"
-            additional_prop_unmarshaler = self.unmarshallers_factory.create(
-                additional_prop_schema
-            )
+                unmarshal_func = self.unmarshallers_factory.create(
+                    additional_prop_schema
+                )
             for prop_name, prop_value in value.items():
-                if prop_name in properties:
+                if prop_name in schema_properties:
                     continue
-                properties[prop_name] = additional_prop_unmarshaler(prop_value)
+                properties[prop_name] = unmarshal_func(prop_value)
 
         return properties
 
diff --git a/tests/integration/validation/test_petstore.py b/tests/integration/validation/test_petstore.py
index c52feeb3..6925bbf9 100644
--- a/tests/integration/validation/test_petstore.py
+++ b/tests/integration/validation/test_petstore.py
@@ -752,6 +752,7 @@ def test_post_cats(self, spec, spec_dict):
             "ears": {
                 "healthy": pet_healthy,
             },
+            "extra": None,
         }
         data = json.dumps(data_json)
         headers = {
@@ -799,6 +800,7 @@ def test_post_cats(self, spec, spec_dict):
         assert result.body.address.street == pet_street
         assert result.body.address.city == pet_city
         assert result.body.healthy == pet_healthy
+        assert result.body.extra is None
 
     def test_post_cats_boolean_string(self, spec, spec_dict):
         host_url = "https://staging.gigantic-server.com/v1"