Feature/explicit exception type #47

Merged
HideyoshiNakazone merged 6 commits from feature/explicit-exception-type into main 2025-09-14 04:13:28 +00:00
11 changed files with 94 additions and 39 deletions
Showing only changes of commit 30290771b1 - Show all commits

View File

@@ -1,3 +1,4 @@
from jambo.exceptions import InvalidSchemaException
from jambo.parser._type_parser import GenericTypeParser from jambo.parser._type_parser import GenericTypeParser
from jambo.types.type_parser_options import TypeParserOptions from jambo.types.type_parser_options import TypeParserOptions
@@ -14,10 +15,15 @@ class AnyOfTypeParser(GenericTypeParser):
self, name, properties, **kwargs: Unpack[TypeParserOptions] self, name, properties, **kwargs: Unpack[TypeParserOptions]
): ):
if "anyOf" not in properties: 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): 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) mapped_properties = self.mappings_properties_builder(properties, **kwargs)

View File

@@ -1,3 +1,4 @@
from jambo.exceptions import InvalidSchemaException
from jambo.parser._type_parser import GenericTypeParser from jambo.parser._type_parser import GenericTypeParser
from jambo.types.type_parser_options import TypeParserOptions from jambo.types.type_parser_options import TypeParserOptions
@@ -26,8 +27,15 @@ class ArrayTypeParser(GenericTypeParser):
): ):
item_properties = kwargs.copy() item_properties = kwargs.copy()
item_properties["required"] = True 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( _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 wrapper_type = set if properties.get("uniqueItems", False) else list
@@ -47,8 +55,9 @@ class ArrayTypeParser(GenericTypeParser):
return lambda: None return lambda: None
if not isinstance(default_list, Iterable): if not isinstance(default_list, Iterable):
raise ValueError( raise InvalidSchemaException(
f"Default value for array must be an iterable, got {type(default_list)}" f"Default value for array must be an iterable, got {type(default_list)}",
invalid_field="default",
) )
return lambda: copy.deepcopy(wrapper_type(default_list)) return lambda: copy.deepcopy(wrapper_type(default_list))

View File

@@ -1,3 +1,4 @@
from jambo.exceptions import InvalidSchemaException
from jambo.parser._type_parser import GenericTypeParser from jambo.parser._type_parser import GenericTypeParser
from jambo.types.type_parser_options import TypeParserOptions from jambo.types.type_parser_options import TypeParserOptions
@@ -20,6 +21,9 @@ class BooleanTypeParser(GenericTypeParser):
default_value = properties.get("default") default_value = properties.get("default")
if default_value is not None and not isinstance(default_value, bool): 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 return bool, mapped_properties

View File

@@ -1,3 +1,4 @@
from jambo.exceptions import InvalidSchemaException
from jambo.parser._type_parser import GenericTypeParser from jambo.parser._type_parser import GenericTypeParser
from jambo.types.json_schema_type import JSONSchemaNativeTypes from jambo.types.json_schema_type import JSONSchemaNativeTypes
from jambo.types.type_parser_options import TypeParserOptions from jambo.types.type_parser_options import TypeParserOptions
@@ -18,13 +19,17 @@ class ConstTypeParser(GenericTypeParser):
self, name, properties, **kwargs: Unpack[TypeParserOptions] self, name, properties, **kwargs: Unpack[TypeParserOptions]
): ):
if "const" not in properties: 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"] const_value = properties["const"]
if not isinstance(const_value, JSONSchemaNativeTypes): if not isinstance(const_value, JSONSchemaNativeTypes):
raise ValueError( raise InvalidSchemaException(
f"Const type {name} must have 'const' value of allowed types: {JSONSchemaNativeTypes}." f"Const type {name} must have 'const' value of allowed types: {JSONSchemaNativeTypes}.",
invalid_field="const",
) )
const_type = self._build_const_type(const_value) const_type = self._build_const_type(const_value)

View File

