From 74e98c66f0cb27c92fc3b8b1d2cab5636dde7f0c Mon Sep 17 00:00:00 2001 From: JAEJIN LEE Date: Wed, 15 May 2024 00:59:35 +0900 Subject: [PATCH] fix(export/jsonschema_converter.py): Add more keys to to_jsonschema() (#180) * fix(export/jsonschema_converter.py): to_jsonschema now supports pattern, enum, minLength, maxLength, and patternProperties * fix(tests/test_export_jsonschema): roll-back what was unintentionally deleted --- datacontract/export/jsonschema_converter.py | 15 +++- .../local-json-complex/data/sts_data.json | 15 ++++ .../local-json-complex/datacontract.yaml | 53 ++++++++++++++ tests/test_export_jsonschema.py | 73 ++++++++++++++++++- 4 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 tests/fixtures/local-json-complex/data/sts_data.json create mode 100644 tests/fixtures/local-json-complex/datacontract.yaml diff --git a/datacontract/export/jsonschema_converter.py b/datacontract/export/jsonschema_converter.py index 8299f02c..f219a150 100644 --- a/datacontract/export/jsonschema_converter.py +++ b/datacontract/export/jsonschema_converter.py @@ -46,9 +46,22 @@ def to_property(field: Field) -> dict: if field.unique: property["unique"] = True if json_type == "object": - property["properties"] = to_properties(field.fields) + # TODO: any better idea to distinguish between properties and patternProperties? + if next(iter(field.fields.keys())).startswith("^"): + property["patternProperties"] = to_properties(field.fields) + else: + property["properties"] = to_properties(field.fields) property["required"] = to_required(field.fields) + if field.pattern: + property["pattern"] = field.pattern + if field.enum: + property["enum"] = field.enum + if field.minLength: + property["minLength"] = field.minLength + if field.maxLength: + property["maxLength"] = field.maxLength + # TODO: all constraints return property diff --git a/tests/fixtures/local-json-complex/data/sts_data.json b/tests/fixtures/local-json-complex/data/sts_data.json new file mode 100644 index 00000000..14a06e25 --- /dev/null +++ b/tests/fixtures/local-json-complex/data/sts_data.json @@ -0,0 +1,15 @@ +{ + "id": "11111111", + "sts_data": { + "connection_test": "SUCCESS", + "key_list": { + "0": { + "key": "12345678" + }, + "1": { + "key": "23456789" + } + } + } + +} \ No newline at end of file diff --git a/tests/fixtures/local-json-complex/datacontract.yaml b/tests/fixtures/local-json-complex/datacontract.yaml new file mode 100644 index 00000000..25f275e5 --- /dev/null +++ b/tests/fixtures/local-json-complex/datacontract.yaml @@ -0,0 +1,53 @@ +dataContractSpecification: 0.9.3 +id: jsonschema-complex-data +info: + title: sts data + version: 1.0.0 + owner: Checkout Team +models: + sts_data: + type: table + fields: + id: + type: string + minLength: 1 + maxLength: 10 + pattern: "^[0-9]{8}$" + required: true + sts_data: + type: object + required: true + fields: + connection_test: + type: string + enum: ["SUCCESS", "FAIL", "NULL"] + required: true + key_list: + type: object + required: true + fields: + ^[0-5]$: + type: object + fields: + key: + type: string + pattern: "^[0-9]{8}$" + required: true +examples: + - type: json # csv, json, yaml, custom + model: sts_data + data: | # expressed as string or inline yaml or via "$ref: data.csv" + { + "id": "11111111", + "sts_data": { + "connection_test": "SUCCESS", + "key_list": { + "0": { + "key": "12345678" + }, + "1": { + "key": "23456789" + } + } + } + } \ No newline at end of file diff --git a/tests/test_export_jsonschema.py b/tests/test_export_jsonschema.py index 9b3f87d3..8855b5a1 100644 --- a/tests/test_export_jsonschema.py +++ b/tests/test_export_jsonschema.py @@ -48,13 +48,16 @@ def test_to_jsonschemas(): "type": "string" }, "1_Auspraegung_Code": { - "type": "string" + "type": "string", + "enum": ["DG"] }, "1_Auspraegung_Label": { - "type": "string" + "type": "string", + "enum": ["Deutschland"] }, "2_Merkmal_Code": { - "type": "string" + "type": "string", + "enum": ["MONAT"] }, "2_Merkmal_Label": { "type": "string" @@ -107,7 +110,6 @@ def test_to_jsonschemas(): """ result = to_jsonschemas(data_contract) - assert result["verbraucherpreisindex"] == json.loads(expected_json_schema) @@ -165,6 +167,69 @@ def test_to_jsonschemas_complex(): assert result["inventory"] == json.loads(expected_json_schema) +def test_to_jsonschemas_complex_2(): + data_contract_file = "fixtures/local-json-complex/datacontract.yaml" + file_content = read_file(data_contract_file=data_contract_file) + data_contract = DataContractSpecification.from_string(file_content) + expected_json_schema = """{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^[0-9]{8}$", + "minLength": 1, + "maxLength": 10 + }, + "sts_data": { + "type": "object", + "properties": { + "connection_test": { + "type": "string", + "enum": [ + "SUCCESS", + "FAIL", + "NULL" + ] + }, + "key_list": { + "type": "object", + "patternProperties": { + "^[0-5]$": { + "type": [ + "object", + "null" + ], + "properties": { + "key": { + "type": "string", + "pattern": "^[0-9]{8}$" + } + }, + "required": [ + "key" + ] + } + }, + "required": [] + } + }, + "required": [ + "connection_test", + "key_list" + ] + } + }, + "required": [ + "id", + "sts_data" + ] +} +""" + result = to_jsonschemas(data_contract) + assert result["sts_data"] == json.loads(expected_json_schema) + + def read_file(data_contract_file): if not os.path.exists(data_contract_file): print(f"The file '{data_contract_file}' does not exist.")