pass
-def _order_json_keys_string(sub_value: JsonTypes, sub_schema: JsonTypes, path: List[str]) -> str:
+def _fmt_path(path: List) -> str:
+ return f'schema[{"][".join(map(repr, path))}]'
+
+
+def _resolve_ref_not_recursive(sub_schema: JsonTypes, schema: JsonTypes) -> JsonTypes:
+ """In case the sub_schema is a dict and has direct "$ref" keys,
+ it is replaced by a dict where the "$ref" key is replaced by the corresponding definition in schema.
+ Nested $ref keys are not resolved.
+ Recursive $ref keys are not resolved.
+
+ :param sub_schema: JSON sub-schema where a "$ref" key is possible replaced.
+ The value of "$ref" could be e.g. "#/definitions/position"
+ :param schema: JSON root schema containing definitions for the keys.
+ :raise ValidationError: In case a "$ref" could not be resolved.
+ """
+ if not isinstance(sub_schema, dict) or '$ref' not in sub_schema:
+ return sub_schema
+ ref = sub_schema['$ref']
+ if not isinstance(ref, str):
+ raise ValidationError(f'Type of reference {ref} is not string.')
+ path = ref.split('/')
+ if len(path) == 0 or path[0] != '#':
+ raise ValidationError(f'Unsupported reference {ref}.')
+ ref_schema = schema
+ for p in path[1:]:
+ if not isinstance(ref_schema, dict) or p not in ref_schema:
+ raise ValidationError(f'Reference path {ref} not found in schema.')
+ ref_schema = ref_schema[p]
+ if not isinstance(ref_schema, dict):
+ raise ValidationError(f'Reference path {ref} is no dict.')
+ sub_schema = sub_schema.copy()
+ del sub_schema['$ref']
+ resolved_schema = ref_schema.copy()
+ resolved_schema.update(sub_schema)
+ return resolved_schema
+
+
+def _resolve_ref(sub_schema: JsonTypes, schema: JsonTypes) -> JsonTypes:
+ """Same as `_resolve_ref_not_recursive` but recursively resolves $ref keys.
+ However, does not resolve nested $ref keys.
+ """
+ resolved_schema = sub_schema
+ while isinstance(resolved_schema, dict) and '$ref' in resolved_schema:
+ resolved_schema = _resolve_ref_not_recursive(resolved_schema, schema)
+ return resolved_schema
+
+
+def _order_json_keys_string(sub_value: JsonTypes, sub_schema: JsonTypes, schema: JsonTypes, path: List) -> str:
if not isinstance(sub_value, str):
- raise ValidationError(f'Type of {"".join(path)} needs to be string (Python str).')
+ raise ValidationError(f'Type of {_fmt_path(path)} needs to be string (Python str).')
return sub_value
-def _order_json_keys_number(sub_value: JsonTypes, sub_schema: JsonTypes, path: List[str]) -> Union[int, float]:
+def _order_json_keys_number(sub_value: JsonTypes, sub_schema: JsonTypes,
+ schema: JsonTypes, path: List) -> Union[int, float]:
if not isinstance(sub_value, (int, float)) or isinstance(sub_value, bool):
- raise ValidationError(f'Type of {"".join(path)} needs to be number (Python int or float).')
+ raise ValidationError(f'Type of {_fmt_path(path)} needs to be number (Python int or float).')
return sub_value
-def _order_json_keys_object(sub_value: JsonTypes, sub_schema: JsonTypes, path: List[str]) -> Dict:
+def _order_json_keys_object(sub_value: JsonTypes, sub_schema: JsonTypes, schema: JsonTypes, path: List) -> Dict:
if not isinstance(sub_value, dict):
- raise ValidationError(f'Type of {"".join(path)} needs to be object (Python dict).')
+ raise ValidationError(f'Type of {_fmt_path(path)} needs to be object (Python dict).')
v = sub_value.copy()
p = sub_schema.get('properties', {})
result = {}
for key in p:
if key in v:
- result[key] = _order_json_keys(v.pop(key), p[key], path + [f"['{key}']"])
+ result[key] = _order_json_keys(v.pop(key), p[key], schema, path + [key])
else:
if key in sub_schema.get('required', []):
- raise ValidationError(f'Required key "{key}" not present ({"".join(path)}).')
+ raise ValidationError(f'Required key "{key}" not present ({_fmt_path(path)}).')
if len(v) > 0:
if sub_schema.get('additionalProperties', True):
# strictly speaking additionalProperties could be more complicated than boolean
result.update(v)
else:
- raise ValidationError(f'Keys not allowed in {"".join(path)}: {", ".join(v)}')
+ raise ValidationError(f'Keys not allowed in {_fmt_path(path)}: {", ".join(v)}')
return result
-def _order_json_keys_array(sub_value: JsonTypes, sub_schema: JsonTypes, path: List[str]) -> List:
+def _order_json_keys_array(sub_value: JsonTypes, sub_schema: JsonTypes, schema: JsonTypes, path: List) -> List:
if not isinstance(sub_value, list):
- raise ValidationError(f'Type of {"".join(path)} needs to be array (Python list).')
+ raise ValidationError(f'Type of {"".join(_fmt_path(path))} needs to be array (Python list).')
s = sub_schema.get('items', True)
- return [_order_json_keys(v, s, path + [f'[{i}]']) for i, v in enumerate(sub_value)]
+ return [_order_json_keys(v, s, schema, path + [i]) for i, v in enumerate(sub_value)]
-def _order_json_keys_boolean(sub_value: JsonTypes, sub_schema: JsonTypes, path: List[str]) -> bool:
+def _order_json_keys_boolean(sub_value: JsonTypes, sub_schema: JsonTypes, schema: JsonTypes, path: List) -> bool:
if not isinstance(sub_value, bool):
- raise ValidationError(f'Type of {"".join(path)} needs to be boolean (Python bool).')
+ raise ValidationError(f'Type of {_fmt_path(path)} needs to be boolean (Python bool).')
return sub_value
-def _order_json_keys_null(sub_value: JsonTypes, sub_schema: JsonTypes, path: List[str]) -> None:
+def _order_json_keys_null(sub_value: JsonTypes, sub_schema: JsonTypes, schema: JsonTypes, path: List) -> None:
if sub_value is not None:
- raise ValidationError(f'Type of {"".join(path)} needs to be null (Python None).')
+ raise ValidationError(f'Type of {_fmt_path(path)} needs to be null (Python None).')
return sub_value
-def _order_json_keys(sub_value: JsonTypes, sub_schema: JsonTypes, path: List[str]) -> JsonTypes:
+def _order_json_keys(sub_value: JsonTypes, sub_schema: JsonTypes, schema: JsonTypes, path: List) -> JsonTypes:
if isinstance(sub_schema, bool):
if sub_schema:
return sub_value
- raise ValidationError(f'Value {sub_value} not allowed in {"".join(path)}.')
+ raise ValidationError(f'Value {sub_value} not allowed in {_fmt_path(path)}.')
+ if isinstance(sub_schema, dict):
+ sub_schema = _resolve_ref(sub_schema, schema)
return {
'string': _order_json_keys_string,
'number': _order_json_keys_number,
+ 'integer': _order_json_keys_number,
'object': _order_json_keys_object,
'array': _order_json_keys_array,
'boolean': _order_json_keys_boolean,
'null': _order_json_keys_null,
- }[sub_schema['type']](sub_value, sub_schema, path)
+ }[sub_schema['type']](sub_value, sub_schema, schema, path)
def order_json_keys(value: JsonTypes, schema: JsonTypes) -> JsonTypes:
- return _order_json_keys(value, schema, ['value'])
+ return _order_json_keys(value, schema, schema, [])