@@ -1,3 +1,4 @@
from jambo.exceptions import InvalidSchemaException
from jambo.parser._type_parser import GenericTypeParser from jambo.parser._type_parser import GenericTypeParser
from jambo.types.json_schema_type import JSONSchemaNativeTypes from jambo.types.json_schema_type import JSONSchemaNativeTypes
from jambo.types.type_parser_options import JSONSchema, TypeParserOptions from jambo.types.type_parser_options import JSONSchema, TypeParserOptions
@@ -14,16 +15,23 @@ class EnumTypeParser(GenericTypeParser):
self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions] self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions]
): ):
if "enum" not in properties: 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"] enum_values = properties["enum"]
if not isinstance(enum_values, list): 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): if any(not isinstance(value, JSONSchemaNativeTypes) for value in enum_values):
raise ValueError( raise InvalidSchemaException(
f"Enum type {name} must have 'enum' values of allowed types: {JSONSchemaNativeTypes}." f"Enum type {name} must have 'enum' values of allowed types: {JSONSchemaNativeTypes}.",
invalid_field="enum",
) )
# Create a new Enum type dynamically # Create a new Enum type dynamically

View File

@@ -1,3 +1,4 @@
from jambo.exceptions import InvalidSchemaException
from jambo.parser._type_parser import GenericTypeParser from jambo.parser._type_parser import GenericTypeParser
from jambo.types.type_parser_options import TypeParserOptions from jambo.types.type_parser_options import TypeParserOptions
@@ -17,10 +18,14 @@ class OneOfTypeParser(GenericTypeParser):
self, name, properties, **kwargs: Unpack[TypeParserOptions] self, name, properties, **kwargs: Unpack[TypeParserOptions]
): ):
if "oneOf" not in properties: 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: 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) mapped_properties = self.mappings_properties_builder(properties, **kwargs)
@@ -58,7 +63,9 @@ class OneOfTypeParser(GenericTypeParser):
Build a type with a discriminator. Build a type with a discriminator.
""" """
if not isinstance(discriminator_prop, dict): 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: for field in subfield_types:
field_type, field_info = get_args(field) field_type, field_info = get_args(field)
@@ -66,13 +73,17 @@ class OneOfTypeParser(GenericTypeParser):
if issubclass(field_type, BaseModel): if issubclass(field_type, BaseModel):
continue continue
raise ValueError( raise InvalidSchemaException(
"When using a discriminator, all subfield types must be of type 'object'." "When using a discriminator, all subfield types must be of type 'object'.",
invalid_field="discriminator",
) )
property_name = discriminator_prop.get("propertyName") property_name = discriminator_prop.get("propertyName")
if property_name is None or not isinstance(property_name, str): 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)] return Annotated[Union[(*subfield_types,)], Field(discriminator=property_name)]

View File

