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
17 changed files with 179 additions and 124 deletions
Showing only changes of commit e31002af32 - Show all commits

View File

@@ -1,5 +1,10 @@
from .internal_assertion_exception import InternalAssertionException
from .invalid_schema_exception import InvalidSchemaException from .invalid_schema_exception import InvalidSchemaException
from .unsupported_schema_exception import UnsupportedSchemaException from .unsupported_schema_exception import UnsupportedSchemaException
__all__ = ["InvalidSchemaException", "UnsupportedSchemaException"] __all__ = [
"InternalAssertionException",
"InvalidSchemaException",
"UnsupportedSchemaException",
]

View File

@@ -0,0 +1,16 @@
class InternalAssertionException(AssertionError):
"""Exception raised for internal assertions."""
def __init__(
self,
message: str,
) -> None:
# Normalize message by stripping redundant prefix if present
message = message.removeprefix("Internal Assertion Failed: ")
super().__init__(message)
def __str__(self) -> str:
return (
f"Internal Assertion Failed: {super().__str__()}\n"
"This is likely a bug in Jambo. Please report it at"
)

View File

@@ -1,4 +1,4 @@
from jambo.exceptions import InvalidSchemaException from jambo.exceptions import InternalAssertionException, 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
@@ -22,17 +22,15 @@ class RefTypeParser(GenericTypeParser):
f"Missing $ref in properties for {name}", invalid_field="$ref" f"Missing $ref in properties for {name}", invalid_field="$ref"
) )
context = kwargs.get("context") if kwargs.get("context") is None:
if context is None: raise InternalAssertionException(
raise InvalidSchemaException( "`context` must be provided in kwargs for RefTypeParser"
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 InvalidSchemaException( raise InternalAssertionException(
f"Missing `ref_cache` in properties for {name}", "`ref_cache` must be provided in kwargs for RefTypeParser"
invalid_field="ref_cache",
) )
mapped_properties = self.mappings_properties_builder(properties, **kwargs) mapped_properties = self.mappings_properties_builder(properties, **kwargs)

View File

@@ -23,19 +23,19 @@ class StringTypeParser(GenericTypeParser):
} }
format_type_mapping = { format_type_mapping = {
# 7.3.1. Dates, Times, and Duration # [7.3.1](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7.3.1). Dates, Times, and Duration
"date": date, "date": date,
"time": time, "time": time,
"date-time": datetime, "date-time": datetime,
"duration": timedelta, "duration": timedelta,
# 7.3.2. Email Addresses # [7.3.2](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7.3.2). Email Addresses
"email": EmailStr, "email": EmailStr,
# 7.3.3. Hostnames # [7.3.3](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7.3.3). Hostnames
"hostname": str, "hostname": str,
# 7.3.4. IP Addresses # [7.3.4](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7.3.4). IP Addresses
"ipv4": IPv4Address, "ipv4": IPv4Address,
"ipv6": IPv6Address, "ipv6": IPv6Address,
# 7.3.5. Resource Identifiers # [7.3.5](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7.3.5). Resource Identifiers
"uri": AnyUrl, "uri": AnyUrl,
# "iri" # Not supported by pydantic and currently not supported by jambo # "iri" # Not supported by pydantic and currently not supported by jambo
"uuid": UUID, "uuid": UUID,

View File

@@ -1,5 +1,8 @@
from jambo.exceptions import InvalidSchemaException
from jambo.parser.allof_type_parser import AllOfTypeParser from jambo.parser.allof_type_parser import AllOfTypeParser
from pydantic import ValidationError
from unittest import TestCase from unittest import TestCase
@@ -42,13 +45,13 @@ class TestAllOfTypeParser(TestCase):
"placeholder", properties "placeholder", properties
) )
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
type_parsing(name="John", age=101) type_parsing(name="John", age=101)
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
type_parsing(name="", age=30) type_parsing(name="", age=30)
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
type_parsing(name="John Invalid", age=30) type_parsing(name="John Invalid", age=30)
obj = type_parsing(name="John", age=30) obj = type_parsing(name="John", age=30)
@@ -87,10 +90,10 @@ class TestAllOfTypeParser(TestCase):
"placeholder", properties "placeholder", properties
) )
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
type_parsing(name="John") type_parsing(name="John")
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
type_parsing(age=30) type_parsing(age=30)
obj = type_parsing(name="John", age=30) obj = type_parsing(name="John", age=30)
@@ -154,7 +157,7 @@ class TestAllOfTypeParser(TestCase):
] ]
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
AllOfTypeParser().from_properties("placeholder", properties) AllOfTypeParser().from_properties("placeholder", properties)
def test_all_of_invalid_type_not_present(self): def test_all_of_invalid_type_not_present(self):
@@ -167,7 +170,7 @@ class TestAllOfTypeParser(TestCase):
] ]
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
AllOfTypeParser().from_properties("placeholder", properties) AllOfTypeParser().from_properties("placeholder", properties)
def test_all_of_invalid_type_in_fields(self): def test_all_of_invalid_type_in_fields(self):
@@ -180,7 +183,7 @@ class TestAllOfTypeParser(TestCase):
] ]
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
AllOfTypeParser().from_properties("placeholder", properties) AllOfTypeParser().from_properties("placeholder", properties)
def test_all_of_invalid_type_not_all_equal(self): def test_all_of_invalid_type_not_all_equal(self):
@@ -196,7 +199,7 @@ class TestAllOfTypeParser(TestCase):
] ]
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
AllOfTypeParser().from_properties("placeholder", properties) AllOfTypeParser().from_properties("placeholder", properties)
def test_all_of_description_field(self): def test_all_of_description_field(self):
@@ -304,5 +307,5 @@ class TestAllOfTypeParser(TestCase):
], ],
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
AllOfTypeParser().from_properties("placeholder", properties) AllOfTypeParser().from_properties("placeholder", properties)

