From 574bac0773f1fb4b2d72ba59f6a24e8dcddff4bc Mon Sep 17 00:00:00 2001 From: Patrick Wang Date: Fri, 19 Feb 2021 09:53:56 -0500 Subject: [PATCH 01/22] Add merge testing infrastructure --- tests/merge_cases/empties.json | 7 +++++++ tests/test_merging.py | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 tests/merge_cases/empties.json create mode 100644 tests/test_merging.py diff --git a/tests/merge_cases/empties.json b/tests/merge_cases/empties.json new file mode 100644 index 0000000..007f116 --- /dev/null +++ b/tests/merge_cases/empties.json @@ -0,0 +1,7 @@ +{ + "schemas": [ + {}, + {} + ], + "merged": {} +} \ No newline at end of file diff --git a/tests/test_merging.py b/tests/test_merging.py new file mode 100644 index 0000000..cd7d3da --- /dev/null +++ b/tests/test_merging.py @@ -0,0 +1,23 @@ +"""Test JSON schema merging.""" +import glob +import json +from pathlib import Path +import pytest + +from json_schema_fuzz.merging import merge + + +THIS_DIR = Path(__file__).parent +CASE_DIR = THIS_DIR / "merge_cases" +case_files = glob.glob(str(CASE_DIR / "*.json")) +cases = [] +for filename in case_files: + with open(filename, "r") as stream: + case = json.load(stream) + cases.append((case["schemas"], case["merged"])) + + +@pytest.mark.parametrize("schemas,merged", cases) +def test_merging(schemas, merged): + """Test that merging the `schemas` results in the `merged` schema.""" + assert merge(*schemas) == merged From af2f72a8e2bafbc0ce5ff28f93a0fdf7fdfe1cdb Mon Sep 17 00:00:00 2001 From: Patrick Wang Date: Fri, 19 Feb 2021 09:54:29 -0500 Subject: [PATCH 02/22] Add simple, very dumb schema merger --- json_schema_fuzz/merging.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 json_schema_fuzz/merging.py diff --git a/json_schema_fuzz/merging.py b/json_schema_fuzz/merging.py new file mode 100644 index 0000000..83e4f95 --- /dev/null +++ b/json_schema_fuzz/merging.py @@ -0,0 +1,9 @@ +"""Merging.""" + + +def merge(schema_a, schema_b): + """Merge two JSON schemas.""" + return { + **schema_a, + **schema_b, + } From d282afbaa989fbc30a5920793d3194cd4be149ef Mon Sep 17 00:00:00 2001 From: Patrick Wang Date: Fri, 19 Feb 2021 10:56:36 -0500 Subject: [PATCH 03/22] isort --- tests/test_merging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_merging.py b/tests/test_merging.py index cd7d3da..8e3ae70 100644 --- a/tests/test_merging.py +++ b/tests/test_merging.py @@ -2,11 +2,11 @@ import glob import json from pathlib import Path + import pytest from json_schema_fuzz.merging import merge - THIS_DIR = Path(__file__).parent CASE_DIR = THIS_DIR / "merge_cases" case_files = glob.glob(str(CASE_DIR / "*.json")) From cedf0c926b945ea0f531144b49e38eb3848dd872 Mon Sep 17 00:00:00 2001 From: Patrick Wang Date: Fri, 19 Feb 2021 11:31:11 -0500 Subject: [PATCH 04/22] Skip testing test files with isort It gets difficult to figure out whether json_schema_fuzz itself is a local module or a third-party module --- .github/workflows/linting.yml | 1 + .isort.cfg | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 .isort.cfg diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 7e2a884..f80fb23 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -26,4 +26,5 @@ jobs: VALIDATE_PYTHON_PYLINT: true LINTER_RULES_PATH: '.' PYTHON_FLAKE8_CONFIG_FILE: .flake8 + PYTHON_ISORT_CONFIG_FILE: .isort.cfg PYTHON_PYLINT_CONFIG_FILE: .pylintrc diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 0000000..294f7a1 --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,2 @@ +[settings] +skip=tests,venv \ No newline at end of file From fe3fb62ae900fc7e56718195d13715f060bda410 Mon Sep 17 00:00:00 2001 From: Alon Greyber Date: Fri, 19 Feb 2021 13:00:44 -0500 Subject: [PATCH 05/22] Added a more in depth recursive merging function --- json_schema_fuzz/merging.py | 41 +++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/json_schema_fuzz/merging.py b/json_schema_fuzz/merging.py index 83e4f95..c12805b 100644 --- a/json_schema_fuzz/merging.py +++ b/json_schema_fuzz/merging.py @@ -1,9 +1,38 @@ """Merging.""" +import copy -def merge(schema_a, schema_b): - """Merge two JSON schemas.""" - return { - **schema_a, - **schema_b, - } +def merge( + a: dict[any, any], + b: dict[any, any], + path: list = None, + update: bool = False +): + """ + Merge two JSON schemas recursively + + Will raise exception for conflicting values unless update = True is specified. + Based on: http://stackoverflow.com/questions/7204805/python-dictionaries-of-dictionaries-merge + """ + if path is None: + path = [] + + # Make a copy of a so that it doesn't get modified in place + a = copy.deepcopy(a) + for key in b: + if key in a: + if isinstance(a[key], dict) and isinstance(b[key], dict): + merge(a[key], b[key], path + [str(key)]) + elif a[key] == b[key]: + pass # same leaf value + elif isinstance(a[key], list) and isinstance(b[key], list): + # Append lists together + a[key].extend(b[key]) + elif update: + a[key] = b[key] + else: + raise NotImplementedError( + f"Conflicting key {key} encountered in path {path}") + else: + a[key] = b[key] + return a From 23d6cf903110ce536f9fd9ca5d3c783bb6742814 Mon Sep 17 00:00:00 2001 From: Alon Greyber Date: Fri, 19 Feb 2021 13:06:18 -0500 Subject: [PATCH 06/22] Added a test to ensure merging doesn't modify parameters --- tests/test_merging.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_merging.py b/tests/test_merging.py index 8e3ae70..82a2e33 100644 --- a/tests/test_merging.py +++ b/tests/test_merging.py @@ -21,3 +21,14 @@ def test_merging(schemas, merged): """Test that merging the `schemas` results in the `merged` schema.""" assert merge(*schemas) == merged + + +def test_merge_doesnt_modify(): + """ Test that merging doesn't modify input values """ + required_a = ["required_property"] + a = {"required": required_a} + b = {"required": ["another_required_property"]} + c = merge(a, b) + + assert len(c['required']) == 2 + assert len(required_a) == 1 From 9371afe49a77333008313b06016c6333420a14b7 Mon Sep 17 00:00:00 2001 From: Alon Greyber Date: Fri, 19 Feb 2021 13:13:07 -0500 Subject: [PATCH 07/22] Added test for merging conflicting values --- tests/test_merging.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_merging.py b/tests/test_merging.py index 82a2e33..5670bd6 100644 --- a/tests/test_merging.py +++ b/tests/test_merging.py @@ -32,3 +32,12 @@ def test_merge_doesnt_modify(): assert len(c['required']) == 2 assert len(required_a) == 1 + + +def test_merge_conflicting(): + """ Test that merging two conflicting values throws a NotImplementedError """ + a = {"multipleOf": 3} + b = {"multipleOf": 5} + + with pytest.raises(NotImplementedError): + merge(a, b) From 02fdefdd900dc33e00dc6ffd0ccdd9f773585887 Mon Sep 17 00:00:00 2001 From: Alon Greyber Date: Fri, 19 Feb 2021 13:29:40 -0500 Subject: [PATCH 08/22] Added test for conflicting with update --- tests/test_merging.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_merging.py b/tests/test_merging.py index 5670bd6..4999341 100644 --- a/tests/test_merging.py +++ b/tests/test_merging.py @@ -41,3 +41,12 @@ def test_merge_conflicting(): with pytest.raises(NotImplementedError): merge(a, b) + + +def test_merge_conflicting_with_update(): + """ Test that merging two conflicting values with update = True works """ + a = {"multipleOf": 3} + b = {"multipleOf": 5} + + c = merge(a, b, update=True) + assert c["multipleOf"] == 5 From 652243d8bbf29a2f7ed25172d47173f0818c0ac1 Mon Sep 17 00:00:00 2001 From: Alon Greyber Date: Fri, 19 Feb 2021 13:51:23 -0500 Subject: [PATCH 09/22] Removed update parameter for merge --- json_schema_fuzz/merging.py | 5 +---- tests/test_merging.py | 9 --------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/json_schema_fuzz/merging.py b/json_schema_fuzz/merging.py index c12805b..42086e6 100644 --- a/json_schema_fuzz/merging.py +++ b/json_schema_fuzz/merging.py @@ -6,12 +6,11 @@ def merge( a: dict[any, any], b: dict[any, any], path: list = None, - update: bool = False ): """ Merge two JSON schemas recursively - Will raise exception for conflicting values unless update = True is specified. + Will raise exception for conflicting values. Based on: http://stackoverflow.com/questions/7204805/python-dictionaries-of-dictionaries-merge """ if path is None: @@ -28,8 +27,6 @@ def merge( elif isinstance(a[key], list) and isinstance(b[key], list): # Append lists together a[key].extend(b[key]) - elif update: - a[key] = b[key] else: raise NotImplementedError( f"Conflicting key {key} encountered in path {path}") diff --git a/tests/test_merging.py b/tests/test_merging.py index 4999341..5670bd6 100644 --- a/tests/test_merging.py +++ b/tests/test_merging.py @@ -41,12 +41,3 @@ def test_merge_conflicting(): with pytest.raises(NotImplementedError): merge(a, b) - - -def test_merge_conflicting_with_update(): - """ Test that merging two conflicting values with update = True works """ - a = {"multipleOf": 3} - b = {"multipleOf": 5} - - c = merge(a, b, update=True) - assert c["multipleOf"] == 5 From aa893ebec7dab1aaa569c16b3edd69d76b8eaffb Mon Sep 17 00:00:00 2001 From: Alon Greyber Date: Fri, 19 Feb 2021 13:52:24 -0500 Subject: [PATCH 10/22] Restructured to reduce nesting --- json_schema_fuzz/merging.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/json_schema_fuzz/merging.py b/json_schema_fuzz/merging.py index 42086e6..ee5770e 100644 --- a/json_schema_fuzz/merging.py +++ b/json_schema_fuzz/merging.py @@ -19,17 +19,17 @@ def merge( # Make a copy of a so that it doesn't get modified in place a = copy.deepcopy(a) for key in b: - if key in a: - if isinstance(a[key], dict) and isinstance(b[key], dict): - merge(a[key], b[key], path + [str(key)]) - elif a[key] == b[key]: - pass # same leaf value - elif isinstance(a[key], list) and isinstance(b[key], list): - # Append lists together - a[key].extend(b[key]) - else: - raise NotImplementedError( - f"Conflicting key {key} encountered in path {path}") - else: + if key not in a: a[key] = b[key] + continue + if isinstance(a[key], dict) and isinstance(b[key], dict): + merge(a[key], b[key], path + [str(key)]) + elif a[key] == b[key]: + pass # same leaf value + elif isinstance(a[key], list) and isinstance(b[key], list): + # Append lists together + a[key].extend(b[key]) + else: + raise NotImplementedError( + f"Conflicting key {key} encountered in path {path}") return a From 552a16c9722265a3a508f3791f9ba208e8712029 Mon Sep 17 00:00:00 2001 From: Alon Greyber Date: Fri, 19 Feb 2021 16:23:23 -0500 Subject: [PATCH 11/22] Fixed for python 3.6 --- json_schema_fuzz/merging.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/json_schema_fuzz/merging.py b/json_schema_fuzz/merging.py index ee5770e..b8542cb 100644 --- a/json_schema_fuzz/merging.py +++ b/json_schema_fuzz/merging.py @@ -1,11 +1,12 @@ """Merging.""" import copy +from typing import Any, Dict, List def merge( - a: dict[any, any], - b: dict[any, any], - path: list = None, + a: Dict[Any, Any], + b: Dict[Any, Any], + path: List[Any] = None, ): """ Merge two JSON schemas recursively From c80cbcedb64d62835a1ce28a17189d2d1eebdc2b Mon Sep 17 00:00:00 2001 From: Patrick Wang Date: Sat, 20 Feb 2021 20:54:44 -0500 Subject: [PATCH 12/22] Rename variables to make the linter happy --- json_schema_fuzz/merging.py | 24 ++++++++++++------------ tests/test_merging.py | 14 +++++++------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/json_schema_fuzz/merging.py b/json_schema_fuzz/merging.py index b8542cb..a456b2e 100644 --- a/json_schema_fuzz/merging.py +++ b/json_schema_fuzz/merging.py @@ -4,8 +4,8 @@ def merge( - a: Dict[Any, Any], - b: Dict[Any, Any], + schema_a: Dict[Any, Any], + schema_b: Dict[Any, Any], path: List[Any] = None, ): """ @@ -18,19 +18,19 @@ def merge( path = [] # Make a copy of a so that it doesn't get modified in place - a = copy.deepcopy(a) - for key in b: - if key not in a: - a[key] = b[key] + schema_a = copy.deepcopy(schema_a) + for key in schema_b: + if key not in schema_a: + schema_a[key] = schema_b[key] continue - if isinstance(a[key], dict) and isinstance(b[key], dict): - merge(a[key], b[key], path + [str(key)]) - elif a[key] == b[key]: + if isinstance(schema_a[key], dict) and isinstance(schema_b[key], dict): + merge(schema_a[key], schema_b[key], path + [str(key)]) + elif schema_a[key] == schema_b[key]: pass # same leaf value - elif isinstance(a[key], list) and isinstance(b[key], list): + elif isinstance(schema_a[key], list) and isinstance(schema_b[key], list): # Append lists together - a[key].extend(b[key]) + schema_a[key].extend(schema_b[key]) else: raise NotImplementedError( f"Conflicting key {key} encountered in path {path}") - return a + return schema_a diff --git a/tests/test_merging.py b/tests/test_merging.py index 5670bd6..2cfb167 100644 --- a/tests/test_merging.py +++ b/tests/test_merging.py @@ -26,18 +26,18 @@ def test_merging(schemas, merged): def test_merge_doesnt_modify(): """ Test that merging doesn't modify input values """ required_a = ["required_property"] - a = {"required": required_a} - b = {"required": ["another_required_property"]} - c = merge(a, b) + schema_a = {"required": required_a} + schema_b = {"required": ["another_required_property"]} + merged = merge(schema_a, schema_b) - assert len(c['required']) == 2 + assert len(merged['required']) == 2 assert len(required_a) == 1 def test_merge_conflicting(): """ Test that merging two conflicting values throws a NotImplementedError """ - a = {"multipleOf": 3} - b = {"multipleOf": 5} + schema_a = {"multipleOf": 3} + schema_b = {"multipleOf": 5} with pytest.raises(NotImplementedError): - merge(a, b) + merge(schema_a, schema_b) From 118277b32f40c45c4d681bbc9166bc857d696d28 Mon Sep 17 00:00:00 2001 From: Patrick Wang Date: Sat, 20 Feb 2021 21:00:24 -0500 Subject: [PATCH 13/22] Shorten too-long lines and standardize docstrings --- json_schema_fuzz/merging.py | 12 +++++------- tests/test_merging.py | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/json_schema_fuzz/merging.py b/json_schema_fuzz/merging.py index a456b2e..e41960b 100644 --- a/json_schema_fuzz/merging.py +++ b/json_schema_fuzz/merging.py @@ -8,12 +8,7 @@ def merge( schema_b: Dict[Any, Any], path: List[Any] = None, ): - """ - Merge two JSON schemas recursively - - Will raise exception for conflicting values. - Based on: http://stackoverflow.com/questions/7204805/python-dictionaries-of-dictionaries-merge - """ + """Merge two JSON schemas recursively.""" if path is None: path = [] @@ -27,7 +22,10 @@ def merge( merge(schema_a[key], schema_b[key], path + [str(key)]) elif schema_a[key] == schema_b[key]: pass # same leaf value - elif isinstance(schema_a[key], list) and isinstance(schema_b[key], list): + elif ( + isinstance(schema_a[key], list) + and isinstance(schema_b[key], list) + ): # Append lists together schema_a[key].extend(schema_b[key]) else: diff --git a/tests/test_merging.py b/tests/test_merging.py index 2cfb167..6d100a5 100644 --- a/tests/test_merging.py +++ b/tests/test_merging.py @@ -35,7 +35,7 @@ def test_merge_doesnt_modify(): def test_merge_conflicting(): - """ Test that merging two conflicting values throws a NotImplementedError """ + """Test that merging conflicting values throws a NotImplementedError.""" schema_a = {"multipleOf": 3} schema_b = {"multipleOf": 5} From eb9fb49417133ebfb10a53a9d4177ab0bc78fb16 Mon Sep 17 00:00:00 2001 From: Alon Greyber Date: Mon, 22 Feb 2021 09:35:23 -0500 Subject: [PATCH 14/22] Fixed issue with recursive merging and deepcopy --- json_schema_fuzz/merging.py | 6 ++++-- tests/test_merging.py | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/json_schema_fuzz/merging.py b/json_schema_fuzz/merging.py index e41960b..baa5a0c 100644 --- a/json_schema_fuzz/merging.py +++ b/json_schema_fuzz/merging.py @@ -12,8 +12,10 @@ def merge( if path is None: path = [] - # Make a copy of a so that it doesn't get modified in place - schema_a = copy.deepcopy(schema_a) + if len(path) == 0: + # Make a copy of a so that it doesn't get modified in place + schema_a = copy.deepcopy(schema_a) + for key in schema_b: if key not in schema_a: schema_a[key] = schema_b[key] diff --git a/tests/test_merging.py b/tests/test_merging.py index 6d100a5..e88afa0 100644 --- a/tests/test_merging.py +++ b/tests/test_merging.py @@ -41,3 +41,13 @@ def test_merge_conflicting(): with pytest.raises(NotImplementedError): merge(schema_a, schema_b) + + +def test_merge_nested(): + """ Test that merging nested dictionaries works """ + schema_a = {"properties": {"a": "value"}} + schema_b = {"properties": {"b": "value"}} + merged = merge(schema_a, schema_b) + + assert 'a' in merged['properties'] + assert 'b' in merged['properties'] From e9d4fa89f25e7dc3b5487f82a1c5c60cad5c2f4d Mon Sep 17 00:00:00 2001 From: Alon Greyber Date: Mon, 22 Feb 2021 10:03:20 -0500 Subject: [PATCH 15/22] Modified merge signature to specifically switch to in place merging for recursive call --- json_schema_fuzz/merging.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/json_schema_fuzz/merging.py b/json_schema_fuzz/merging.py index baa5a0c..dc4abb2 100644 --- a/json_schema_fuzz/merging.py +++ b/json_schema_fuzz/merging.py @@ -7,12 +7,13 @@ def merge( schema_a: Dict[Any, Any], schema_b: Dict[Any, Any], path: List[Any] = None, + in_place: bool = False, ): """Merge two JSON schemas recursively.""" if path is None: path = [] - if len(path) == 0: + if not in_place: # Make a copy of a so that it doesn't get modified in place schema_a = copy.deepcopy(schema_a) @@ -21,7 +22,9 @@ def merge( schema_a[key] = schema_b[key] continue if isinstance(schema_a[key], dict) and isinstance(schema_b[key], dict): - merge(schema_a[key], schema_b[key], path + [str(key)]) + merge(schema_a[key], schema_b[key], + path=path + [str(key)], + in_place=True) elif schema_a[key] == schema_b[key]: pass # same leaf value elif ( @@ -33,4 +36,7 @@ def merge( else: raise NotImplementedError( f"Conflicting key {key} encountered in path {path}") - return schema_a + if in_place: + return None + else: + return schema_a From 4a68e0ca43a84c23439402cfd2d82ddf20fb2e9f Mon Sep 17 00:00:00 2001 From: Alon Greyber Date: Thu, 18 Feb 2021 16:57:19 -0500 Subject: [PATCH 16/22] Added allOf support to the schema --- json_schema_fuzz/__init__.py | 9 +++++++++ tests/test_generator.py | 15 +++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/json_schema_fuzz/__init__.py b/json_schema_fuzz/__init__.py index d5eb849..5ef06cf 100644 --- a/json_schema_fuzz/__init__.py +++ b/json_schema_fuzz/__init__.py @@ -4,6 +4,8 @@ import exrex +from .utils import merge + def random_integer(schema): """Generate random integer.""" @@ -12,8 +14,15 @@ def random_integer(schema): def random_object(schema): """Generate random JSON object.""" + + # Merge allOf into the base schema + all_of = schema.get("allOf", []) + for subschema in all_of: + schema = merge(schema, subschema, update=True) + properties = schema.get("properties", dict()) required = schema.get("required", []) + object = dict() for key, value in properties.items(): if key in required or random.choice([True, False]): diff --git a/tests/test_generator.py b/tests/test_generator.py index 1687f3e..8f077c0 100644 --- a/tests/test_generator.py +++ b/tests/test_generator.py @@ -28,6 +28,21 @@ def test_object(): assert isinstance(output.get("a", 0), int) +def test_allof(): + """Test a schema with an allOf property""" + schema = { + "type": "object", + "allOf": [ + {"properties": {"a": {"type": "integer"}}, "required": ["a"]}, + {"properties": {"b": {"type": "integer"}}, "required": ["b"]}, + ], + } + output = generate_json(schema) + assert isinstance(output, dict) + assert isinstance(output["a"], int) + assert isinstance(output["b"], int) + + def test_boolean(): """Test generating booleans.""" schema = { From b45a141ff71aacda23e5dc2af393fde24170e919 Mon Sep 17 00:00:00 2001 From: Alon Greyber Date: Mon, 22 Feb 2021 10:19:40 -0500 Subject: [PATCH 17/22] Moved allOf handling to generate_json method --- json_schema_fuzz/__init__.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/json_schema_fuzz/__init__.py b/json_schema_fuzz/__init__.py index 5ef06cf..6523a47 100644 --- a/json_schema_fuzz/__init__.py +++ b/json_schema_fuzz/__init__.py @@ -4,7 +4,7 @@ import exrex -from .utils import merge +from .merging import merge def random_integer(schema): @@ -15,11 +15,6 @@ def random_integer(schema): def random_object(schema): """Generate random JSON object.""" - # Merge allOf into the base schema - all_of = schema.get("allOf", []) - for subschema in all_of: - schema = merge(schema, subschema, update=True) - properties = schema.get("properties", dict()) required = schema.get("required", []) @@ -63,6 +58,12 @@ def random_array(schema): def generate_json(schema): """Generate random JSON conforming to schema.""" + + # Merge allOf subschemas into the base schema + all_of = schema.get("allOf", []) + for subschema in all_of: + schema = merge(schema, subschema) + type = schema.get("type", None) if type == "integer": return random_integer(schema) From 90c7028b662a9565eb8c9a33688dad4c55310504 Mon Sep 17 00:00:00 2001 From: Patrick Wang Date: Thu, 18 Feb 2021 16:52:25 -0500 Subject: [PATCH 18/22] Add test for anyOf --- tests/test_generator.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_generator.py b/tests/test_generator.py index 8f077c0..d304d40 100644 --- a/tests/test_generator.py +++ b/tests/test_generator.py @@ -88,3 +88,19 @@ def test_no_pattern_string(): } output = generate_json(schema) assert isinstance(output, str) + + +def test_anyof(): + """Test generating an object from an anyOf schema.""" + schema = { + "anyOf": [ + { + "type": "string", + }, + { + "type": "integer", + }, + ], + } + output = generate_json(schema) + assert isinstance(output, (str, int)) From 0f75400e20115b7e43d4f6dc0c54d27305e98f2a Mon Sep 17 00:00:00 2001 From: Patrick Wang Date: Thu, 18 Feb 2021 16:52:43 -0500 Subject: [PATCH 19/22] Add simple support for anyOf --- json_schema_fuzz/__init__.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/json_schema_fuzz/__init__.py b/json_schema_fuzz/__init__.py index 6523a47..ed35693 100644 --- a/json_schema_fuzz/__init__.py +++ b/json_schema_fuzz/__init__.py @@ -65,15 +65,19 @@ def generate_json(schema): schema = merge(schema, subschema) type = schema.get("type", None) - if type == "integer": - return random_integer(schema) - elif type == "object": - return random_object(schema) - elif type == "boolean": - return random_boolean(schema) - elif type == "string": - return random_string(schema) - elif type == "array": - return random_array(schema) - else: - raise NotImplementedError() + anyof = schema.get("anyOf", None) + if type is not None: + if type == "integer": + return random_integer(schema) + elif type == "object": + return random_object(schema) + elif type == "boolean": + return random_boolean(schema) + elif type == "string": + return random_string(schema) + elif type == "array": + return random_array(schema) + else: + raise NotImplementedError() + elif anyof is not None: + return generate_json(random.choice(anyof)) From 52b1011325678578000f5007a032e88fe16e7ae9 Mon Sep 17 00:00:00 2001 From: Patrick Wang Date: Thu, 18 Feb 2021 17:00:04 -0500 Subject: [PATCH 20/22] Catch another not-implemented area --- json_schema_fuzz/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/json_schema_fuzz/__init__.py b/json_schema_fuzz/__init__.py index ed35693..159e208 100644 --- a/json_schema_fuzz/__init__.py +++ b/json_schema_fuzz/__init__.py @@ -81,3 +81,5 @@ def generate_json(schema): raise NotImplementedError() elif anyof is not None: return generate_json(random.choice(anyof)) + else: + raise NotImplementedError() From dafc03705bedefa99923e2c56578390bb80cd92c Mon Sep 17 00:00:00 2001 From: Alon Greyber Date: Mon, 22 Feb 2021 10:49:58 -0500 Subject: [PATCH 21/22] Added sampling of anyOf schemas --- json_schema_fuzz/__init__.py | 44 ++++++++++++++++++++++-------------- json_schema_fuzz/merging.py | 8 +++++++ 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/json_schema_fuzz/__init__.py b/json_schema_fuzz/__init__.py index 159e208..65246bd 100644 --- a/json_schema_fuzz/__init__.py +++ b/json_schema_fuzz/__init__.py @@ -4,7 +4,7 @@ import exrex -from .merging import merge +from .merging import merge, merge_list def random_integer(schema): @@ -64,22 +64,32 @@ def generate_json(schema): for subschema in all_of: schema = merge(schema, subschema) + any_of = schema.get("anyOf", None) + if any_of: + found = False + while not found: + # Pick a random sample of the any_of sublist + # and determine whether it is valid + chosen_anyof = random.sample( + any_of, random.randint(1, len(any_of))) + try: + combined_anyof_schema = merge_list(chosen_anyof) + found = True + except NotImplementedError: + pass + schema = merge(schema, combined_anyof_schema) + type = schema.get("type", None) - anyof = schema.get("anyOf", None) - if type is not None: - if type == "integer": - return random_integer(schema) - elif type == "object": - return random_object(schema) - elif type == "boolean": - return random_boolean(schema) - elif type == "string": - return random_string(schema) - elif type == "array": - return random_array(schema) - else: - raise NotImplementedError() - elif anyof is not None: - return generate_json(random.choice(anyof)) + + if type == "integer": + return random_integer(schema) + elif type == "object": + return random_object(schema) + elif type == "boolean": + return random_boolean(schema) + elif type == "string": + return random_string(schema) + elif type == "array": + return random_array(schema) else: raise NotImplementedError() diff --git a/json_schema_fuzz/merging.py b/json_schema_fuzz/merging.py index dc4abb2..5a3410a 100644 --- a/json_schema_fuzz/merging.py +++ b/json_schema_fuzz/merging.py @@ -40,3 +40,11 @@ def merge( return None else: return schema_a + + +def merge_list(schemas: List[Dict]) -> Dict: + """ Merge a list of JSON schemas together """ + combined_schema = {} + for schema in schemas: + merge(combined_schema, schema) + return combined_schema From 2d0c69fb3ff0abe07c3bf392f051e3895185fd38 Mon Sep 17 00:00:00 2001 From: Alon Greyber Date: Mon, 22 Feb 2021 10:55:38 -0500 Subject: [PATCH 22/22] Fixed bug with in place merge_list --- json_schema_fuzz/merging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/json_schema_fuzz/merging.py b/json_schema_fuzz/merging.py index 5a3410a..13b3ee4 100644 --- a/json_schema_fuzz/merging.py +++ b/json_schema_fuzz/merging.py @@ -46,5 +46,5 @@ def merge_list(schemas: List[Dict]) -> Dict: """ Merge a list of JSON schemas together """ combined_schema = {} for schema in schemas: - merge(combined_schema, schema) + merge(combined_schema, schema, in_place=True) return combined_schema