diff --git a/jambo/parser/_type_parser.py b/jambo/parser/_type_parser.py index efca162..10d479a 100644 --- a/jambo/parser/_type_parser.py +++ b/jambo/parser/_type_parser.py @@ -73,9 +73,40 @@ class GenericTypeParser(ABC, Generic[T]): :param kwargs: Additional options for type parsing. :return: A tuple containing the type and its properties. """ - parser = cls._get_impl(properties) + + parser = cls._get_impl( + cls._normalize_properties(properties) + ) return parser().from_properties(name=name, properties=properties, **kwargs) + + @staticmethod + def _normalize_properties(properties: JSONSchema) -> JSONSchema: + """ + Normalizes the properties dictionary to ensure consistent structure. + :param properties: The properties to be normalized. + """ + type_value = properties.pop("type", None) + + if isinstance(type_value, str): + properties["type"] = type_value + return properties + + if isinstance(type_value, list) and len(type_value) == 0: + raise InvalidSchemaException( + "Invalid schema: 'type' list cannot be empty", invalid_field=str(properties) + ) + + + if isinstance(type_value, list) and len(type_value) == 1: + properties["type"] = type_value[0] + return properties + + if isinstance(type_value, list): + properties["anyOf"] = [{"type": t} for t in type_value] + return properties + + return properties @classmethod def _get_impl(cls, properties: JSONSchema) -> type[Self]: diff --git a/jambo/schema_converter.py b/jambo/schema_converter.py index 4a10c2d..c2c8cae 100644 --- a/jambo/schema_converter.py +++ b/jambo/schema_converter.py @@ -1,4 +1,4 @@ -from jambo.exceptions import InvalidSchemaException, UnsupportedSchemaException +from jambo.exceptions import InternalAssertionException, InvalidSchemaException, UnsupportedSchemaException from jambo.parser import ObjectTypeParser, RefTypeParser from jambo.types import JSONSchema, RefCacheDict @@ -135,5 +135,12 @@ class SchemaConverter: """ if "$ref" in schema: return "$ref" + + type_value = schema.get("type") + if isinstance(type_value, list): + raise InternalAssertionException( + "SchemaConverter._get_schema_type: 'type' field should not be a list here." + " This should have been normalized earlier." + ) - return schema.get("type") + return type_value diff --git a/jambo/types/json_schema_type.py b/jambo/types/json_schema_type.py index 3e73387..82b6762 100644 --- a/jambo/types/json_schema_type.py +++ b/jambo/types/json_schema_type.py @@ -42,7 +42,7 @@ JSONSchema = TypedDict( "description": str, "default": JSONType, "examples": List[JSONType], - "type": JSONSchemaType, + "type": JSONSchemaType|List[JSONSchemaType], "enum": List[JSONType], "const": JSONType, "properties": Dict[str, "JSONSchema"], diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index 018c412..01c18be 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -998,3 +998,52 @@ class TestSchemaConverter(TestCase): cached_address_model = self.converter.get_cached_ref("address") self.assertIsNotNone(cached_address_model) + + def test_parse_list_type_multiple_values(self): + schema = { + "title": "TestListType", + "type": "object", + "properties": { + "values": { + "type": ["string", "number"] + } + }, + } + + Model = self.converter.build_with_cache(schema) + + obj1 = Model(values="a string") + self.assertEqual(obj1.values, "a string") + + obj2 = Model(values=42) + self.assertEqual(obj2.values, 42) + + def test_parse_list_type_one_value(self): + schema = { + "title": "TestListType", + "type": "object", + "properties": { + "values": { + "type": ["string"] + } + }, + } + + Model = self.converter.build_with_cache(schema) + + obj1 = Model(values="a string") + self.assertEqual(obj1.values, "a string") + + def test_parse_list_type_empty(self): + schema = { + "title": "TestListType", + "type": "object", + "properties": { + "values": { + "type": [] + } + }, + } + + with self.assertRaises(InvalidSchemaException): + self.converter.build_with_cache(schema) \ No newline at end of file