Better Defaults Validation Implementation

This commit is contained in:
2025-04-19 15:44:27 -03:00
parent 5c3d3a39ba
commit c6a37dab74
8 changed files with 67 additions and 122 deletions

View File

@@ -50,8 +50,8 @@ class AnyOfTypeParser(GenericTypeParser):
if not required:
mapped_properties["default"] = mapped_properties.get("default")
# By defining the type as Union, we can use the Field validator to enforce
# the constraints on the union type.
# By defining the type as Union of Annotated type we can use the Field validator
# to enforce the constraints of each union type when needed.
# We use Annotated to attach the Field validators to the type.
field_types = [Annotated[t, Field(**v)] if v else t for t, v in sub_types]

View File

@@ -27,6 +27,7 @@ class ArrayTypeParser(GenericTypeParser):
}
wrapper_type = set if properties.get("uniqueItems", False) else list
field_type = wrapper_type[_item_type]
mapped_properties = mappings_properties_builder(
properties,
@@ -37,24 +38,10 @@ class ArrayTypeParser(GenericTypeParser):
default_list = properties.get("default")
if default_list is not None:
if not isinstance(default_list, list):
raise ValueError(
f"Default value must be a list, got {type(default_list).__name__}"
)
if len(default_list) > properties.get("maxItems", float("inf")):
raise ValueError(
f"Default list exceeds maxItems limit of {properties.get('maxItems')}"
)
if len(default_list) < properties.get("minItems", 0):
raise ValueError(
f"Default list is below minItems limit of {properties.get('minItems')}"
)
if not all(isinstance(item, _item_type) for item in default_list):
raise ValueError(
f"All items in the default list must be of type {_item_type.__name__}"
ArrayTypeParser.validate_default(
field_type,
mapped_properties,
default_list,
)
if wrapper_type is list:
@@ -69,4 +56,4 @@ class ArrayTypeParser(GenericTypeParser):
if "default_factory" in mapped_properties and "default" in mapped_properties:
del mapped_properties["default"]
return wrapper_type[_item_type], mapped_properties
return field_type, mapped_properties

View File

@@ -14,4 +14,11 @@ class BooleanTypeParser(GenericTypeParser):
_mappings = {
"default": "default",
}
return bool, mappings_properties_builder(properties, _mappings)
mapped_properties = mappings_properties_builder(properties, _mappings, required)
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.")
return bool, mapped_properties

View File

@@ -1,6 +1,6 @@
from jambo.parser._type_parser import GenericTypeParser
from jambo.utils.properties_builder.numeric_properties_builder import (
numeric_properties_builder,
from jambo.utils.properties_builder.mappings_properties_builder import (
mappings_properties_builder,
)
@@ -11,4 +11,18 @@ class FloatTypeParser(GenericTypeParser):
@staticmethod
def from_properties(name, properties, required=False):
return float, numeric_properties_builder(properties, required)
_mappings = {
"minimum": "ge",
"exclusiveMinimum": "gt",
"maximum": "le",
"exclusiveMaximum": "lt",
"multipleOf": "multiple_of",
"default": "default",
}
mapped_properties = mappings_properties_builder(properties, _mappings, required)
default_value = mapped_properties.get("default")
if default_value is not None:
FloatTypeParser.validate_default(float, mapped_properties, default_value)
return float, mapped_properties

View File

@@ -1,6 +1,6 @@
from jambo.parser._type_parser import GenericTypeParser
from jambo.utils.properties_builder.numeric_properties_builder import (
numeric_properties_builder,
from jambo.utils.properties_builder.mappings_properties_builder import (
mappings_properties_builder,
)
@@ -11,4 +11,18 @@ class IntTypeParser(GenericTypeParser):
@staticmethod
def from_properties(name, properties, required=False):
return int, numeric_properties_builder(properties, required)
_mappings = {
"minimum": "ge",
"exclusiveMinimum": "gt",
"maximum": "le",
"exclusiveMaximum": "lt",
"multipleOf": "multiple_of",
"default": "default",
}
mapped_properties = mappings_properties_builder(properties, _mappings, required)
default_value = mapped_properties.get("default")
if default_value is not None:
IntTypeParser.validate_default(int, mapped_properties, default_value)
return int, mapped_properties

View File

@@ -66,38 +66,25 @@ class TestArrayTypeParser(TestCase):
properties = {"items": {"type": "string"}, "default": ["a", 1, "c"]}
with self.assertRaises(ValueError) as context:
with self.assertRaises(ValueError):
parser.from_properties("placeholder", properties)
self.assertEqual(
str(context.exception),
"All items in the default list must be of type str",
)
def test_array_parser_with_invalid_default_type(self):
parser = ArrayTypeParser()
properties = {"items": {"type": "string"}, "default": "not_a_list"}
with self.assertRaises(ValueError) as context:
with self.assertRaises(ValueError):
parser.from_properties("placeholder", properties)
self.assertEqual(
str(context.exception), "Default value must be a list, got str"
)
def test_array_parser_with_invalid_default_min(self):
parser = ArrayTypeParser()
properties = {"items": {"type": "string"}, "default": ["a"], "minItems": 2}
with self.assertRaises(ValueError) as context:
with self.assertRaises(ValueError):
parser.from_properties("placeholder", properties)
self.assertEqual(
str(context.exception), "Default list is below minItems limit of 2"
)
def test_array_parser_with_invalid_default_max(self):
parser = ArrayTypeParser()
@@ -107,9 +94,5 @@ class TestArrayTypeParser(TestCase):
"maxItems": 3,
}
with self.assertRaises(ValueError) as context:
with self.assertRaises(ValueError):
parser.from_properties("placeholder", properties)
self.assertEqual(
str(context.exception), "Default list exceeds maxItems limit of 3"
)

View File

@@ -61,14 +61,9 @@ class TestFloatTypeParser(TestCase):
"multipleOf": 0.5,
}
with self.assertRaises(ValueError) as context:
with self.assertRaises(ValueError):
parser.from_properties("placeholder", properties)
self.assertEqual(
str(context.exception),
"Default value must be a number, got str",
)
def test_float_parser_with_default_invalid_maximum(self):
parser = FloatTypeParser()
@@ -80,14 +75,9 @@ class TestFloatTypeParser(TestCase):
"multipleOf": 0.5,
}
with self.assertRaises(ValueError) as context:
with self.assertRaises(ValueError):
parser.from_properties("placeholder", properties)
self.assertEqual(
str(context.exception),
"Default value exceeds maximum limit of 10.5",
)
def test_float_parser_with_default_invalid_minimum(self):
parser = FloatTypeParser()
@@ -99,14 +89,9 @@ class TestFloatTypeParser(TestCase):
"multipleOf": 0.5,
}
with self.assertRaises(ValueError) as context:
with self.assertRaises(ValueError):
parser.from_properties("placeholder", properties)
self.assertEqual(
str(context.exception),
"Default value is below minimum limit of 1.0",
)
def test_float_parser_with_default_invalid_exclusive_maximum(self):
parser = FloatTypeParser()
@@ -118,14 +103,9 @@ class TestFloatTypeParser(TestCase):
"multipleOf": 0.5,
}
with self.assertRaises(ValueError) as context:
with self.assertRaises(ValueError):
parser.from_properties("placeholder", properties)
self.assertEqual(
str(context.exception),
"Default value exceeds exclusive maximum limit of 10.5",
)
def test_float_parser_with_default_invalid_exclusive_minimum(self):
parser = FloatTypeParser()
@@ -137,14 +117,9 @@ class TestFloatTypeParser(TestCase):
"multipleOf": 0.5,
}
with self.assertRaises(ValueError) as context:
with self.assertRaises(ValueError):
parser.from_properties("placeholder", properties)
self.assertEqual(
str(context.exception),
"Default value is below exclusive minimum limit of 1.0",
)
def test_float_parser_with_default_invalid_multiple(self):
parser = FloatTypeParser()
@@ -156,10 +131,5 @@ class TestFloatTypeParser(TestCase):
"multipleOf": 2.0,
}
with self.assertRaises(ValueError) as context:
with self.assertRaises(ValueError):
parser.from_properties("placeholder", properties)
self.assertEqual(
str(context.exception),
"Default value 5.0 is not a multiple of 2.0",
)

