From 30290771b193fe0976f92191bbf53018fb457d4b Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sun, 14 Sep 2025 00:05:21 -0300 Subject: [PATCH] feat: alters all standart errors and messages for more specific errors --- jambo/parser/anyof_type_parser.py | 10 +++++-- jambo/parser/array_type_parser.py | 15 ++++++++-- jambo/parser/boolean_type_parser.py | 6 +++- jambo/parser/const_type_parser.py | 11 +++++-- jambo/parser/enum_type_parser.py | 16 ++++++++--- jambo/parser/oneof_type_parser.py | 23 +++++++++++---- jambo/parser/ref_type_parser.py | 38 +++++++++++++++---------- jambo/parser/string_type_parser.py | 5 +++- jambo/types/json_schema_type.py | 2 +- tests/parser/test_ref_type_parser.py | 4 +-- tests/parser/test_string_type_parser.py | 3 +- 11 files changed, 94 insertions(+), 39 deletions(-) diff --git a/jambo/parser/anyof_type_parser.py b/jambo/parser/anyof_type_parser.py index 55ff3ec..4b961ac 100644 --- a/jambo/parser/anyof_type_parser.py +++ b/jambo/parser/anyof_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions @@ -14,10 +15,15 @@ class AnyOfTypeParser(GenericTypeParser): self, name, properties, **kwargs: Unpack[TypeParserOptions] ): if "anyOf" not in properties: - raise ValueError(f"Invalid JSON Schema: {properties}") + raise InvalidSchemaException( + f"AnyOf type {name} must have 'anyOf' property defined.", + invalid_field="anyOf", + ) if not isinstance(properties["anyOf"], list): - raise ValueError(f"Invalid JSON Schema: {properties['anyOf']}") + raise InvalidSchemaException( + "AnyOf must be a list of types.", invalid_field="anyOf" + ) mapped_properties = self.mappings_properties_builder(properties, **kwargs) diff --git a/jambo/parser/array_type_parser.py b/jambo/parser/array_type_parser.py index 7d59ea5..790a65a 100644 --- a/jambo/parser/array_type_parser.py +++ b/jambo/parser/array_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions @@ -26,8 +27,15 @@ class ArrayTypeParser(GenericTypeParser): ): item_properties = kwargs.copy() item_properties["required"] = True + + if (items := properties.get("items")) is None: + raise InvalidSchemaException( + f"Array type {name} must have 'items' property defined.", + invalid_field="items", + ) + _item_type, _item_args = GenericTypeParser.type_from_properties( - name, properties["items"], **item_properties + name, items, **item_properties ) wrapper_type = set if properties.get("uniqueItems", False) else list @@ -47,8 +55,9 @@ class ArrayTypeParser(GenericTypeParser): return lambda: None if not isinstance(default_list, Iterable): - raise ValueError( - f"Default value for array must be an iterable, got {type(default_list)}" + raise InvalidSchemaException( + f"Default value for array must be an iterable, got {type(default_list)}", + invalid_field="default", ) return lambda: copy.deepcopy(wrapper_type(default_list)) diff --git a/jambo/parser/boolean_type_parser.py b/jambo/parser/boolean_type_parser.py index ecb703a..d948069 100644 --- a/jambo/parser/boolean_type_parser.py +++ b/jambo/parser/boolean_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions @@ -20,6 +21,9 @@ class BooleanTypeParser(GenericTypeParser): default_value = properties.get("default") if default_value is not None and not isinstance(default_value, bool): - raise ValueError(f"Default value for {name} must be a boolean.") + raise InvalidSchemaException( + f"Default value for {name} must be a boolean.", + invalid_field="default", + ) return bool, mapped_properties diff --git a/jambo/parser/const_type_parser.py b/jambo/parser/const_type_parser.py index da55bc0..76c6893 100644 --- a/jambo/parser/const_type_parser.py +++ b/jambo/parser/const_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser._type_parser import GenericTypeParser from jambo.types.json_schema_type import JSONSchemaNativeTypes from jambo.types.type_parser_options import TypeParserOptions @@ -18,13 +19,17 @@ class ConstTypeParser(GenericTypeParser): self, name, properties, **kwargs: Unpack[TypeParserOptions] ): if "const" not in properties: - raise ValueError(f"Const type {name} must have 'const' property defined.") + raise InvalidSchemaException( + f"Const type {name} must have 'const' property defined.", + invalid_field="const", + ) const_value = properties["const"] if not isinstance(const_value, JSONSchemaNativeTypes): - raise ValueError( - f"Const type {name} must have 'const' value of allowed types: {JSONSchemaNativeTypes}." + raise InvalidSchemaException( + f"Const type {name} must have 'const' value of allowed types: {JSONSchemaNativeTypes}.", + invalid_field="const", ) const_type = self._build_const_type(const_value) diff --git a/jambo/parser/enum_type_parser.py b/jambo/parser/enum_type_parser.py index c59a725..f0b001f 100644 --- a/jambo/parser/enum_type_parser.py +++ b/jambo/parser/enum_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser._type_parser import GenericTypeParser from jambo.types.json_schema_type import JSONSchemaNativeTypes from jambo.types.type_parser_options import JSONSchema, TypeParserOptions @@ -14,16 +15,23 @@ class EnumTypeParser(GenericTypeParser): self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions] ): if "enum" not in properties: - raise ValueError(f"Enum type {name} must have 'enum' property defined.") + raise InvalidSchemaException( + f"Enum type {name} must have 'enum' property defined.", + invalid_field="enum", + ) enum_values = properties["enum"] if not isinstance(enum_values, list): - raise ValueError(f"Enum type {name} must have 'enum' as a list of values.") + raise InvalidSchemaException( + f"Enum type {name} must have 'enum' as a list of values.", + invalid_field="enum", + ) if any(not isinstance(value, JSONSchemaNativeTypes) for value in enum_values): - raise ValueError( - f"Enum type {name} must have 'enum' values of allowed types: {JSONSchemaNativeTypes}." + raise InvalidSchemaException( + f"Enum type {name} must have 'enum' values of allowed types: {JSONSchemaNativeTypes}.", + invalid_field="enum", ) # Create a new Enum type dynamically diff --git a/jambo/parser/oneof_type_parser.py b/jambo/parser/oneof_type_parser.py index 317ce61..4713d9c 100644 --- a/jambo/parser/oneof_type_parser.py +++ b/jambo/parser/oneof_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions @@ -17,10 +18,14 @@ class OneOfTypeParser(GenericTypeParser): self, name, properties, **kwargs: Unpack[TypeParserOptions] ): if "oneOf" not in properties: - raise ValueError(f"Invalid JSON Schema: {properties}") + raise InvalidSchemaException( + f"Invalid JSON Schema: {properties}", invalid_field="oneOf" + ) if not isinstance(properties["oneOf"], list) or len(properties["oneOf"]) == 0: - raise ValueError(f"Invalid JSON Schema: {properties['oneOf']}") + raise InvalidSchemaException( + f"Invalid JSON Schema: {properties['oneOf']}", invalid_field="oneOf" + ) mapped_properties = self.mappings_properties_builder(properties, **kwargs) @@ -58,7 +63,9 @@ class OneOfTypeParser(GenericTypeParser): Build a type with a discriminator. """ if not isinstance(discriminator_prop, dict): - raise ValueError("Discriminator must be a dictionary") + raise InvalidSchemaException( + "Discriminator must be a dictionary", invalid_field="discriminator" + ) for field in subfield_types: field_type, field_info = get_args(field) @@ -66,13 +73,17 @@ class OneOfTypeParser(GenericTypeParser): if issubclass(field_type, BaseModel): continue - raise ValueError( - "When using a discriminator, all subfield types must be of type 'object'." + raise InvalidSchemaException( + "When using a discriminator, all subfield types must be of type 'object'.", + invalid_field="discriminator", ) property_name = discriminator_prop.get("propertyName") if property_name is None or not isinstance(property_name, str): - raise ValueError("Discriminator must have a 'propertyName' key") + raise InvalidSchemaException( + "Discriminator must have a 'propertyName' key", + invalid_field="propertyName", + ) return Annotated[Union[(*subfield_types,)], Field(discriminator=property_name)] diff --git a/jambo/parser/ref_type_parser.py b/jambo/parser/ref_type_parser.py index 7aa435d..615283e 100644 --- a/jambo/parser/ref_type_parser.py +++ b/jambo/parser/ref_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser import GenericTypeParser from jambo.types.json_schema_type import JSONSchema from jambo.types.type_parser_options import TypeParserOptions @@ -17,18 +18,21 @@ class RefTypeParser(GenericTypeParser): self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions] ) -> tuple[RefType, dict]: if "$ref" not in properties: - raise ValueError(f"RefTypeParser: Missing $ref in properties for {name}") + raise InvalidSchemaException( + f"Missing $ref in properties for {name}", invalid_field="$ref" + ) context = kwargs.get("context") if context is None: - raise RuntimeError( - f"RefTypeParser: Missing `content` in properties for {name}" + raise InvalidSchemaException( + f"Missing `context` in properties for {name}", invalid_field="context" ) ref_cache = kwargs.get("ref_cache") if ref_cache is None: - raise RuntimeError( - f"RefTypeParser: Missing `ref_cache` in properties for {name}" + raise InvalidSchemaException( + f"Missing `ref_cache` in properties for {name}", + invalid_field="ref_cache", ) mapped_properties = self.mappings_properties_builder(properties, **kwargs) @@ -63,8 +67,8 @@ class RefTypeParser(GenericTypeParser): ref_name, ref_property, **kwargs ) case _: - raise ValueError( - f"RefTypeParser: Unsupported $ref {ref_property['$ref']}" + raise InvalidSchemaException( + f"Unsupported $ref {ref_property['$ref']}", invalid_field="$ref" ) return mapped_type @@ -93,8 +97,9 @@ class RefTypeParser(GenericTypeParser): if properties.get("$ref") == "#": ref_name = kwargs["context"].get("title") if ref_name is None: - raise ValueError( - "RefTypeParser: Missing title in properties for $ref of Root Reference" + raise InvalidSchemaException( + "Missing title in properties for $ref of Root Reference", + invalid_field="title", ) return "forward_ref", ref_name, {} @@ -104,8 +109,9 @@ class RefTypeParser(GenericTypeParser): ) return "def_ref", target_name, target_property - raise ValueError( - "RefTypeParser: Only Root and $defs references are supported at the moment" + raise InvalidSchemaException( + "Only Root and $defs references are supported at the moment", + invalid_field="$ref", ) def _extract_target_ref( @@ -115,14 +121,16 @@ class RefTypeParser(GenericTypeParser): target_property = kwargs["context"] for prop_name in properties["$ref"].split("/")[1:]: if prop_name not in target_property: - raise ValueError( - f"RefTypeParser: Missing {prop_name} in" - " properties for $ref {properties['$ref']}" + raise InvalidSchemaException( + f"Missing {prop_name} in properties for $ref {properties['$ref']}", + invalid_field=prop_name, ) target_name = prop_name target_property = target_property[prop_name] # type: ignore if not isinstance(target_name, str) or target_property is None: - raise ValueError(f"RefTypeParser: Invalid $ref {properties['$ref']}") + raise InvalidSchemaException( + f"Invalid $ref {properties['$ref']}", invalid_field="$ref" + ) return target_name, target_property diff --git a/jambo/parser/string_type_parser.py b/jambo/parser/string_type_parser.py index 21e1fd6..8ec583e 100644 --- a/jambo/parser/string_type_parser.py +++ b/jambo/parser/string_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions @@ -54,7 +55,9 @@ class StringTypeParser(GenericTypeParser): return str, mapped_properties if format_type not in self.format_type_mapping: - raise ValueError(f"Unsupported string format: {format_type}") + raise InvalidSchemaException( + f"Unsupported string format: {format_type}", invalid_field="format" + ) mapped_type = self.format_type_mapping[format_type] if format_type in self.format_pattern_mapping: diff --git a/jambo/types/json_schema_type.py b/jambo/types/json_schema_type.py index 954db75..3e73387 100644 --- a/jambo/types/json_schema_type.py +++ b/jambo/types/json_schema_type.py @@ -52,7 +52,7 @@ JSONSchema = TypedDict( "minProperties": int, "maxProperties": int, "dependencies": Dict[str, Union[List[str], "JSONSchema"]], - "items": Union["JSONSchema", List["JSONSchema"]], + "items": "JSONSchema", "prefixItems": List["JSONSchema"], "additionalItems": Union[bool, "JSONSchema"], "contains": "JSONSchema", diff --git a/tests/parser/test_ref_type_parser.py b/tests/parser/test_ref_type_parser.py index 3e08ff4..c16d0b6 100644 --- a/tests/parser/test_ref_type_parser.py +++ b/tests/parser/test_ref_type_parser.py @@ -40,7 +40,7 @@ class TestRefTypeParser(TestCase): }, } - with self.assertRaises(RuntimeError): + with self.assertRaises(ValueError): RefTypeParser().from_properties( "person", properties, @@ -63,7 +63,7 @@ class TestRefTypeParser(TestCase): }, } - with self.assertRaises(RuntimeError): + with self.assertRaises(ValueError): RefTypeParser().from_properties( "person", properties, diff --git a/tests/parser/test_string_type_parser.py b/tests/parser/test_string_type_parser.py index 279e20f..2a242e2 100644 --- a/tests/parser/test_string_type_parser.py +++ b/tests/parser/test_string_type_parser.py @@ -187,7 +187,8 @@ class TestStringTypeParser(TestCase): parser.from_properties("placeholder", properties) self.assertEqual( - str(context.exception), "Unsupported string format: unsupported-format" + str(context.exception), + "Invalid JSON Schema: Unsupported string format: unsupported-format (invalid field: format)", ) def test_string_parser_with_date_format(self):