View File

@@ -1,3 +1,4 @@
from jambo.exceptions import InvalidSchemaException
from jambo.parser.anyof_type_parser import AnyOfTypeParser from jambo.parser.anyof_type_parser import AnyOfTypeParser
from typing_extensions import Annotated, Union, get_args, get_origin from typing_extensions import Annotated, Union, get_args, get_origin
@@ -14,7 +15,7 @@ class TestAnyOfTypeParser(TestCase):
], ],
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
AnyOfTypeParser().from_properties("placeholder", properties) AnyOfTypeParser().from_properties("placeholder", properties)
def test_any_of_with_invalid_properties(self): def test_any_of_with_invalid_properties(self):
@@ -22,7 +23,7 @@ class TestAnyOfTypeParser(TestCase):
"anyOf": None, "anyOf": None,
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
AnyOfTypeParser().from_properties("placeholder", properties) AnyOfTypeParser().from_properties("placeholder", properties)
def test_any_of_string_or_int(self): def test_any_of_string_or_int(self):
@@ -95,5 +96,5 @@ class TestAnyOfTypeParser(TestCase):
"default": 3.14, "default": 3.14,
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
AnyOfTypeParser().from_properties("placeholder", properties) AnyOfTypeParser().from_properties("placeholder", properties)

View File

@@ -1,3 +1,4 @@
from jambo.exceptions import InvalidSchemaException
from jambo.parser import ArrayTypeParser from jambo.parser import ArrayTypeParser
from typing_extensions import get_args from typing_extensions import get_args
@@ -67,7 +68,7 @@ class TestArrayTypeParser(TestCase):
properties = {"items": {"type": "string"}, "default": ["a", 1, "c"]} properties = {"items": {"type": "string"}, "default": ["a", 1, "c"]}
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
parser.from_properties("placeholder", properties) parser.from_properties("placeholder", properties)
def test_array_parser_with_invalid_default_type(self): def test_array_parser_with_invalid_default_type(self):
@@ -75,15 +76,15 @@ class TestArrayTypeParser(TestCase):
properties = {"items": {"type": "string"}, "default": 000} properties = {"items": {"type": "string"}, "default": 000}
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
parser.from_properties("placeholder", properties) parser.from_properties("placeholder", properties=properties)
def test_array_parser_with_invalid_default_min(self): def test_array_parser_with_invalid_default_min(self):
parser = ArrayTypeParser() parser = ArrayTypeParser()
properties = {"items": {"type": "string"}, "default": ["a"], "minItems": 2} properties = {"items": {"type": "string"}, "default": ["a"], "minItems": 2}
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
parser.from_properties("placeholder", properties) parser.from_properties("placeholder", properties)
def test_array_parser_with_invalid_default_max(self): def test_array_parser_with_invalid_default_max(self):
@@ -95,5 +96,5 @@ class TestArrayTypeParser(TestCase):
"maxItems": 3, "maxItems": 3,
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
parser.from_properties("placeholder", properties) parser.from_properties("placeholder", properties)

View File

@@ -1,3 +1,4 @@
from jambo.exceptions import InvalidSchemaException
from jambo.parser import BooleanTypeParser from jambo.parser import BooleanTypeParser
from unittest import TestCase from unittest import TestCase
@@ -39,5 +40,5 @@ class TestBoolTypeParser(TestCase):
"default": "invalid", "default": "invalid",
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
parser.from_properties_impl("placeholder", properties) parser.from_properties_impl("placeholder", properties)

View File

