Skip to content

Commit 17969a7

Browse files
authored
Merge pull request #142 from abravalheri/display-ref-in-composite
Expand `$ref` when assigning the `definition` field to `JsonSchemaValueException`
2 parents 1aeffb5 + 64ac97e commit 17969a7

File tree

7 files changed

+161
-3
lines changed

7 files changed

+161
-3
lines changed

fastjsonschema/generator.py

+13-2
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,19 @@ def exc(self, msg, *args, rule=None):
252252
Short-cut for creating raising exception in the code.
253253
"""
254254
msg = 'raise JsonSchemaValueException("'+msg+'", value={variable}, name="{name}", definition={definition}, rule={rule})'
255-
definition_rule = self.e(self._definition.get(rule) if isinstance(self._definition, dict) else None)
256-
self.l(msg, *args, definition=repr(self._definition), rule=repr(rule), definition_rule=definition_rule)
255+
definition = self._expand_refs(self._definition)
256+
definition_rule = self.e(definition.get(rule) if isinstance(definition, dict) else None)
257+
self.l(msg, *args, definition=repr(definition), rule=repr(rule), definition_rule=definition_rule)
258+
259+
def _expand_refs(self, definition):
260+
if isinstance(definition, list):
261+
return [self._expand_refs(v) for v in definition]
262+
if not isinstance(definition, dict):
263+
return definition
264+
if "$ref" in definition and isinstance(definition["$ref"], str):
265+
with self._resolver.resolving(definition["$ref"]) as schema:
266+
return schema
267+
return {k: self._expand_refs(v) for k, v in definition.items()}
257268

258269
def create_variable_with_length(self):
259270
"""

tests/conftest.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ def f(definition, value, expected, formats={}):
3333
if isinstance(expected, JsonSchemaValueException):
3434
with pytest.raises(JsonSchemaValueException) as exc:
3535
validator(value)
36-
assert exc.value.message == expected.message
36+
if expected.message is not any:
37+
assert exc.value.message == expected.message
3738
assert exc.value.value == (value if expected.value == '{data}' else expected.value)
3839
assert exc.value.name == expected.name
3940
assert exc.value.definition == (definition if expected.definition == '{definition}' else expected.definition)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"type": "object",
3+
"properties": {
4+
"street_address": {
5+
"type": "string"
6+
},
7+
"country": {
8+
"default": "United States of America",
9+
"enum": ["United States of America", "Canada"]
10+
}
11+
},
12+
"if": {
13+
"properties": { "country": { "const": "United States of America" } }
14+
},
15+
"then": {
16+
"properties": { "postal_code": { "pattern": "[0-9]{5}(-[0-9]{4})?" } }
17+
},
18+
"else": {
19+
"properties": { "postal_code": { "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" } }
20+
}
21+
}
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
data.postal_code must match pattern [A-Z][0-9][A-Z] [0-9][A-Z][0-9]
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"street_address": "24 Sussex Drive",
3+
"country": "Canada",
4+
"postal_code": "10000"
5+
}

tests/examples/conditional/valid.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"street_address": "1600 Pennsylvania Avenue NW",
3+
"country": "United States of America",
4+
"postal_code": "20500"
5+
}

tests/test_composition.py

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import pytest
2+
3+
import fastjsonschema
4+
from fastjsonschema import JsonSchemaValueException
5+
6+
7+
def _composition_example(composition="oneOf"):
8+
return {
9+
"definitions": {
10+
"multiple-of-3": {"type": "number", "multipleOf": 3},
11+
"multiple-of-5": {"type": "number", "multipleOf": 5},
12+
},
13+
composition: [
14+
{"$ref": "#/definitions/multiple-of-3"},
15+
{"$ref": "#/definitions/multiple-of-5"},
16+
],
17+
}
18+
19+
20+
@pytest.mark.parametrize(
21+
"composition, value", [("oneOf", 10), ("allOf", 15), ("anyOf", 9)]
22+
)
23+
def test_composition(asserter, composition, value):
24+
asserter(_composition_example(composition), value, value)
25+
26+
27+
@pytest.mark.parametrize(
28+
"composition, value", [("oneOf", 2), ("anyOf", 2), ("allOf", 3)]
29+
)
30+
def test_ref_is_expanded_on_composition_error(composition, value):
31+
with pytest.raises(JsonSchemaValueException) as exc:
32+
fastjsonschema.validate(_composition_example(composition), value)
33+
34+
if composition in exc.value.definition:
35+
assert exc.value.definition[composition] == [
36+
{"type": "number", "multipleOf": 3},
37+
{"type": "number", "multipleOf": 5},
38+
]
39+
else:
40+
# allOf will fail on the first invalid item,
41+
# so the error message will not refer to the entire composition
42+
assert composition == "allOf"
43+
assert "$ref" not in exc.value.definition
44+
assert exc.value.definition["type"] == "number"
45+
46+
47+
@pytest.mark.parametrize(
48+
"composition, value", [("oneOf", 2), ("anyOf", 2), ("allOf", 3)]
49+
)
50+
def test_ref_is_expanded_with_resolver(composition, value):
51+
repo = {
52+
"sch://multiple-of-3": {"type": "number", "multipleOf": 3},
53+
"sch://multiple-of-5": {"type": "number", "multipleOf": 5},
54+
}
55+
schema = {
56+
composition: [
57+
{"$ref": "sch://multiple-of-3"},
58+
{"$ref": "sch://multiple-of-5"},
59+
]
60+
}
61+
62+
with pytest.raises(JsonSchemaValueException) as exc:
63+
fastjsonschema.validate(schema, value, handlers={"sch": repo.__getitem__})
64+
65+
if composition in exc.value.definition:
66+
assert exc.value.definition[composition] == [
67+
{"type": "number", "multipleOf": 3},
68+
{"type": "number", "multipleOf": 5},
69+
]
70+
else:
71+
# allOf will fail on the first invalid item,
72+
# so the error message will not refer to the entire composition
73+
assert composition == "allOf"
74+
assert "$ref" not in exc.value.definition
75+
assert exc.value.definition["type"] == "number"
76+
77+
78+
def test_ref_in_conditional():
79+
repo = {
80+
"sch://USA": {"properties": {"country": {"const": "United States of America"}}},
81+
"sch://USA-post-code": {
82+
"properties": {"postal_code": {"pattern": "[0-9]{5}(-[0-9]{4})?"}}
83+
},
84+
"sch://general-post-code": {
85+
"properties": {
86+
"postal_code": {"pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]"}
87+
}
88+
},
89+
}
90+
schema = {
91+
"type": "object",
92+
"properties": {
93+
"street_address": {"type": "string"},
94+
"country": {
95+
"default": "United States of America",
96+
"enum": ["United States of America", "Canada"],
97+
},
98+
},
99+
"if": {"allOf": [{"$ref": "sch://USA"}]},
100+
"then": {"oneOf": [{"$ref": "sch://USA-post-code"}]},
101+
"else": {"anyOf": [{"$ref": "sch://general-post-code"}]},
102+
}
103+
invalid = {
104+
"street_address": "1600 Pennsylvania Avenue NW",
105+
"country": "United States of America",
106+
"postal_code": "BS12 3FG",
107+
}
108+
109+
with pytest.raises(JsonSchemaValueException) as exc:
110+
fastjsonschema.validate(schema, invalid, handlers={"sch": repo.__getitem__})
111+
112+
assert exc.value.definition["oneOf"] == [
113+
{"properties": {"postal_code": {"pattern": "[0-9]{5}(-[0-9]{4})?"}}}
114+
]

0 commit comments

Comments
 (0)