View File

@@ -61,14 +61,9 @@ class TestIntTypeParser(TestCase):
"multipleOf": 2,
}
with self.assertRaises(ValueError) as context:
with self.assertRaises(ValueError):
parser.from_properties("placeholder", properties)
self.assertEqual(
str(context.exception),
"Default value must be a number, got str",
)
def test_int_parser_with_default_invalid_maximum(self):
parser = IntTypeParser()
@@ -80,14 +75,9 @@ class TestIntTypeParser(TestCase):
"multipleOf": 2,
}
with self.assertRaises(ValueError) as context:
with self.assertRaises(ValueError):
parser.from_properties("placeholder", properties)
self.assertEqual(
str(context.exception),
"Default value exceeds maximum limit of 10",
)
def test_int_parser_with_default_invalid_minimum(self):
parser = IntTypeParser()
@@ -99,14 +89,9 @@ class TestIntTypeParser(TestCase):
"multipleOf": 2,
}
with self.assertRaises(ValueError) as context:
with self.assertRaises(ValueError):
parser.from_properties("placeholder", properties)
self.assertEqual(
str(context.exception),
"Default value is below minimum limit of 1",
)
def test_int_parser_with_default_invalid_exclusive_maximum(self):
parser = IntTypeParser()
@@ -118,14 +103,9 @@ class TestIntTypeParser(TestCase):
"multipleOf": 2,
}
with self.assertRaises(ValueError) as context:
with self.assertRaises(ValueError):
parser.from_properties("placeholder", properties)
self.assertEqual(
str(context.exception),
"Default value exceeds exclusive maximum limit of 10",
)
def test_int_parser_with_default_invalid_exclusive_minimum(self):
parser = IntTypeParser()
@@ -137,14 +117,9 @@ class TestIntTypeParser(TestCase):
"multipleOf": 2,
}
with self.assertRaises(ValueError) as context:
with self.assertRaises(ValueError):
parser.from_properties("placeholder", properties)
self.assertEqual(
str(context.exception),
"Default value is below exclusive minimum limit of 1",
)
def test_int_parser_with_default_invalid_multipleOf(self):
parser = IntTypeParser()
@@ -156,10 +131,5 @@ class TestIntTypeParser(TestCase):
"multipleOf": 2,
}
with self.assertRaises(ValueError) as context:
with self.assertRaises(ValueError):
parser.from_properties("placeholder", properties)
self.assertEqual(
str(context.exception),
"Default value 5 is not a multiple of 2",
)