@@ -1,3 +1,4 @@
from jambo.exceptions import InvalidSchemaException
from jambo.parser import GenericTypeParser from jambo.parser import GenericTypeParser
from jambo.types.json_schema_type import JSONSchema from jambo.types.json_schema_type import JSONSchema
from jambo.types.type_parser_options import TypeParserOptions from jambo.types.type_parser_options import TypeParserOptions
@@ -17,18 +18,21 @@ class RefTypeParser(GenericTypeParser):
self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions] self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions]
) -> tuple[RefType, dict]: ) -> tuple[RefType, dict]:
if "$ref" not in properties: 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") context = kwargs.get("context")
if context is None: if context is None:
raise RuntimeError( raise InvalidSchemaException(
f"RefTypeParser: Missing `content` in properties for {name}" f"Missing `context` in properties for {name}", invalid_field="context"
) )
ref_cache = kwargs.get("ref_cache") ref_cache = kwargs.get("ref_cache")
if ref_cache is None: if ref_cache is None:
raise RuntimeError( raise InvalidSchemaException(
f"RefTypeParser: Missing `ref_cache` in properties for {name}" f"Missing `ref_cache` in properties for {name}",
invalid_field="ref_cache",
) )
mapped_properties = self.mappings_properties_builder(properties, **kwargs) mapped_properties = self.mappings_properties_builder(properties, **kwargs)
@@ -63,8 +67,8 @@ class RefTypeParser(GenericTypeParser):
ref_name, ref_property, **kwargs ref_name, ref_property, **kwargs
) )
case _: case _:
raise ValueError( raise InvalidSchemaException(
f"RefTypeParser: Unsupported $ref {ref_property['$ref']}" f"Unsupported $ref {ref_property['$ref']}", invalid_field="$ref"
) )
return mapped_type return mapped_type
@@ -93,8 +97,9 @@ class RefTypeParser(GenericTypeParser):
if properties.get("$ref") == "#": if properties.get("$ref") == "#":
ref_name = kwargs["context"].get("title") ref_name = kwargs["context"].get("title")
if ref_name is None: if ref_name is None:
raise ValueError( raise InvalidSchemaException(
"RefTypeParser: Missing title in properties for $ref of Root Reference" "Missing title in properties for $ref of Root Reference",
invalid_field="title",
) )
return "forward_ref", ref_name, {} return "forward_ref", ref_name, {}
@@ -104,8 +109,9 @@ class RefTypeParser(GenericTypeParser):
) )
return "def_ref", target_name, target_property return "def_ref", target_name, target_property
raise ValueError( raise InvalidSchemaException(
"RefTypeParser: Only Root and $defs references are supported at the moment" "Only Root and $defs references are supported at the moment",
invalid_field="$ref",
) )
def _extract_target_ref( def _extract_target_ref(
@@ -115,14 +121,16 @@ class RefTypeParser(GenericTypeParser):
target_property = kwargs["context"] target_property = kwargs["context"]
for prop_name in properties["$ref"].split("/")[1:]: for prop_name in properties["$ref"].split("/")[1:]:
if prop_name not in target_property: if prop_name not in target_property:
raise ValueError( raise InvalidSchemaException(
f"RefTypeParser: Missing {prop_name} in" f"Missing {prop_name} in properties for $ref {properties['$ref']}",
" properties for $ref {properties['$ref']}" invalid_field=prop_name,
) )
target_name = prop_name target_name = prop_name
target_property = target_property[prop_name] # type: ignore target_property = target_property[prop_name] # type: ignore
if not isinstance(target_name, str) or target_property is None: 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 return target_name, target_property

View File

@@ -1,3 +1,4 @@
from jambo.exceptions import InvalidSchemaException
from jambo.parser._type_parser import GenericTypeParser from jambo.parser._type_parser import GenericTypeParser
from jambo.types.type_parser_options import TypeParserOptions from jambo.types.type_parser_options import TypeParserOptions
@@ -54,7 +55,9 @@ class StringTypeParser(GenericTypeParser):
return str, mapped_properties return str, mapped_properties
if format_type not in self.format_type_mapping: 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] mapped_type = self.format_type_mapping[format_type]
if format_type in self.format_pattern_mapping: if format_type in self.format_pattern_mapping:

View File

@@ -52,7 +52,7 @@ JSONSchema = TypedDict(
"minProperties": int, "minProperties": int,
"maxProperties": int, "maxProperties": int,
"dependencies": Dict[str, Union[List[str], "JSONSchema"]], "dependencies": Dict[str, Union[List[str], "JSONSchema"]],
"items": Union["JSONSchema", List["JSONSchema"]], "items": "JSONSchema",
"prefixItems": List["JSONSchema"], "prefixItems": List["JSONSchema"],
"additionalItems": Union[bool, "JSONSchema"], "additionalItems": Union[bool, "JSONSchema"],
"contains": "JSONSchema", "contains": "JSONSchema",

View File

@@ -40,7 +40,7 @@ class TestRefTypeParser(TestCase):
}, },
} }
with self.assertRaises(RuntimeError): with self.assertRaises(ValueError):
RefTypeParser().from_properties( RefTypeParser().from_properties(
"person", "person",
properties, properties,
@@ -63,7 +63,7 @@ class TestRefTypeParser(TestCase):
}, },
} }
with self.assertRaises(RuntimeError): with self.assertRaises(ValueError):
RefTypeParser().from_properties( RefTypeParser().from_properties(
"person", "person",
properties, properties,

View File

@@ -187,7 +187,8 @@ class TestStringTypeParser(TestCase):
parser.from_properties("placeholder", properties) parser.from_properties("placeholder", properties)
self.assertEqual( 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): def test_string_parser_with_date_format(self):