Skip to content

Commit

Permalink
Handle "type" being an array of strings
Browse files Browse the repository at this point in the history
  • Loading branch information
adrianisk committed Jan 29, 2024
1 parent a35571c commit c1fd345
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 2 deletions.
18 changes: 16 additions & 2 deletions recap/converters/json_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,14 @@ def to_recap(

def _parse(
self,
json_schema: dict,
json_schema: dict | str,
alias_strategy: AliasStrategy,
) -> RecapType:
extra_attrs = {}
# Check if json_schema is just a string representing a basic type, and convert
# to a dict with a "type" property if so
if isinstance(json_schema, str):
json_schema = {"type": json_schema}
if "description" in json_schema:
extra_attrs["doc"] = json_schema["description"]
if "default" in json_schema:
Expand All @@ -65,12 +69,22 @@ def _parse(
self.json_registry = resource @ self.json_registry
extra_attrs["alias"] = alias_strategy(resource_id)

# Special handling for "type" defined as a list of strings like
# {"type": ["string", "boolean"]}
if "type" in json_schema and isinstance(json_schema["type"], list):
types = [self._parse(s, alias_strategy) for s in json_schema["type"]]
return UnionType(types, **extra_attrs)

match json_schema:
case {"type": "object", "properties": properties}:
fields = []
for name, prop in properties.items():
field = self._parse(prop, alias_strategy)
if name not in json_schema.get("required", []):
# Make type nullable if it's not required, but avoid double nullable types
if (
name not in json_schema.get("required", [])
and not field.is_nullable()
):
field = field.make_nullable()
field.extra_attrs["name"] = name
fields.append(field)
Expand Down
8 changes: 8 additions & 0 deletions recap/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ def make_nullable(self) -> UnionType:
type_copy = RecapTypeClass(**attrs, **extra_attrs)
return UnionType([NullType(), type_copy], **union_attrs)

def is_nullable(self) -> bool:
"""
Returns True if the type is nullable.
:return: True if the type is nullable.
"""

return isinstance(self, UnionType) and NullType() in self.types

def validate(self) -> None:
# Default to valid type
pass
Expand Down
66 changes: 66 additions & 0 deletions tests/unit/converters/test_json_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,72 @@ def test_all_basic_types():
]


def test_nullable_types():
"""Tests nullable types (["null", "string"]), with and without default values. Also tests that nullable properties aren't
made double nullable if they're not required."""
json_schema = """
{
"type": "object",
"properties": {
"required_nullable_no_default": {"type": ["null", "string"]},
"required_nullable_with_null_default": {"type": ["null", "string"], "default": null},
"required_nullable_with__default": {"type": ["null", "string"], "default": "default_value"},
"nullable_no_default": {"type": ["null", "string"]},
"nullable_with_null_default": {"type": ["null", "string"], "default": null},
"nullable_with__default": {"type": ["null", "string"], "default": "default_value"}
},
"required": ["required_nullable_no_default", "required_nullable_with_null_default", "required_nullable_with__default"]
}
"""
Draft202012Validator.check_schema(loads(json_schema))
struct_type = JSONSchemaConverter().to_recap(json_schema)
assert isinstance(struct_type, StructType)
assert struct_type.fields == [
UnionType([NullType(), StringType()], name="required_nullable_no_default"),
UnionType(
[NullType(), StringType()],
name="required_nullable_with_null_default",
default=None,
),
UnionType(
[NullType(), StringType()],
name="required_nullable_with__default",
default="default_value",
),
UnionType([NullType(), StringType()], name="nullable_no_default"),
UnionType(
[NullType(), StringType()],
name="nullable_with_null_default",
default=None,
),
UnionType(
[NullType(), StringType()],
name="nullable_with__default",
default="default_value",
),
]


def test_union_types():
json_schema = """
{
"type": "object",
"properties": {
"union": {"type": ["null", "string", "boolean", "number"]}
},
"required": ["union"]
}
"""
Draft202012Validator.check_schema(loads(json_schema))
struct_type = JSONSchemaConverter().to_recap(json_schema)
assert isinstance(struct_type, StructType)
assert struct_type.fields == [
UnionType(
[NullType(), StringType(), BoolType(), FloatType(bits=64)], name="union"
),
]


def test_nested_objects():
json_schema = """
{
Expand Down

0 comments on commit c1fd345

Please sign in to comment.