diff --git a/news/2037.bugfix b/news/2037.bugfix new file mode 100644 index 000000000..ef400bc04 --- /dev/null +++ b/news/2037.bugfix @@ -0,0 +1 @@ +Fix crash in JSON schema providers for Dict and List fields when sub-types are missing by making adapter lookups safe. diff --git a/src/plone/restapi/tests/test_types.py b/src/plone/restapi/tests/test_types.py index a19186ca0..694d8c624 100644 --- a/src/plone/restapi/tests/test_types.py +++ b/src/plone/restapi/tests/test_types.py @@ -797,3 +797,37 @@ def test_jsonfield(self): }, adapter.get_schema(), ) + + def test_dict_without_types(self): + field = schema.Dict(title="My field", description="My great field") + adapter = getMultiAdapter( + (field, self.portal, self.request), IJsonSchemaProvider + ) + # This currently crashes with ComponentLookupError + self.assertEqual( + { + "type": "dict", + "title": "My field", + "description": "My great field", + }, + adapter.get_schema(), + ) + + def test_list_without_value_type(self): + field = schema.List(title="My field", description="My great field") + adapter = getMultiAdapter( + (field, self.portal, self.request), IJsonSchemaProvider + ) + # This currently crashes with ComponentLookupError + self.assertEqual( + { + "type": "array", + "title": "My field", + "description": "My great field", + "factory": "List", + "uniqueItems": False, + "additionalItems": True, + "items": {}, + }, + adapter.get_schema(), + ) diff --git a/src/plone/restapi/types/adapters.py b/src/plone/restapi/types/adapters.py index bfd4197f9..2a98a1e5e 100644 --- a/src/plone/restapi/types/adapters.py +++ b/src/plone/restapi/types/adapters.py @@ -13,7 +13,7 @@ from plone.schema import IJSONField from z3c.formwidget.query.interfaces import IQuerySource from zope.component import adapter -from zope.component import getMultiAdapter +from zope.component import queryMultiAdapter from zope.i18n import translate from zope.interface import implementer from zope.interface import Interface @@ -270,11 +270,16 @@ def get_factory(self): def get_items(self): """Get items properties.""" - value_type_adapter = getMultiAdapter( - (self.field.value_type, self.context, self.request), IJsonSchemaProvider - ) - - return value_type_adapter.get_schema() + if self.field.value_type: + value_type_adapter = queryMultiAdapter( + (self.field.value_type, self.context, self.request), IJsonSchemaProvider + ) + if value_type_adapter: + try: + return value_type_adapter.get_schema() + except NotImplementedError: + pass + return {} def additional(self): info = {} @@ -434,20 +439,30 @@ def get_type(self): def additional(self): info = {} - key_type = getMultiAdapter( - (self.field.key_type, self.context, self.request), IJsonSchemaProvider - ) - info["key_type"] = { - "schema": key_type.get_schema(), - "additional": key_type.additional(), - } - value_type = getMultiAdapter( - (self.field.value_type, self.context, self.request), IJsonSchemaProvider - ) - info["value_type"] = { - "schema": value_type.get_schema(), - "additional": value_type.additional(), - } + if self.field.key_type: + key_type = queryMultiAdapter( + (self.field.key_type, self.context, self.request), IJsonSchemaProvider + ) + if key_type: + try: + info["key_type"] = { + "schema": key_type.get_schema(), + "additional": key_type.additional(), + } + except NotImplementedError: + pass + if self.field.value_type: + value_type = queryMultiAdapter( + (self.field.value_type, self.context, self.request), IJsonSchemaProvider + ) + if value_type: + try: + info["value_type"] = { + "schema": value_type.get_schema(), + "additional": value_type.additional(), + } + except NotImplementedError: + pass return info