@@ -1,3 +1,4 @@
from jambo.exceptions import InvalidSchemaException
from jambo.parser import ConstTypeParser from jambo.parser import ConstTypeParser
from typing_extensions import Annotated, Literal, get_args, get_origin from typing_extensions import Annotated, Literal, get_args, get_origin
@@ -80,7 +81,7 @@ class TestConstTypeParser(TestCase):
expected_const_value = "United States of America" expected_const_value = "United States of America"
properties = {"notConst": expected_const_value} properties = {"notConst": expected_const_value}
with self.assertRaises(ValueError) as context: with self.assertRaises(InvalidSchemaException) as context:
parser.from_properties_impl("invalid_country", properties) parser.from_properties_impl("invalid_country", properties)
self.assertIn( self.assertIn(
@@ -93,7 +94,7 @@ class TestConstTypeParser(TestCase):
properties = {"const": {}} properties = {"const": {}}
with self.assertRaises(ValueError) as context: with self.assertRaises(InvalidSchemaException) as context:
parser.from_properties_impl("invalid_country", properties) parser.from_properties_impl("invalid_country", properties)
self.assertIn( self.assertIn(

View File

@@ -1,3 +1,4 @@
from jambo.exceptions import InvalidSchemaException
from jambo.parser import EnumTypeParser from jambo.parser import EnumTypeParser
from enum import Enum from enum import Enum
@@ -10,7 +11,7 @@ class TestEnumTypeParser(TestCase):
schema = {} schema = {}
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
parsed_type, parsed_properties = parser.from_properties_impl( parsed_type, parsed_properties = parser.from_properties_impl(
"TestEnum", "TestEnum",
schema, schema,
@@ -23,7 +24,7 @@ class TestEnumTypeParser(TestCase):
"enum": "not_a_list", "enum": "not_a_list",
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
parsed_type, parsed_properties = parser.from_properties_impl( parsed_type, parsed_properties = parser.from_properties_impl(
"TestEnum", "TestEnum",
schema, schema,
@@ -86,5 +87,5 @@ class TestEnumTypeParser(TestCase):
"enum": ["value1", 42, dict()], "enum": ["value1", 42, dict()],
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
parser.from_properties_impl("TestEnum", schema) parser.from_properties_impl("TestEnum", schema)

View File

@@ -1,3 +1,4 @@
from jambo.exceptions import InvalidSchemaException
from jambo.parser import FloatTypeParser from jambo.parser import FloatTypeParser
from unittest import TestCase from unittest import TestCase
@@ -61,7 +62,7 @@ class TestFloatTypeParser(TestCase):
"multipleOf": 0.5, "multipleOf": 0.5,
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
parser.from_properties("placeholder", properties) parser.from_properties("placeholder", properties)
def test_float_parser_with_default_invalid_maximum(self): def test_float_parser_with_default_invalid_maximum(self):
@@ -75,7 +76,7 @@ class TestFloatTypeParser(TestCase):
"multipleOf": 0.5, "multipleOf": 0.5,
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
parser.from_properties("placeholder", properties) parser.from_properties("placeholder", properties)
def test_float_parser_with_default_invalid_minimum(self): def test_float_parser_with_default_invalid_minimum(self):
@@ -89,7 +90,7 @@ class TestFloatTypeParser(TestCase):
"multipleOf": 0.5, "multipleOf": 0.5,
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
parser.from_properties("placeholder", properties) parser.from_properties("placeholder", properties)
def test_float_parser_with_default_invalid_exclusive_maximum(self): def test_float_parser_with_default_invalid_exclusive_maximum(self):
@@ -103,7 +104,7 @@ class TestFloatTypeParser(TestCase):
"multipleOf": 0.5, "multipleOf": 0.5,
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
parser.from_properties("placeholder", properties) parser.from_properties("placeholder", properties)
def test_float_parser_with_default_invalid_exclusive_minimum(self): def test_float_parser_with_default_invalid_exclusive_minimum(self):
@@ -117,7 +118,7 @@ class TestFloatTypeParser(TestCase):
"multipleOf": 0.5, "multipleOf": 0.5,
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
parser.from_properties("placeholder", properties) parser.from_properties("placeholder", properties)
def test_float_parser_with_default_invalid_multiple(self): def test_float_parser_with_default_invalid_multiple(self):
@@ -131,5 +132,5 @@ class TestFloatTypeParser(TestCase):
"multipleOf": 2.0, "multipleOf": 2.0,
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
parser.from_properties("placeholder", properties) parser.from_properties("placeholder", properties)

View File

@@ -1,3 +1,4 @@
from jambo.exceptions import InvalidSchemaException
from jambo.parser import IntTypeParser from jambo.parser import IntTypeParser
from unittest import TestCase from unittest import TestCase
@@ -61,7 +62,7 @@ class TestIntTypeParser(TestCase):
"multipleOf": 2, "multipleOf": 2,
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
parser.from_properties("placeholder", properties) parser.from_properties("placeholder", properties)
def test_int_parser_with_default_invalid_maximum(self): def test_int_parser_with_default_invalid_maximum(self):
@@ -75,7 +76,7 @@ class TestIntTypeParser(TestCase):
"multipleOf": 2, "multipleOf": 2,
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
parser.from_properties("placeholder", properties) parser.from_properties("placeholder", properties)
def test_int_parser_with_default_invalid_minimum(self): def test_int_parser_with_default_invalid_minimum(self):
@@ -89,7 +90,7 @@ class TestIntTypeParser(TestCase):
"multipleOf": 2, "multipleOf": 2,
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
parser.from_properties("placeholder", properties) parser.from_properties("placeholder", properties)
def test_int_parser_with_default_invalid_exclusive_maximum(self): def test_int_parser_with_default_invalid_exclusive_maximum(self):
@@ -103,7 +104,7 @@ class TestIntTypeParser(TestCase):
"multipleOf": 2, "multipleOf": 2,
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
parser.from_properties("placeholder", properties) parser.from_properties("placeholder", properties)
def test_int_parser_with_default_invalid_exclusive_minimum(self): def test_int_parser_with_default_invalid_exclusive_minimum(self):
@@ -117,7 +118,7 @@ class TestIntTypeParser(TestCase):
"multipleOf": 2, "multipleOf": 2,
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
parser.from_properties("placeholder", properties) parser.from_properties("placeholder", properties)
def test_int_parser_with_default_invalid_multipleOf(self): def test_int_parser_with_default_invalid_multipleOf(self):
@@ -131,5 +132,5 @@ class TestIntTypeParser(TestCase):
"multipleOf": 2, "multipleOf": 2,
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
parser.from_properties("placeholder", properties) parser.from_properties("placeholder", properties)

View File

@@ -1,12 +1,15 @@
from jambo import SchemaConverter from jambo import SchemaConverter
from jambo.exceptions import InvalidSchemaException
from jambo.parser.oneof_type_parser import OneOfTypeParser from jambo.parser.oneof_type_parser import OneOfTypeParser
from pydantic import ValidationError
from unittest import TestCase from unittest import TestCase
class TestOneOfTypeParser(TestCase): class TestOneOfTypeParser(TestCase):
def test_oneof_raises_on_invalid_property(self): def test_oneof_raises_on_invalid_property(self):
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
OneOfTypeParser().from_properties_impl( OneOfTypeParser().from_properties_impl(
"test_field", "test_field",
{ {
@@ -17,7 +20,7 @@ class TestOneOfTypeParser(TestCase):
ref_cache={}, ref_cache={},
) )
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
SchemaConverter.build( SchemaConverter.build(
{ {
"title": "Test", "title": "Test",
@@ -71,13 +74,13 @@ class TestOneOfTypeParser(TestCase):
Model = SchemaConverter.build(schema) Model = SchemaConverter.build(schema)
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
Model(id=-5) Model(id=-5)
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
Model(id="invalid") Model(id="invalid")
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
Model(id=123.45) Model(id=123.45)
def test_oneof_with_conflicting_schemas(self): def test_oneof_with_conflicting_schemas(self):
@@ -103,11 +106,11 @@ class TestOneOfTypeParser(TestCase):
obj2 = Model(data=9) obj2 = Model(data=9)
self.assertEqual(obj2.data, 9) self.assertEqual(obj2.data, 9)
with self.assertRaises(ValueError) as cm: with self.assertRaises(ValidationError) as cm:
Model(data=6) Model(data=6)
self.assertIn("matches multiple oneOf schemas", str(cm.exception)) self.assertIn("matches multiple oneOf schemas", str(cm.exception))
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
Model(data=5) Model(data=5)
def test_oneof_with_objects(self): def test_oneof_with_objects(self):
@@ -147,7 +150,7 @@ class TestOneOfTypeParser(TestCase):
obj2 = Model(contact_info={"phone": "123-456-7890"}) obj2 = Model(contact_info={"phone": "123-456-7890"})
self.assertEqual(obj2.contact_info.phone, "123-456-7890") self.assertEqual(obj2.contact_info.phone, "123-456-7890")
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
Model(contact_info={"email": "user@example.com", "phone": "123-456-7890"}) Model(contact_info={"email": "user@example.com", "phone": "123-456-7890"})
def test_oneof_with_discriminator_basic(self): def test_oneof_with_discriminator_basic(self):
@@ -190,14 +193,14 @@ class TestOneOfTypeParser(TestCase):
self.assertEqual(dog.pet.type, "dog") self.assertEqual(dog.pet.type, "dog")
self.assertEqual(dog.pet.barks, False) self.assertEqual(dog.pet.barks, False)
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
Model(pet={"type": "cat", "barks": True}) Model(pet={"type": "cat", "barks": True})
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
Model(pet={"type": "bird", "flies": True}) Model(pet={"type": "bird", "flies": True})
def test_oneof_with_invalid_types(self): def test_oneof_with_invalid_types(self):
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
SchemaConverter.build( SchemaConverter.build(
{ {
"title": "Pet", "title": "Pet",
@@ -301,13 +304,13 @@ class TestOneOfTypeParser(TestCase):
Model = SchemaConverter.build(schema) Model = SchemaConverter.build(schema)
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
Model(shape={"type": "triangle", "base": 5, "height": 3}) Model(shape={"type": "triangle", "base": 5, "height": 3})
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
Model(shape={"type": "circle", "side": 5}) Model(shape={"type": "circle", "side": 5})
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
Model(shape={"radius": 5}) Model(shape={"radius": 5})
def test_oneof_missing_properties(self): def test_oneof_missing_properties(self):
@@ -324,7 +327,7 @@ class TestOneOfTypeParser(TestCase):
}, },
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
SchemaConverter.build(schema) SchemaConverter.build(schema)
def test_oneof_invalid_properties(self): def test_oneof_invalid_properties(self):
@@ -336,7 +339,7 @@ class TestOneOfTypeParser(TestCase):
}, },
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
SchemaConverter.build(schema) SchemaConverter.build(schema)
def test_oneof_with_default_value(self): def test_oneof_with_default_value(self):
@@ -373,12 +376,12 @@ class TestOneOfTypeParser(TestCase):
}, },
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
SchemaConverter.build(schema) SchemaConverter.build(schema)
def test_oneof_discriminator_without_property_name(self): def test_oneof_discriminator_without_property_name(self):
# Should throw because the spec determines propertyName is required for discriminator # Should throw because the spec determines propertyName is required for discriminator
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
SchemaConverter.build( SchemaConverter.build(
{ {
"title": "Test", "title": "Test",
@@ -409,7 +412,7 @@ class TestOneOfTypeParser(TestCase):
def test_oneof_discriminator_with_invalid_discriminator(self): def test_oneof_discriminator_with_invalid_discriminator(self):
# Should throw because a valid discriminator is required # Should throw because a valid discriminator is required
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
SchemaConverter.build( SchemaConverter.build(
{ {
"title": "Test", "title": "Test",
@@ -465,8 +468,9 @@ class TestOneOfTypeParser(TestCase):
self.assertEqual(obj2.value, "very long string") self.assertEqual(obj2.value, "very long string")
# Invalid: Medium string (matches BOTH schemas - violates oneOf) # Invalid: Medium string (matches BOTH schemas - violates oneOf)
with self.assertRaises(ValueError) as cm: with self.assertRaises(ValidationError) as cm:
Model(value="hello") # 5 chars: matches maxLength=6 AND minLength=4 Model(value="hello") # 5 chars: matches maxLength=6 AND minLength=4
self.assertIn("matches multiple oneOf schemas", str(cm.exception)) self.assertIn("matches multiple oneOf schemas", str(cm.exception))
def test_oneof_shapes_discriminator_from_docs(self): def test_oneof_shapes_discriminator_from_docs(self):
@@ -515,5 +519,5 @@ class TestOneOfTypeParser(TestCase):
self.assertEqual(rectangle.shape.height, 20) self.assertEqual(rectangle.shape.height, 20)
# Invalid: Wrong properties for the type # Invalid: Wrong properties for the type
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
Model(shape={"type": "circle", "width": 10}) Model(shape={"type": "circle", "width": 10})

View File

@@ -1,5 +1,8 @@
from jambo.exceptions import InternalAssertionException, InvalidSchemaException
from jambo.parser import ObjectTypeParser, RefTypeParser from jambo.parser import ObjectTypeParser, RefTypeParser
from pydantic import ValidationError
from typing import ForwardRef from typing import ForwardRef
from unittest import TestCase from unittest import TestCase
@@ -16,7 +19,7 @@ class TestRefTypeParser(TestCase):
"required": ["name", "age"], "required": ["name", "age"],
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
RefTypeParser().from_properties( RefTypeParser().from_properties(
"person", "person",
properties, properties,
@@ -40,7 +43,7 @@ class TestRefTypeParser(TestCase):
}, },
} }
with self.assertRaises(ValueError): with self.assertRaises(InternalAssertionException):
RefTypeParser().from_properties( RefTypeParser().from_properties(
"person", "person",
properties, properties,
@@ -63,7 +66,7 @@ class TestRefTypeParser(TestCase):
}, },
} }
with self.assertRaises(ValueError): with self.assertRaises(InternalAssertionException):
RefTypeParser().from_properties( RefTypeParser().from_properties(
"person", "person",
properties, properties,
@@ -77,7 +80,7 @@ class TestRefTypeParser(TestCase):
"$ref": "https://example.com/schemas/person.json", "$ref": "https://example.com/schemas/person.json",
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
RefTypeParser().from_properties( RefTypeParser().from_properties(
"person", "person",
properties, properties,
@@ -110,7 +113,7 @@ class TestRefTypeParser(TestCase):
}, },
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
ObjectTypeParser().from_properties( ObjectTypeParser().from_properties(
"person", "person",
properties, properties,
@@ -126,7 +129,7 @@ class TestRefTypeParser(TestCase):
"$defs": {}, "$defs": {},
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
RefTypeParser().from_properties( RefTypeParser().from_properties(
"person", "person",
properties, properties,
@@ -142,7 +145,7 @@ class TestRefTypeParser(TestCase):
"$defs": {"person": None}, "$defs": {"person": None},
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
RefTypeParser().from_properties( RefTypeParser().from_properties(
"person", "person",
properties, properties,
@@ -232,7 +235,7 @@ class TestRefTypeParser(TestCase):
"required": ["name", "age"], "required": ["name", "age"],
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
ObjectTypeParser().from_properties( ObjectTypeParser().from_properties(
"person", "person",
properties, properties,
@@ -264,7 +267,7 @@ class TestRefTypeParser(TestCase):
) )
# checks if when created via FowardRef the model is validated correctly. # checks if when created via FowardRef the model is validated correctly.
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
model( model(
name="John", name="John",
age=30, age=30,
@@ -421,7 +424,7 @@ class TestRefTypeParser(TestCase):
}, },
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
ref_strategy, ref_name, ref_property = RefTypeParser()._parse_from_strategy( ref_strategy, ref_name, ref_property = RefTypeParser()._parse_from_strategy(
"invalid_strategy", "invalid_strategy",
"person", "person",

View File

@@ -1,3 +1,4 @@
from jambo.exceptions import InvalidSchemaException
from jambo.parser import StringTypeParser from jambo.parser import StringTypeParser
from pydantic import AnyUrl, EmailStr from pydantic import AnyUrl, EmailStr
@@ -62,7 +63,7 @@ class TestStringTypeParser(TestCase):
"minLength": 5, "minLength": 5,
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
parser.from_properties("placeholder", properties) parser.from_properties("placeholder", properties)
def test_string_parser_with_default_invalid_maxlength(self): def test_string_parser_with_default_invalid_maxlength(self):
@@ -75,7 +76,7 @@ class TestStringTypeParser(TestCase):
"minLength": 1, "minLength": 1,
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
parser.from_properties("placeholder", properties) parser.from_properties("placeholder", properties)
def test_string_parser_with_default_invalid_minlength(self): def test_string_parser_with_default_invalid_minlength(self):
@@ -88,7 +89,7 @@ class TestStringTypeParser(TestCase):
"minLength": 2, "minLength": 2,
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
parser.from_properties("placeholder", properties) parser.from_properties("placeholder", properties)
def test_string_parser_with_email_format(self): def test_string_parser_with_email_format(self):
@@ -183,7 +184,7 @@ class TestStringTypeParser(TestCase):
"format": "unsupported-format", "format": "unsupported-format",
} }
with self.assertRaises(ValueError) as context: with self.assertRaises(InvalidSchemaException) as context:
parser.from_properties("placeholder", properties) parser.from_properties("placeholder", properties)
self.assertEqual( self.assertEqual(

View File

@@ -1,3 +1,4 @@
from jambo.exceptions import InvalidSchemaException
from jambo.parser import StringTypeParser from jambo.parser import StringTypeParser
from jambo.parser._type_parser import GenericTypeParser from jambo.parser._type_parser import GenericTypeParser
@@ -17,5 +18,5 @@ class TestGenericTypeParser(TestCase):
StringTypeParser.json_schema_type = "type:string" StringTypeParser.json_schema_type = "type:string"
def test_get_impl_invalid_type(self): def test_get_impl_invalid_type(self):
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
GenericTypeParser._get_impl({"type": "invalid_type"}) GenericTypeParser._get_impl({"type": "invalid_type"})

View File

@@ -1,6 +1,7 @@
from jambo import SchemaConverter from jambo import SchemaConverter
from jambo.exceptions import InvalidSchemaException, UnsupportedSchemaException
from pydantic import AnyUrl, BaseModel from pydantic import AnyUrl, BaseModel, ValidationError
from ipaddress import IPv4Address, IPv6Address from ipaddress import IPv4Address, IPv6Address
from unittest import TestCase from unittest import TestCase
@@ -23,7 +24,7 @@ class TestSchemaConverter(TestCase):
}, },
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
SchemaConverter.build(schema) SchemaConverter.build(schema)
def test_invalid_schema_type(self): def test_invalid_schema_type(self):
@@ -37,7 +38,7 @@ class TestSchemaConverter(TestCase):
}, },
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
SchemaConverter.build(schema) SchemaConverter.build(schema)
def test_build_expects_title(self): def test_build_expects_title(self):
@@ -50,7 +51,7 @@ class TestSchemaConverter(TestCase):
}, },
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
SchemaConverter.build(schema) SchemaConverter.build(schema)
def test_build_expects_object(self): def test_build_expects_object(self):
@@ -60,7 +61,7 @@ class TestSchemaConverter(TestCase):
"type": "string", "type": "string",
} }
with self.assertRaises(ValueError): with self.assertRaises(UnsupportedSchemaException):
SchemaConverter.build(schema) SchemaConverter.build(schema)
def test_is_invalid_field(self): def test_is_invalid_field(self):
@@ -76,7 +77,7 @@ class TestSchemaConverter(TestCase):
# 'required': ['name', 'age', 'is_active', 'friends', 'address'], # 'required': ['name', 'age', 'is_active', 'friends', 'address'],
} }
with self.assertRaises(ValueError) as context: with self.assertRaises(InvalidSchemaException) as context:
SchemaConverter.build(schema) SchemaConverter.build(schema)
self.assertTrue("Unknown type" in str(context.exception)) self.assertTrue("Unknown type" in str(context.exception))
@@ -117,16 +118,16 @@ class TestSchemaConverter(TestCase):
self.assertEqual(model(name="John", age=30).name, "John") self.assertEqual(model(name="John", age=30).name, "John")
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
model(name=123, age=30, email="teste@hideyoshi.com") model(name=123, age=30, email="teste@hideyoshi.com")
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
model(name="John Invalid", age=45, email="teste@hideyoshi.com") model(name="John Invalid", age=45, email="teste@hideyoshi.com")
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
model(name="", age=45, email="teste@hideyoshi.com") model(name="", age=45, email="teste@hideyoshi.com")
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
model(name="John", age=45, email="hideyoshi.com") model(name="John", age=45, email="hideyoshi.com")
def test_validation_integer(self): def test_validation_integer(self):
@@ -148,10 +149,10 @@ class TestSchemaConverter(TestCase):
self.assertEqual(model(age=30).age, 30) self.assertEqual(model(age=30).age, 30)
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
model(age=-1) model(age=-1)
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
model(age=121) model(age=121)
def test_validation_float(self): def test_validation_float(self):
@@ -173,10 +174,10 @@ class TestSchemaConverter(TestCase):
self.assertEqual(model(age=30).age, 30.0) self.assertEqual(model(age=30).age, 30.0)
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
model(age=-1.0) model(age=-1.0)
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
model(age=121.0) model(age=121.0)
def test_validation_boolean(self): def test_validation_boolean(self):
@@ -219,10 +220,10 @@ class TestSchemaConverter(TestCase):
model(friends=["John", "Jane", "John"]).friends, {"John", "Jane"} model(friends=["John", "Jane", "John"]).friends, {"John", "Jane"}
) )
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
model(friends=[]) model(friends=[])
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
model(friends=["John", "Jane", "Invalid"]) model(friends=["John", "Jane", "Invalid"])
def test_validation_list_with_missing_items(self): def test_validation_list_with_missing_items(self):
@@ -262,7 +263,7 @@ class TestSchemaConverter(TestCase):
} }
) )
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
model() model()
def test_validation_object(self): def test_validation_object(self):
@@ -290,7 +291,7 @@ class TestSchemaConverter(TestCase):
self.assertEqual(obj.address.street, "123 Main St") self.assertEqual(obj.address.street, "123 Main St")
self.assertEqual(obj.address.city, "Springfield") self.assertEqual(obj.address.city, "Springfield")
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
model() model()
def test_default_for_string(self): def test_default_for_string(self):
@@ -329,7 +330,7 @@ class TestSchemaConverter(TestCase):
"required": ["name"], "required": ["name"],
} }
with self.assertRaises(ValueError): with self.assertRaises(InvalidSchemaException):
SchemaConverter.build(schema_max_length) SchemaConverter.build(schema_max_length)
def test_default_for_list(self): def test_default_for_list(self):
@@ -421,10 +422,10 @@ class TestSchemaConverter(TestCase):
self.assertEqual(obj.name, "J") self.assertEqual(obj.name, "J")
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
Model(name="John Invalid") Model(name="John Invalid")
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
Model(name="") Model(name="")
def test_any_of(self): def test_any_of(self):
@@ -450,13 +451,13 @@ class TestSchemaConverter(TestCase):
obj = Model(id="12345678901") obj = Model(id="12345678901")
self.assertEqual(obj.id, "12345678901") self.assertEqual(obj.id, "12345678901")
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
Model(id="") Model(id="")
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
Model(id="12345678901234567890") Model(id="12345678901234567890")
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
Model(id=11) Model(id=11)
def test_string_format_email(self): def test_string_format_email(self):
@@ -465,9 +466,11 @@ class TestSchemaConverter(TestCase):
"type": "object", "type": "object",
"properties": {"email": {"type": "string", "format": "email"}}, "properties": {"email": {"type": "string", "format": "email"}},
} }
model = SchemaConverter.build(schema) model = SchemaConverter.build(schema)
self.assertEqual(model(email="test@example.com").email, "test@example.com") self.assertEqual(model(email="test@example.com").email, "test@example.com")
with self.assertRaises(ValueError):
with self.assertRaises(ValidationError):
model(email="invalid-email") model(email="invalid-email")
def test_string_format_uri(self): def test_string_format_uri(self):
@@ -476,11 +479,13 @@ class TestSchemaConverter(TestCase):
"type": "object", "type": "object",
"properties": {"website": {"type": "string", "format": "uri"}}, "properties": {"website": {"type": "string", "format": "uri"}},
} }
model = SchemaConverter.build(schema) model = SchemaConverter.build(schema)
self.assertEqual( self.assertEqual(
model(website="https://example.com").website, AnyUrl("https://example.com") model(website="https://example.com").website, AnyUrl("https://example.com")
) )
with self.assertRaises(ValueError):
with self.assertRaises(ValidationError):
model(website="invalid-uri") model(website="invalid-uri")
def test_string_format_ipv4(self): def test_string_format_ipv4(self):
@@ -489,9 +494,11 @@ class TestSchemaConverter(TestCase):
"type": "object", "type": "object",
"properties": {"ip": {"type": "string", "format": "ipv4"}}, "properties": {"ip": {"type": "string", "format": "ipv4"}},
} }
model = SchemaConverter.build(schema) model = SchemaConverter.build(schema)
self.assertEqual(model(ip="192.168.1.1").ip, IPv4Address("192.168.1.1")) self.assertEqual(model(ip="192.168.1.1").ip, IPv4Address("192.168.1.1"))
with self.assertRaises(ValueError):
with self.assertRaises(ValidationError):
model(ip="256.256.256.256") model(ip="256.256.256.256")
def test_string_format_ipv6(self): def test_string_format_ipv6(self):
@@ -500,12 +507,14 @@ class TestSchemaConverter(TestCase):
"type": "object", "type": "object",
"properties": {"ip": {"type": "string", "format": "ipv6"}}, "properties": {"ip": {"type": "string", "format": "ipv6"}},
} }
model = SchemaConverter.build(schema) model = SchemaConverter.build(schema)
self.assertEqual( self.assertEqual(
model(ip="2001:0db8:85a3:0000:0000:8a2e:0370:7334").ip, model(ip="2001:0db8:85a3:0000:0000:8a2e:0370:7334").ip,
IPv6Address("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), IPv6Address("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
) )
with self.assertRaises(ValueError):
with self.assertRaises(ValidationError):
model(ip="invalid-ipv6") model(ip="invalid-ipv6")
def test_string_format_uuid(self): def test_string_format_uuid(self):
@@ -514,6 +523,7 @@ class TestSchemaConverter(TestCase):
"type": "object", "type": "object",
"properties": {"id": {"type": "string", "format": "uuid"}}, "properties": {"id": {"type": "string", "format": "uuid"}},
} }
model = SchemaConverter.build(schema) model = SchemaConverter.build(schema)
self.assertEqual( self.assertEqual(
@@ -521,7 +531,7 @@ class TestSchemaConverter(TestCase):
UUID("123e4567-e89b-12d3-a456-426614174000"), UUID("123e4567-e89b-12d3-a456-426614174000"),
) )
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
model(id="invalid-uuid") model(id="invalid-uuid")
def test_string_format_hostname(self): def test_string_format_hostname(self):
@@ -530,9 +540,11 @@ class TestSchemaConverter(TestCase):
"type": "object", "type": "object",
"properties": {"hostname": {"type": "string", "format": "hostname"}}, "properties": {"hostname": {"type": "string", "format": "hostname"}},
} }
model = SchemaConverter.build(schema) model = SchemaConverter.build(schema)
self.assertEqual(model(hostname="example.com").hostname, "example.com") self.assertEqual(model(hostname="example.com").hostname, "example.com")
with self.assertRaises(ValueError):
with self.assertRaises(ValidationError):
model(hostname="invalid..hostname") model(hostname="invalid..hostname")
def test_string_format_datetime(self): def test_string_format_datetime(self):
@@ -541,12 +553,14 @@ class TestSchemaConverter(TestCase):
"type": "object", "type": "object",
"properties": {"timestamp": {"type": "string", "format": "date-time"}}, "properties": {"timestamp": {"type": "string", "format": "date-time"}},
} }
model = SchemaConverter.build(schema) model = SchemaConverter.build(schema)
self.assertEqual( self.assertEqual(
model(timestamp="2024-01-01T12:00:00Z").timestamp.isoformat(), model(timestamp="2024-01-01T12:00:00Z").timestamp.isoformat(),
"2024-01-01T12:00:00+00:00", "2024-01-01T12:00:00+00:00",
) )
with self.assertRaises(ValueError):
with self.assertRaises(ValidationError):
model(timestamp="invalid-datetime") model(timestamp="invalid-datetime")
def test_string_format_time(self): def test_string_format_time(self):
@@ -555,11 +569,13 @@ class TestSchemaConverter(TestCase):
"type": "object", "type": "object",
"properties": {"time": {"type": "string", "format": "time"}}, "properties": {"time": {"type": "string", "format": "time"}},
} }
model = SchemaConverter.build(schema) model = SchemaConverter.build(schema)
self.assertEqual( self.assertEqual(
model(time="20:20:39+00:00").time.isoformat(), "20:20:39+00:00" model(time="20:20:39+00:00").time.isoformat(), "20:20:39+00:00"
) )
with self.assertRaises(ValueError):
with self.assertRaises(ValidationError):
model(time="25:00:00") model(time="25:00:00")
def test_string_format_unsupported(self): def test_string_format_unsupported(self):
@@ -568,7 +584,8 @@ class TestSchemaConverter(TestCase):
"type": "object", "type": "object",
"properties": {"field": {"type": "string", "format": "unsupported"}}, "properties": {"field": {"type": "string", "format": "unsupported"}},
} }
with self.assertRaises(ValueError):
with self.assertRaises(InvalidSchemaException):
SchemaConverter.build(schema) SchemaConverter.build(schema)
def test_ref_with_root_ref(self): def test_ref_with_root_ref(self):
@@ -726,10 +743,10 @@ class TestSchemaConverter(TestCase):
obj = Model() obj = Model()
self.assertEqual(obj.name, "United States of America") self.assertEqual(obj.name, "United States of America")
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
obj.name = "Canada" obj.name = "Canada"
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
Model(name="Canada") Model(name="Canada")
def test_const_type_parser_with_non_hashable_value(self): def test_const_type_parser_with_non_hashable_value(self):
@@ -749,10 +766,10 @@ class TestSchemaConverter(TestCase):
obj = Model() obj = Model()
self.assertEqual(obj.name, ["Brazil"]) self.assertEqual(obj.name, ["Brazil"])
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
obj.name = ["Argentina"] obj.name = ["Argentina"]
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
Model(name=["Argentina"]) Model(name=["Argentina"])
def test_null_type_parser(self): def test_null_type_parser(self):
@@ -772,5 +789,5 @@ class TestSchemaConverter(TestCase):
obj = Model(a_thing=None) obj = Model(a_thing=None)
self.assertIsNone(obj.a_thing) self.assertIsNone(obj.a_thing)
with self.assertRaises(ValueError): with self.assertRaises(ValidationError):
Model(a_thing="not none") Model(a_thing="not none")