diff --git a/jambo/parser/_type_parser.py b/jambo/parser/_type_parser.py index efca162..bcc908b 100644 --- a/jambo/parser/_type_parser.py +++ b/jambo/parser/_type_parser.py @@ -73,10 +73,39 @@ 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]: for subcls in cls.__subclasses__(): diff --git a/jambo/schema_converter.py b/jambo/schema_converter.py index 4a10c2d..37a062d 100644 --- a/jambo/schema_converter.py +++ b/jambo/schema_converter.py @@ -136,4 +136,11 @@ class SchemaConverter: if "$ref" in schema: return "$ref" - return schema.get("type") + type_value = schema.get("type") + if isinstance(type_value, list): + raise InvalidSchemaException( + "Invalid schema: 'type' cannot be a list at the top level", + invalid_field=str(schema), + ) + + return type_value diff --git a/jambo/types/json_schema_type.py b/jambo/types/json_schema_type.py index 3e73387..c720106 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..450e441 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -998,3 +998,46 @@ 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) + + def test_parse_list_type_root_level_throws(self): + schema = {"title": "TestListType", "type": ["string", "number"]} + + with self.assertRaises(InvalidSchemaException): + self.converter.build_with_cache(schema)