]> ToastFreeware Gitweb - philipp/winterrodeln/wrpylib.git/commitdiff
Write function to resolve references ("$ref").
authorPhilipp Spitzer <philipp@spitzer.priv.at>
Fri, 29 Oct 2021 09:33:37 +0000 (11:33 +0200)
committerPhilipp Spitzer <philipp@spitzer.priv.at>
Fri, 29 Oct 2021 09:50:10 +0000 (11:50 +0200)
tests/test_json_validate.py
wrpylib/json_tools.py

index e8e8766357a7d4c0f9bd578ff322e40b56341417..01221e912cb9c3f4f55e56e00a91a08fb8550d69 100644 (file)
@@ -1,10 +1,10 @@
 import unittest
 from copy import deepcopy
+from typing import Dict
 
-from wrpylib.json_tools import order_json_keys
+from wrpylib.json_tools import order_json_keys, _resolve_ref
 
-
-schema_object = {
+schema_object: Dict = {
     "$schema": "http://json-schema.org/draft-07/schema#",
     "type": "object",
     "required": [
@@ -54,7 +54,30 @@ schema_array = {
 }
 
 
-class TestJsonValidate(unittest.TestCase):
+class TestResolveRef(unittest.TestCase):
+    def test_no_ref(self):
+        sub_schema = schema_object['properties']['aliases']
+        actual = _resolve_ref(sub_schema, schema_object)
+        self.assertEqual(actual, sub_schema)
+
+    def test_ref(self):
+        root_schema = schema_object.copy()
+        root_schema["definitions"] = {
+            "weblink": {
+                "type": "string",
+            }
+        }
+        sub_schema = {
+            "$ref": "#/definitions/weblink"
+        }
+        expected = {
+            "type": "string",
+        }
+        actual = _resolve_ref(sub_schema, root_schema)
+        self.assertEqual(expected, actual)
+
+
+class TestOrderJsonKeys(unittest.TestCase):
     def test_string_empty(self):
         actual = order_json_keys('', {"type": "string"})
         self.assertEqual('', actual)
index fc6223f1ec0d46aac5cd356809c4d6ffb7fe61b0..d688309cc63b3c0ac04627ce63d35782e0bb1a23 100644 (file)
@@ -12,6 +12,49 @@ 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 {_fmt_path(path)} needs to be string (Python str).')
@@ -70,6 +113,8 @@ def _order_json_keys(sub_value: JsonTypes, sub_schema: JsonTypes, schema: JsonTy
         if sub_schema:
             return sub_value
         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,