From 98f306f1e621e6589efbbead8b4ae691e8652919 Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Thu, 14 Mar 2024 11:06:12 +0100 Subject: [PATCH 01/15] empty commit From 82d57fe66acb5f74244d8c2322c94d047227a7f9 Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Thu, 14 Mar 2024 11:11:34 +0100 Subject: [PATCH 02/15] Revert "update implementation to handle forward references" This reverts commit bf6415e3197a6f8a7a14554315e8eb69ba099792. From 5dbbfe6f7e3e6b23ec3ed10bcad2ceb5e02c009c Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Thu, 14 Mar 2024 11:12:16 +0100 Subject: [PATCH 03/15] fix implementation --- .../python/renderers/ClassRenderer.ts | 90 +++++++++++++++++-- .../generators/python/PythonGenerator.spec.ts | 29 ++++++ .../PythonGenerator.spec.ts.snap | 60 +++++++++++++ 3 files changed, 170 insertions(+), 9 deletions(-) diff --git a/src/generators/python/renderers/ClassRenderer.ts b/src/generators/python/renderers/ClassRenderer.ts index 326b758f03..658a20b0cf 100644 --- a/src/generators/python/renderers/ClassRenderer.ts +++ b/src/generators/python/renderers/ClassRenderer.ts @@ -1,8 +1,13 @@ import { PythonRenderer } from '../PythonRenderer'; import { + ConstrainedArrayModel, + ConstrainedDictionaryModel, + ConstrainedMetaModel, ConstrainedObjectModel, ConstrainedObjectPropertyModel, - ConstrainedReferenceModel + ConstrainedReferenceModel, + ConstrainedTupleModel, + ConstrainedUnionModel } from '../../../models'; import { PythonOptions } from '../PythonGenerator'; import { ClassPresetType } from '../PythonPreset'; @@ -72,6 +77,62 @@ ${this.indent(this.renderBlock(content, 2))} runSetterPreset(property: ConstrainedObjectPropertyModel): Promise { return this.runPreset('setter', { property }); } + + /** + * Return forward reference types (`Address` becomes `'Address'`) when it resolves around self-references that are + * either a direct dependency or part of another model such as array, union, dictionary and tuple. + * + * Otherwise just return the provided propertyType as is. + */ + returnPropertyType({ + model, + property, + propertyType + }: { + model: ConstrainedMetaModel; + property: ConstrainedMetaModel; + propertyType: string; + }): string { + const isSelfReference = + property instanceof ConstrainedReferenceModel && property.ref === model; + if (isSelfReference) { + // Use forward references for getters and setters + return propertyType.replace(property.ref.type, `'${property.ref.type}'`); + } else if (property instanceof ConstrainedArrayModel) { + return this.returnPropertyType({ + model, + property: property.valueModel, + propertyType + }); + } else if (property instanceof ConstrainedTupleModel) { + let newPropType = propertyType; + for (const tupl of property.tuple) { + newPropType = this.returnPropertyType({ + model, + property: tupl.value, + propertyType + }); + } + return newPropType; + } else if (property instanceof ConstrainedUnionModel) { + let newPropType = propertyType; + for (const unionModel of property.union) { + newPropType = this.returnPropertyType({ + model, + property: unionModel, + propertyType + }); + } + return newPropType; + } else if (property instanceof ConstrainedDictionaryModel) { + return this.returnPropertyType({ + model, + property: property.value, + propertyType + }); + } + return propertyType; + } } export const PYTHON_DEFAULT_CLASS_PRESET: ClassPresetType = { @@ -83,17 +144,18 @@ export const PYTHON_DEFAULT_CLASS_PRESET: ClassPresetType = { let body = ''; if (Object.keys(properties).length > 0) { const assignments = Object.values(properties).map((property) => { + const propertyType = property.property.type; if (property.property.options.const) { - return `self._${property.propertyName}: ${property.property.type} = ${property.property.options.const.value}`; + return `self._${property.propertyName}: ${propertyType} = ${property.property.options.const.value}`; } let assignment: string; if (property.property instanceof ConstrainedReferenceModel) { - assignment = `self._${property.propertyName}: ${property.property.type} = ${property.property.type}(input['${property.propertyName}'])`; + assignment = `self._${property.propertyName}: ${propertyType} = ${propertyType}(input['${property.propertyName}'])`; } else { - assignment = `self._${property.propertyName}: ${property.property.type} = input['${property.propertyName}']`; + assignment = `self._${property.propertyName}: ${propertyType} = input['${property.propertyName}']`; } if (!property.required) { - return `if hasattr(input, '${property.propertyName}'): + return `if '${property.propertyName}' in input: ${renderer.indent(assignment, 2)}`; } return assignment; @@ -108,20 +170,30 @@ No properties return `def __init__(self, input: Dict): ${renderer.indent(body, 2)}`; }, - getter({ property, renderer }) { + getter({ property, renderer, model }) { + const propertyType = renderer.returnPropertyType({ + model, + property: property.property, + propertyType: property.property.type + }); const propAssignment = `return self._${property.propertyName}`; return `@property -def ${property.propertyName}(self) -> ${property.property.type}: +def ${property.propertyName}(self) -> ${propertyType}: ${renderer.indent(propAssignment, 2)}`; }, - setter({ property, renderer }) { + setter({ property, renderer, model }) { // if const value exists we should not render a setter if (property.property.options.const?.value) { return ''; } + const propertyType = renderer.returnPropertyType({ + model, + property: property.property, + propertyType: property.property.type + }); const propAssignment = `self._${property.propertyName} = ${property.propertyName}`; - const propArgument = `${property.propertyName}: ${property.property.type}`; + const propArgument = `${property.propertyName}: ${propertyType}`; return `@${property.propertyName}.setter def ${property.propertyName}(self, ${propArgument}): diff --git a/test/generators/python/PythonGenerator.spec.ts b/test/generators/python/PythonGenerator.spec.ts index e9dee10f67..cd043f6f40 100644 --- a/test/generators/python/PythonGenerator.spec.ts +++ b/test/generators/python/PythonGenerator.spec.ts @@ -81,6 +81,35 @@ describe('PythonGenerator', () => { expect(models).toHaveLength(1); expect(models[0].result).toMatchSnapshot(); }); + test('should handle self reference models', async () => { + const doc = { + $id: 'Address', + type: 'object', + properties: { + test: { type: 'string' }, + self_model: { $ref: '#' }, + array_model: { type: 'array', items: { $ref: '#' } }, + tuple_model: { + type: 'array', + items: [{ $ref: '#' }], + additionalItems: false + }, + map_model: { + type: 'object', + additionalProperties: { + $ref: '#' + } + }, + union_model: { + oneOf: [{ $ref: '#' }] + } + }, + additionalProperties: false + }; + const models = await generator.generate(doc); + expect(models).toHaveLength(1); + expect(models[0].result).toMatchSnapshot(); + }); test('should render `class` type', async () => { const doc = { $id: 'Address', diff --git a/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap b/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap index be589e3ed0..7374e80511 100644 --- a/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap +++ b/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap @@ -1,5 +1,65 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`PythonGenerator Class should handle self reference models 1`] = ` +"class Address: + def __init__(self, input: Dict): + if 'test' in input: + self._test: str = input['test'] + if 'self_model' in input: + self._self_model: Address = Address(input['self_model']) + if 'array_model' in input: + self._array_model: List[Address] = input['array_model'] + if 'tuple_model' in input: + self._tuple_model: tuple[Address] = input['tuple_model'] + if 'map_model' in input: + self._map_model: dict[str, Address] = input['map_model'] + if 'union_model' in input: + self._union_model: Address = input['union_model'] + + @property + def test(self) -> str: + return self._test + @test.setter + def test(self, test: str): + self._test = test + + @property + def self_model(self) -> 'Address': + return self._self_model + @self_model.setter + def self_model(self, self_model: 'Address'): + self._self_model = self_model + + @property + def array_model(self) -> List['Address']: + return self._array_model + @array_model.setter + def array_model(self, array_model: List['Address']): + self._array_model = array_model + + @property + def tuple_model(self) -> tuple['Address']: + return self._tuple_model + @tuple_model.setter + def tuple_model(self, tuple_model: tuple['Address']): + self._tuple_model = tuple_model + + @property + def map_model(self) -> dict[str, 'Address']: + return self._map_model + @map_model.setter + def map_model(self, map_model: dict[str, 'Address']): + self._map_model = map_model + + @property + def union_model(self) -> 'Address': + return self._union_model + @union_model.setter + def union_model(self, union_model: 'Address'): + self._union_model = union_model +" +`; + exports[`PythonGenerator Class should not render reserved keyword 1`] = ` "class Address: def __init__(self, input: Dict): From ccbbb25470b7de6b5e090128981510d81d7ad1b8 Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Thu, 14 Mar 2024 11:16:09 +0100 Subject: [PATCH 04/15] update snapshot --- test/generators/python/PythonGenerator.spec.ts | 1 - .../python/__snapshots__/PythonGenerator.spec.ts.snap | 9 --------- 2 files changed, 10 deletions(-) diff --git a/test/generators/python/PythonGenerator.spec.ts b/test/generators/python/PythonGenerator.spec.ts index cd043f6f40..e183102e8b 100644 --- a/test/generators/python/PythonGenerator.spec.ts +++ b/test/generators/python/PythonGenerator.spec.ts @@ -86,7 +86,6 @@ describe('PythonGenerator', () => { $id: 'Address', type: 'object', properties: { - test: { type: 'string' }, self_model: { $ref: '#' }, array_model: { type: 'array', items: { $ref: '#' } }, tuple_model: { diff --git a/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap b/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap index 7374e80511..4dbdb9d671 100644 --- a/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap +++ b/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap @@ -3,8 +3,6 @@ exports[`PythonGenerator Class should handle self reference models 1`] = ` "class Address: def __init__(self, input: Dict): - if 'test' in input: - self._test: str = input['test'] if 'self_model' in input: self._self_model: Address = Address(input['self_model']) if 'array_model' in input: @@ -16,13 +14,6 @@ exports[`PythonGenerator Class should handle self reference models 1`] = ` if 'union_model' in input: self._union_model: Address = input['union_model'] - @property - def test(self) -> str: - return self._test - @test.setter - def test(self, test: str): - self._test = test - @property def self_model(self) -> 'Address': return self._self_model From d5b294103ccbf1e5dbcabc5ecfc4fd2c43d524d7 Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Thu, 14 Mar 2024 11:24:11 +0100 Subject: [PATCH 05/15] commit snapshots --- .../__snapshots__/index.spec.ts.snap | 16 ++++++++-------- .../__snapshots__/index.spec.ts.snap | 2 +- .../__snapshots__/index.spec.ts.snap | 4 ++-- .../__snapshots__/PythonGenerator.spec.ts.snap | 12 ++++++------ .../__snapshots__/JsonSerializer.spec.ts.snap | 4 ++-- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/examples/generate-python-complete-models/__snapshots__/index.spec.ts.snap b/examples/generate-python-complete-models/__snapshots__/index.spec.ts.snap index b65fc34153..6dc7a78ecf 100644 --- a/examples/generate-python-complete-models/__snapshots__/index.spec.ts.snap +++ b/examples/generate-python-complete-models/__snapshots__/index.spec.ts.snap @@ -6,9 +6,9 @@ Array [ from typing import Any, Dict class ObjProperty: def __init__(self, input: Dict): - if hasattr(input, 'number'): + if 'number' in input: self._number: float = input['number'] - if hasattr(input, 'additional_properties'): + if 'additional_properties' in input: self._additional_properties: dict[str, Any] = input['additional_properties'] @property @@ -34,9 +34,9 @@ Array [ from typing import Any, Dict class ObjProperty: def __init__(self, input: Dict): - if hasattr(input, 'number'): + if 'number' in input: self._number: float = input['number'] - if hasattr(input, 'additional_properties'): + if 'additional_properties' in input: self._additional_properties: dict[str, Any] = input['additional_properties'] @property @@ -62,9 +62,9 @@ Array [ from typing import Any, Dict class Root: def __init__(self, input: Dict): - if hasattr(input, 'email'): + if 'email' in input: self._email: str = input['email'] - if hasattr(input, 'obj_property'): + if 'obj_property' in input: self._obj_property: ObjProperty = ObjProperty(input['obj_property']) @property @@ -90,9 +90,9 @@ Array [ from typing import Any, Dict class Root: def __init__(self, input: Dict): - if hasattr(input, 'email'): + if 'email' in input: self._email: str = input['email'] - if hasattr(input, 'obj_property'): + if 'obj_property' in input: self._obj_property: ObjProperty = ObjProperty(input['obj_property']) @property diff --git a/examples/generate-python-models/__snapshots__/index.spec.ts.snap b/examples/generate-python-models/__snapshots__/index.spec.ts.snap index d417a91f92..bf629a654a 100644 --- a/examples/generate-python-models/__snapshots__/index.spec.ts.snap +++ b/examples/generate-python-models/__snapshots__/index.spec.ts.snap @@ -4,7 +4,7 @@ exports[`Should be able to render python models and should log expected output t Array [ "class Root: def __init__(self, input: Dict): - if hasattr(input, 'email'): + if 'email' in input: self._email: str = input['email'] @property diff --git a/examples/python-generate-json-serializer-and-deserializer/__snapshots__/index.spec.ts.snap b/examples/python-generate-json-serializer-and-deserializer/__snapshots__/index.spec.ts.snap index 30113f1a8f..a64ad85d29 100644 --- a/examples/python-generate-json-serializer-and-deserializer/__snapshots__/index.spec.ts.snap +++ b/examples/python-generate-json-serializer-and-deserializer/__snapshots__/index.spec.ts.snap @@ -4,9 +4,9 @@ exports[`Should be able to render JSON serialization and deserialization functio Array [ "class Root: def __init__(self, input: Dict): - if hasattr(input, 'email'): + if 'email' in input: self._email: str = input['email'] - if hasattr(input, 'additional_properties'): + if 'additional_properties' in input: self._additional_properties: dict[str, Any] = input['additional_properties'] @property diff --git a/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap b/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap index 4dbdb9d671..545f57d6a4 100644 --- a/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap +++ b/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap @@ -54,7 +54,7 @@ exports[`PythonGenerator Class should handle self reference models 1`] = ` exports[`PythonGenerator Class should not render reserved keyword 1`] = ` "class Address: def __init__(self, input: Dict): - if hasattr(input, 'reserved_del'): + if 'reserved_del' in input: self._reserved_del: str = input['reserved_del'] @property @@ -73,12 +73,12 @@ exports[`PythonGenerator Class should render \`class\` type 1`] = ` self._city: str = input['city'] self._state: str = input['state'] self._house_number: float = input['house_number'] - if hasattr(input, 'marriage'): + if 'marriage' in input: self._marriage: bool = input['marriage'] - if hasattr(input, 'members'): + if 'members' in input: self._members: str | float | bool = input['members'] self._array_type: List[str | float | Any] = input['array_type'] - if hasattr(input, 'additional_properties'): + if 'additional_properties' in input: self._additional_properties: dict[str, Any | str] = input['additional_properties'] @property @@ -158,9 +158,9 @@ exports[`PythonGenerator Class should work with custom preset for \`class\` type def __init__(self, input: Dict): - if hasattr(input, 'property'): + if 'property' in input: self._property: str = input['property'] - if hasattr(input, 'additional_properties'): + if 'additional_properties' in input: self._additional_properties: dict[str, Any] = input['additional_properties'] test2 diff --git a/test/generators/python/presets/__snapshots__/JsonSerializer.spec.ts.snap b/test/generators/python/presets/__snapshots__/JsonSerializer.spec.ts.snap index b337ade90b..e71260fa49 100644 --- a/test/generators/python/presets/__snapshots__/JsonSerializer.spec.ts.snap +++ b/test/generators/python/presets/__snapshots__/JsonSerializer.spec.ts.snap @@ -3,9 +3,9 @@ exports[`PYTHON_JSON_SERIALIZER_PRESET should render serializer and deserializer for class 1`] = ` "class Test: def __init__(self, input: Dict): - if hasattr(input, 'prop'): + if 'prop' in input: self._prop: str = input['prop'] - if hasattr(input, 'additional_properties'): + if 'additional_properties' in input: self._additional_properties: dict[str, Any] = input['additional_properties'] @property From 707e6d26afbde5a1fc6e7f6eed19b823b522cf7e Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Mon, 18 Mar 2024 11:03:43 +0100 Subject: [PATCH 06/15] add test --- .../python/renderers/ClassRenderer.ts | 20 ++++-- .../generators/python/PythonGenerator.spec.ts | 65 +++++++++++++++++++ 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/src/generators/python/renderers/ClassRenderer.ts b/src/generators/python/renderers/ClassRenderer.ts index 658a20b0cf..50490e3dc8 100644 --- a/src/generators/python/renderers/ClassRenderer.ts +++ b/src/generators/python/renderers/ClassRenderer.ts @@ -87,12 +87,18 @@ ${this.indent(this.renderBlock(content, 2))} returnPropertyType({ model, property, - propertyType + propertyType, + visitedMap = [] }: { model: ConstrainedMetaModel; property: ConstrainedMetaModel; propertyType: string; + visitedMap?: string[]; }): string { + if (visitedMap.includes(property.name)) { + return propertyType; + } + visitedMap.push(property.name); const isSelfReference = property instanceof ConstrainedReferenceModel && property.ref === model; if (isSelfReference) { @@ -102,7 +108,8 @@ ${this.indent(this.renderBlock(content, 2))} return this.returnPropertyType({ model, property: property.valueModel, - propertyType + propertyType, + visitedMap }); } else if (property instanceof ConstrainedTupleModel) { let newPropType = propertyType; @@ -110,7 +117,8 @@ ${this.indent(this.renderBlock(content, 2))} newPropType = this.returnPropertyType({ model, property: tupl.value, - propertyType + propertyType, + visitedMap }); } return newPropType; @@ -120,7 +128,8 @@ ${this.indent(this.renderBlock(content, 2))} newPropType = this.returnPropertyType({ model, property: unionModel, - propertyType + propertyType, + visitedMap }); } return newPropType; @@ -128,7 +137,8 @@ ${this.indent(this.renderBlock(content, 2))} return this.returnPropertyType({ model, property: property.value, - propertyType + propertyType, + visitedMap }); } return propertyType; diff --git a/test/generators/python/PythonGenerator.spec.ts b/test/generators/python/PythonGenerator.spec.ts index e183102e8b..1399cbdcaf 100644 --- a/test/generators/python/PythonGenerator.spec.ts +++ b/test/generators/python/PythonGenerator.spec.ts @@ -109,6 +109,71 @@ describe('PythonGenerator', () => { expect(models).toHaveLength(1); expect(models[0].result).toMatchSnapshot(); }); + + test('should handle weird self reference models', async () => { + const doc = { + allOf: [ + { + $ref: '#/definitions/json-schema-draft-07-schema' + }, + { + properties: { + items: { + anyOf: [ + { + $ref: '#' + }, + { + type: 'array', + minItems: 1, + items: { + $ref: '#' + } + } + ], + default: {} + } + } + } + ], + definitions: { + 'json-schema-draft-07-schema': { + title: 'Core schema meta-schema', + definitions: { + schemaArray: { + type: 'array', + minItems: 1, + items: { + $ref: '#/definitions/json-schema-draft-07-schema' + } + } + }, + type: ['object', 'boolean'], + properties: { + additionalItems: { + $ref: '#/definitions/json-schema-draft-07-schema' + }, + items: { + anyOf: [ + { + $ref: '#/definitions/json-schema-draft-07-schema' + }, + { + $ref: '#/definitions/json-schema-draft-07-schema/definitions/schemaArray' + } + ], + default: true + } + }, + default: true + } + } + }; + const models = await generator.generate(doc); + expect(models).toHaveLength(1); + expect(models[0].result).toMatchSnapshot(); + }); + test('should render `class` type', async () => { const doc = { $id: 'Address', From 968a55faae12bb8eb02f99975242115240947838 Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Wed, 3 Apr 2024 11:18:12 +0200 Subject: [PATCH 07/15] changes --- src/helpers/ConstrainHelpers.ts | 1 + src/interpreter/InterpretAdditionalItems.ts | 13 ++++++++----- test/generators/python/PythonGenerator.spec.ts | 12 +++++------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/helpers/ConstrainHelpers.ts b/src/helpers/ConstrainHelpers.ts index 8370f4ec9f..4d8ee00c3d 100644 --- a/src/helpers/ConstrainHelpers.ts +++ b/src/helpers/ConstrainHelpers.ts @@ -616,6 +616,7 @@ export function constrainMetaModel< options: context.options }); const newContext = { ...context, constrainedName }; + if (newContext.metaModel instanceof ObjectModel) { return constrainObjectModel( typeMapping, diff --git a/src/interpreter/InterpretAdditionalItems.ts b/src/interpreter/InterpretAdditionalItems.ts index 712cbf84b1..dde3bbda5e 100644 --- a/src/interpreter/InterpretAdditionalItems.ts +++ b/src/interpreter/InterpretAdditionalItems.ts @@ -19,15 +19,18 @@ export default function interpretAdditionalItems( interpreter: Interpreter, interpreterOptions: InterpreterOptions = Interpreter.defaultInterpreterOptions ): void { - if (typeof schema === 'boolean' || model.type?.includes('array') === false) { + const isTuple = model.items !== undefined && Array.isArray(model.items); + if ( + typeof schema === 'boolean' || + model.type?.includes('array') === false || + !isTuple + ) { return; } const hasArrayTypes = schema.items !== undefined; let defaultAdditionalItems = true; - if (hasArrayTypes && interpreterOptions.ignoreAdditionalItems !== undefined) { - defaultAdditionalItems = interpreterOptions.ignoreAdditionalItems - ? false - : true; + if (hasArrayTypes && interpreterOptions.ignoreAdditionalItems === true) { + defaultAdditionalItems = false; } const additionalItemsModel = interpreter.interpret( diff --git a/test/generators/python/PythonGenerator.spec.ts b/test/generators/python/PythonGenerator.spec.ts index fc81d9ce35..649df024b4 100644 --- a/test/generators/python/PythonGenerator.spec.ts +++ b/test/generators/python/PythonGenerator.spec.ts @@ -129,8 +129,7 @@ describe('PythonGenerator', () => { $ref: '#' } } - ], - default: {} + ] } } } @@ -160,13 +159,12 @@ describe('PythonGenerator', () => { { $ref: '#/definitions/json-schema-draft-07-schema/definitions/schemaArray' } - ], - default: true + ] } - }, - default: true + } } - } + }, + "$id": "root" }; const models = await generator.generate(doc); expect(models).toHaveLength(1); From c6a8d720df67a9318b72415f9025449da5703ba1 Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Thu, 18 Apr 2024 08:54:45 +0200 Subject: [PATCH 08/15] wip --- docs/migrations/version-3-to-4.md | 7 + .../cplusplus/CplusplusGenerator.ts | 24 +- src/generators/csharp/CSharpGenerator.ts | 25 +- src/generators/dart/DartGenerator.ts | 29 +- src/generators/go/GoGenerator.ts | 27 +- src/generators/java/JavaGenerator.ts | 27 +- .../javascript/JavaScriptGenerator.ts | 32 +- src/generators/kotlin/KotlinGenerator.ts | 32 +- src/generators/php/PhpGenerator.ts | 32 +- src/generators/python/PythonGenerator.ts | 23 +- src/generators/rust/RustGenerator.ts | 27 +- src/generators/scala/ScalaGenerator.ts | 27 +- src/generators/template/TemplateGenerator.ts | 26 +- .../typescript/TypeScriptGenerator.ts | 23 +- src/helpers/CommonModelToMetaModel.ts | 1 + src/helpers/ConstrainHelpers.ts | 649 +----------------- src/helpers/ConstrainedTypes.ts | 326 +++++++++ src/helpers/MetaModelIterator.ts | 153 +++++ src/helpers/MetaModelToConstrained.ts | 555 +++++++++++++++ src/processors/JsonSchemaInputProcessor.ts | 1 + .../CplusplusGenerator.spec.ts.snap | 2 +- .../CSharpGenerator.spec.ts.snap | 14 +- .../__snapshots__/JavaGenerator.spec.ts.snap | 1 - .../KotlinGenerator.spec.ts.snap | 8 +- .../ConstraintsPreset.spec.ts.snap | 4 +- .../DescriptionPreset.spec.ts.snap | 2 +- .../__snapshots__/PhpGenerator.spec.ts.snap | 50 +- .../generators/python/PythonGenerator.spec.ts | 25 +- .../PythonGenerator.spec.ts.snap | 102 ++- .../__snapshots__/Pydantic.spec.ts.snap | 2 +- .../TypeScriptGenerator.spec.ts.snap | 42 +- test/models/ConstrainedMetaModel.spec.ts | 2 +- 32 files changed, 1566 insertions(+), 734 deletions(-) create mode 100644 src/helpers/ConstrainedTypes.ts create mode 100644 src/helpers/MetaModelIterator.ts create mode 100644 src/helpers/MetaModelToConstrained.ts diff --git a/docs/migrations/version-3-to-4.md b/docs/migrations/version-3-to-4.md index e07a07da39..5002e55d0e 100644 --- a/docs/migrations/version-3-to-4.md +++ b/docs/migrations/version-3-to-4.md @@ -2,6 +2,13 @@ This document contain all the breaking changes and migrations guidelines for adapting your code to the new version. +## Rewrite of how types are determined + +In v3 and below, there was a edge case where a type could not be determined IF it was a cyclic model that was NOT split out into separate models. This change affects all generators to various degrees. + +For example + + ## Deprecation of `processor.interpreter` Since the early days we had the option to set `processorOptions.interpreter` options to change how JSON Schema is interpreted to Meta models. However, these options are more accurately part of the `processorOptions.jsonSchema` options. diff --git a/src/generators/cplusplus/CplusplusGenerator.ts b/src/generators/cplusplus/CplusplusGenerator.ts index 9cc74b9da3..67d7501a65 100644 --- a/src/generators/cplusplus/CplusplusGenerator.ts +++ b/src/generators/cplusplus/CplusplusGenerator.ts @@ -6,9 +6,15 @@ import { defaultGeneratorOptions } from '../AbstractGenerator'; import { + ConstrainedAnyModel, + ConstrainedBooleanModel, ConstrainedEnumModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, ConstrainedMetaModel, ConstrainedObjectModel, + ConstrainedReferenceModel, + ConstrainedStringModel, InputMetaModel, MetaModel, RenderOutput @@ -52,6 +58,21 @@ export type CplusplusPropertyKeyConstraint = // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface CplusplusRenderCompleteModelOptions {} + +/** + * All the constrained models that do not depend on others to determine the type + */ +const SAFE_MODEL_TYPES: any[] = [ + ConstrainedObjectModel, + ConstrainedReferenceModel, + ConstrainedAnyModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, + ConstrainedStringModel, + ConstrainedBooleanModel, + ConstrainedEnumModel +]; + export class CplusplusGenerator extends AbstractGenerator< CplusplusOptions, CplusplusRenderCompleteModelOptions @@ -126,7 +147,8 @@ export class CplusplusGenerator extends AbstractGenerator< dependencyManager: dependencyManagerToUse, options: this.options, constrainedName: '' //This is just a placeholder, it will be constrained within the function - } + }, + SAFE_MODEL_TYPES ); } diff --git a/src/generators/csharp/CSharpGenerator.ts b/src/generators/csharp/CSharpGenerator.ts index add9bab4ec..2bd83b54a4 100644 --- a/src/generators/csharp/CSharpGenerator.ts +++ b/src/generators/csharp/CSharpGenerator.ts @@ -6,9 +6,16 @@ import { defaultGeneratorOptions } from '../AbstractGenerator'; import { + ConstrainedAnyModel, + ConstrainedBooleanModel, ConstrainedEnumModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, ConstrainedMetaModel, ConstrainedObjectModel, + ConstrainedReferenceModel, + ConstrainedStringModel, + ConstrainedUnionModel, InputMetaModel, MetaModel, RenderOutput @@ -61,6 +68,21 @@ export interface CSharpRenderCompleteModelOptions { namespace: string; } +/** + * All the constrained models that do not depend on others to determine the type + */ +const SAFE_MODEL_TYPES: any[] = [ + ConstrainedObjectModel, + ConstrainedReferenceModel, + ConstrainedAnyModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, + ConstrainedStringModel, + ConstrainedBooleanModel, + ConstrainedEnumModel, + ConstrainedUnionModel +]; + /** * Generator for CSharp */ @@ -144,7 +166,8 @@ export class CSharpGenerator extends AbstractGenerator< dependencyManager: dependencyManagerToUse, options: this.options, constrainedName: '' //This is just a placeholder, it will be constrained within the function, - } + }, + SAFE_MODEL_TYPES ); } diff --git a/src/generators/dart/DartGenerator.ts b/src/generators/dart/DartGenerator.ts index 08885ba0f5..0c7083bd79 100644 --- a/src/generators/dart/DartGenerator.ts +++ b/src/generators/dart/DartGenerator.ts @@ -11,7 +11,15 @@ import { MetaModel, ConstrainedObjectModel, ConstrainedEnumModel, - InputMetaModel + InputMetaModel, + ConstrainedAnyModel, + ConstrainedBooleanModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, + ConstrainedReferenceModel, + ConstrainedStringModel, + ConstrainedTupleModel, + ConstrainedUnionModel } from '../../models'; import { ConstantConstraint, @@ -52,6 +60,22 @@ export interface DartRenderCompleteModelOptions { packageName: string; } +/** + * All the constrained models that do not depend on others to determine the type + */ +const SAFE_MODEL_TYPES: any[] = [ + ConstrainedObjectModel, + ConstrainedReferenceModel, + ConstrainedAnyModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, + ConstrainedStringModel, + ConstrainedBooleanModel, + ConstrainedTupleModel, + ConstrainedEnumModel, + ConstrainedUnionModel +]; + export class DartGenerator extends AbstractGenerator< DartOptions, DartRenderCompleteModelOptions @@ -123,7 +147,8 @@ export class DartGenerator extends AbstractGenerator< dependencyManager: dependencyManagerToUse, options: this.options, constrainedName: '' //This is just a placeholder, it will be constrained within the function - } + }, + SAFE_MODEL_TYPES ); } /** diff --git a/src/generators/go/GoGenerator.ts b/src/generators/go/GoGenerator.ts index 12fbf55107..35deed4114 100644 --- a/src/generators/go/GoGenerator.ts +++ b/src/generators/go/GoGenerator.ts @@ -11,7 +11,14 @@ import { ConstrainedObjectModel, ConstrainedEnumModel, ConstrainedMetaModel, - MetaModel + MetaModel, + ConstrainedAnyModel, + ConstrainedBooleanModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, + ConstrainedReferenceModel, + ConstrainedStringModel, + ConstrainedTupleModel } from '../../models'; import { ConstantConstraint, @@ -48,6 +55,21 @@ export interface GoRenderCompleteModelOptions { packageName: string; } +/** + * All the constrained models that do not depend on others to determine the type + */ +const SAFE_MODEL_TYPES: any[] = [ + ConstrainedObjectModel, + ConstrainedReferenceModel, + ConstrainedAnyModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, + ConstrainedStringModel, + ConstrainedBooleanModel, + ConstrainedTupleModel, + ConstrainedEnumModel +]; + /** * Generator for Go */ @@ -121,7 +143,8 @@ export class GoGenerator extends AbstractGenerator< dependencyManager: dependencyManagerToUse, options: this.options, constrainedName: '' //This is just a placeholder, it will be constrained within the function - } + }, + SAFE_MODEL_TYPES ); } diff --git a/src/generators/java/JavaGenerator.ts b/src/generators/java/JavaGenerator.ts index c2033ad93d..55134ead06 100644 --- a/src/generators/java/JavaGenerator.ts +++ b/src/generators/java/JavaGenerator.ts @@ -6,9 +6,16 @@ import { defaultGeneratorOptions } from '../AbstractGenerator'; import { + ConstrainedAnyModel, + ConstrainedBooleanModel, ConstrainedEnumModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, ConstrainedMetaModel, ConstrainedObjectModel, + ConstrainedReferenceModel, + ConstrainedStringModel, + ConstrainedTupleModel, ConstrainedUnionModel, InputMetaModel, MetaModel, @@ -54,6 +61,23 @@ export type JavaTypeMapping = TypeMapping; export interface JavaRenderCompleteModelOptions { packageName: string; } + +/** + * All the constrained models that do not depend on others to determine the type + */ +const SAFE_MODEL_TYPES: any[] = [ + ConstrainedObjectModel, + ConstrainedReferenceModel, + ConstrainedAnyModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, + ConstrainedStringModel, + ConstrainedBooleanModel, + ConstrainedTupleModel, + ConstrainedEnumModel, + ConstrainedUnionModel +]; + export class JavaGenerator extends AbstractGenerator< JavaOptions, JavaRenderCompleteModelOptions @@ -126,7 +150,8 @@ export class JavaGenerator extends AbstractGenerator< dependencyManager: dependencyManagerToUse, options: this.options, constrainedName: '' //This is just a placeholder, it will be constrained within the function - } + }, + SAFE_MODEL_TYPES ); } diff --git a/src/generators/javascript/JavaScriptGenerator.ts b/src/generators/javascript/JavaScriptGenerator.ts index c665555347..07cd169464 100644 --- a/src/generators/javascript/JavaScriptGenerator.ts +++ b/src/generators/javascript/JavaScriptGenerator.ts @@ -6,8 +6,19 @@ import { defaultGeneratorOptions } from '../AbstractGenerator'; import { + ConstrainedAnyModel, + ConstrainedArrayModel, + ConstrainedBooleanModel, + ConstrainedDictionaryModel, + ConstrainedEnumModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, ConstrainedMetaModel, ConstrainedObjectModel, + ConstrainedReferenceModel, + ConstrainedStringModel, + ConstrainedTupleModel, + ConstrainedUnionModel, InputMetaModel, MetaModel, RenderOutput @@ -55,6 +66,24 @@ export type JavaScriptTypeMapping = TypeMapping< // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface JavaScriptRenderCompleteModelOptions {} +/** + * All the constrained models that do not depend on others to determine the type + */ +const SAFE_MODEL_TYPES: any[] = [ + ConstrainedObjectModel, + ConstrainedReferenceModel, + ConstrainedAnyModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, + ConstrainedStringModel, + ConstrainedBooleanModel, + ConstrainedTupleModel, + ConstrainedArrayModel, + ConstrainedEnumModel, + ConstrainedUnionModel, + ConstrainedDictionaryModel +]; + /** * Generator for JavaScript */ @@ -210,7 +239,8 @@ ${modelCode}`; dependencyManager: dependencyManagerToUse, options: this.options, constrainedName: '' //This is just a placeholder, it will be constrained within the function - } + }, + SAFE_MODEL_TYPES ); } diff --git a/src/generators/kotlin/KotlinGenerator.ts b/src/generators/kotlin/KotlinGenerator.ts index bc00a3186a..43665a28be 100644 --- a/src/generators/kotlin/KotlinGenerator.ts +++ b/src/generators/kotlin/KotlinGenerator.ts @@ -6,9 +6,19 @@ import { defaultGeneratorOptions } from '../AbstractGenerator'; import { + ConstrainedAnyModel, + ConstrainedArrayModel, + ConstrainedBooleanModel, + ConstrainedDictionaryModel, ConstrainedEnumModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, ConstrainedMetaModel, ConstrainedObjectModel, + ConstrainedReferenceModel, + ConstrainedStringModel, + ConstrainedTupleModel, + ConstrainedUnionModel, InputMetaModel, MetaModel, RenderOutput @@ -52,6 +62,25 @@ export type KotlinTypeMapping = TypeMapping< export interface KotlinRenderCompleteModelOptions { packageName: string; } + +/** + * All the constrained models that do not depend on others to determine the type + */ +const SAFE_MODEL_TYPES: any[] = [ + ConstrainedObjectModel, + ConstrainedReferenceModel, + ConstrainedAnyModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, + ConstrainedStringModel, + ConstrainedBooleanModel, + ConstrainedTupleModel, + ConstrainedArrayModel, + ConstrainedEnumModel, + ConstrainedUnionModel, + ConstrainedDictionaryModel +]; + export class KotlinGenerator extends AbstractGenerator< KotlinOptions, KotlinRenderCompleteModelOptions @@ -127,7 +156,8 @@ export class KotlinGenerator extends AbstractGenerator< dependencyManager: dependencyManagerToUse, options: optionsToUse, constrainedName: '' //This is just a placeholder, it will be constrained within the function - } + }, + SAFE_MODEL_TYPES ); } diff --git a/src/generators/php/PhpGenerator.ts b/src/generators/php/PhpGenerator.ts index fa19930ab9..396f2dfb12 100644 --- a/src/generators/php/PhpGenerator.ts +++ b/src/generators/php/PhpGenerator.ts @@ -6,9 +6,19 @@ import { defaultGeneratorOptions } from '../AbstractGenerator'; import { + ConstrainedAnyModel, + ConstrainedArrayModel, + ConstrainedBooleanModel, + ConstrainedDictionaryModel, ConstrainedEnumModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, ConstrainedMetaModel, ConstrainedObjectModel, + ConstrainedReferenceModel, + ConstrainedStringModel, + ConstrainedTupleModel, + ConstrainedUnionModel, InputMetaModel, MetaModel, RenderOutput @@ -45,6 +55,25 @@ export interface PhpRenderCompleteModelOptions { namespace: string; declareStrictTypes: boolean; } + +/** + * All the constrained models that do not depend on others to determine the type + */ +const SAFE_MODEL_TYPES: any[] = [ + ConstrainedAnyModel, + ConstrainedBooleanModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, + ConstrainedStringModel, + ConstrainedReferenceModel, + ConstrainedObjectModel, + ConstrainedUnionModel, + ConstrainedEnumModel, + ConstrainedTupleModel, + ConstrainedArrayModel, + ConstrainedDictionaryModel +]; + export class PhpGenerator extends AbstractGenerator< PhpOptions, PhpRenderCompleteModelOptions @@ -119,7 +148,8 @@ export class PhpGenerator extends AbstractGenerator< dependencyManager: dependencyManagerToUse, options: this.options, constrainedName: '' //This is just a placeholder, it will be constrained within the function - } + }, + SAFE_MODEL_TYPES ); } diff --git a/src/generators/python/PythonGenerator.ts b/src/generators/python/PythonGenerator.ts index cc9024e1c8..df9e017891 100644 --- a/src/generators/python/PythonGenerator.ts +++ b/src/generators/python/PythonGenerator.ts @@ -7,9 +7,15 @@ import { defaultGeneratorOptions } from '../AbstractGenerator'; import { + ConstrainedAnyModel, + ConstrainedBooleanModel, ConstrainedEnumModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, ConstrainedMetaModel, ConstrainedObjectModel, + ConstrainedReferenceModel, + ConstrainedStringModel, InputMetaModel, MetaModel, RenderOutput @@ -53,6 +59,20 @@ export type PythonTypeMapping = TypeMapping< // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface PythonRenderCompleteModelOptions {} +/** + * All the constrained models that do not depend on others to determine the type + */ +const SAFE_MODEL_TYPES: any[] = [ + ConstrainedAnyModel, + ConstrainedBooleanModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, + ConstrainedStringModel, + ConstrainedReferenceModel, + ConstrainedObjectModel, + ConstrainedEnumModel +]; + export class PythonGenerator extends AbstractGenerator< PythonOptions, PythonRenderCompleteModelOptions @@ -131,7 +151,8 @@ export class PythonGenerator extends AbstractGenerator< dependencyManager: dependencyManagerToUse, options: { ...this.options }, constrainedName: '' //This is just a placeholder, it will be constrained within the function - } + }, + SAFE_MODEL_TYPES ); } diff --git a/src/generators/rust/RustGenerator.ts b/src/generators/rust/RustGenerator.ts index 14d743e4da..87978f4464 100644 --- a/src/generators/rust/RustGenerator.ts +++ b/src/generators/rust/RustGenerator.ts @@ -14,7 +14,13 @@ import { ConstrainedMetaModel, MetaModel, ConstrainedTupleModel, - ConstrainedUnionModel + ConstrainedUnionModel, + ConstrainedAnyModel, + ConstrainedBooleanModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, + ConstrainedReferenceModel, + ConstrainedStringModel } from '../../models'; import { ConstantConstraint, @@ -93,6 +99,22 @@ export const defaultRustRenderCompleteModelOptions = { export class ConstrainedSupportFileModel extends ConstrainedMetaModel {} +/** + * All the constrained models that do not depend on others to determine the type + */ +const SAFE_MODEL_TYPES: any[] = [ + ConstrainedAnyModel, + ConstrainedBooleanModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, + ConstrainedStringModel, + ConstrainedReferenceModel, + ConstrainedObjectModel, + ConstrainedUnionModel, + ConstrainedEnumModel, + ConstrainedTupleModel +]; + /** * Generator for Rust */ @@ -185,7 +207,8 @@ export class RustGenerator extends AbstractGenerator< dependencyManager: dependencyManagerToUse, options: this.options, constrainedName: '' //This is just a placeholder, it will be constrained within the function - } + }, + SAFE_MODEL_TYPES ); } diff --git a/src/generators/scala/ScalaGenerator.ts b/src/generators/scala/ScalaGenerator.ts index 0309e0c756..782c340435 100644 --- a/src/generators/scala/ScalaGenerator.ts +++ b/src/generators/scala/ScalaGenerator.ts @@ -6,9 +6,17 @@ import { defaultGeneratorOptions } from '../AbstractGenerator'; import { + ConstrainedAnyModel, + ConstrainedBooleanModel, ConstrainedEnumModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, ConstrainedMetaModel, ConstrainedObjectModel, + ConstrainedReferenceModel, + ConstrainedStringModel, + ConstrainedTupleModel, + ConstrainedUnionModel, InputMetaModel, MetaModel, RenderOutput @@ -55,6 +63,22 @@ export interface ScalaRenderCompleteModelOptions { packageName: string; } +/** + * All the constrained models that do not depend on others to determine the type + */ +const SAFE_MODEL_TYPES: any[] = [ + ConstrainedAnyModel, + ConstrainedBooleanModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, + ConstrainedStringModel, + ConstrainedReferenceModel, + ConstrainedObjectModel, + ConstrainedUnionModel, + ConstrainedEnumModel, + ConstrainedTupleModel +]; + export class ScalaGenerator extends AbstractGenerator< ScalaOptions, ScalaRenderCompleteModelOptions @@ -124,7 +148,8 @@ export class ScalaGenerator extends AbstractGenerator< dependencyManager: dependencyManagerToUse, options: this.options, constrainedName: '' //This is just a placeholder, it will be constrained within the function - } + }, + SAFE_MODEL_TYPES ); } diff --git a/src/generators/template/TemplateGenerator.ts b/src/generators/template/TemplateGenerator.ts index d7033fcb85..2e7bb8bd55 100644 --- a/src/generators/template/TemplateGenerator.ts +++ b/src/generators/template/TemplateGenerator.ts @@ -6,9 +6,16 @@ import { defaultGeneratorOptions } from '../AbstractGenerator'; import { + ConstrainedAnyModel, + ConstrainedBooleanModel, ConstrainedEnumModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, ConstrainedMetaModel, ConstrainedObjectModel, + ConstrainedReferenceModel, + ConstrainedStringModel, + ConstrainedUnionModel, InputMetaModel, MetaModel, RenderOutput @@ -49,6 +56,22 @@ export type TemplatePropertyKeyConstraint = export interface TemplateRenderCompleteModelOptions { packageName: string; } + +/** + * All the constrained models that do not depend on others to determine the type + */ +const SAFE_MODEL_TYPES: any[] = [ + ConstrainedAnyModel, + ConstrainedBooleanModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, + ConstrainedStringModel, + ConstrainedReferenceModel, + ConstrainedObjectModel, + ConstrainedUnionModel, + ConstrainedEnumModel +]; + export class TemplateGenerator extends AbstractGenerator< TemplateOptions, TemplateRenderCompleteModelOptions @@ -126,7 +149,8 @@ export class TemplateGenerator extends AbstractGenerator< dependencyManager: dependencyManagerToUse, options: this.options, constrainedName: '' //This is just a placeholder, it will be constrained within the function - } + }, + SAFE_MODEL_TYPES ); } diff --git a/src/generators/typescript/TypeScriptGenerator.ts b/src/generators/typescript/TypeScriptGenerator.ts index 074bdeb367..33e1f52d20 100644 --- a/src/generators/typescript/TypeScriptGenerator.ts +++ b/src/generators/typescript/TypeScriptGenerator.ts @@ -6,9 +6,15 @@ import { defaultGeneratorOptions } from '../AbstractGenerator'; import { + ConstrainedAnyModel, + ConstrainedBooleanModel, ConstrainedEnumModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, ConstrainedMetaModel, ConstrainedObjectModel, + ConstrainedReferenceModel, + ConstrainedStringModel, InputMetaModel, MetaModel, RenderOutput @@ -78,6 +84,20 @@ export interface TypeScriptRenderCompleteModelOptions { exportType: TypeScriptExportType; } +/** + * All the constrained models that do not depend on others to determine the type + */ +const SAFE_MODEL_TYPES: any[] = [ + ConstrainedAnyModel, + ConstrainedBooleanModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, + ConstrainedStringModel, + ConstrainedReferenceModel, + ConstrainedObjectModel, + ConstrainedEnumModel +]; + /** * Generator for TypeScript */ @@ -171,7 +191,8 @@ export class TypeScriptGenerator extends AbstractGenerator< dependencyManager: dependencyManagerToUse, options: { ...this.options }, constrainedName: '' //This is just a placeholder, it will be constrained within the function - } + }, + SAFE_MODEL_TYPES ); } diff --git a/src/helpers/CommonModelToMetaModel.ts b/src/helpers/CommonModelToMetaModel.ts index 5da841bd68..67bd7f7dfa 100644 --- a/src/helpers/CommonModelToMetaModel.ts +++ b/src/helpers/CommonModelToMetaModel.ts @@ -421,6 +421,7 @@ export function convertToEnumModel( return metaModel; } + export function convertToBooleanModel( context: ConverterContext ): BooleanModel | undefined { diff --git a/src/helpers/ConstrainHelpers.ts b/src/helpers/ConstrainHelpers.ts index 4d8ee00c3d..e43b9ee645 100644 --- a/src/helpers/ConstrainHelpers.ts +++ b/src/helpers/ConstrainHelpers.ts @@ -1,41 +1,29 @@ import { AbstractDependencyManager } from '../generators/AbstractDependencyManager'; import { + ConstrainedMetaModel, + ConstrainedObjectModel, + ConstrainedEnumModel, + ConstrainedObjectPropertyModel, ConstrainedAnyModel, ConstrainedBooleanModel, ConstrainedFloatModel, ConstrainedIntegerModel, - ConstrainedMetaModel, - ConstrainedObjectModel, ConstrainedReferenceModel, ConstrainedStringModel, - ConstrainedTupleModel, - ConstrainedTupleValueModel, - ConstrainedArrayModel, ConstrainedUnionModel, - ConstrainedEnumModel, + ConstrainedArrayModel, ConstrainedDictionaryModel, - ConstrainedEnumValueModel, - ConstrainedObjectPropertyModel, - ConstrainedMetaModelOptions + ConstrainedTupleModel } from '../models/ConstrainedMetaModel'; import { - AnyModel, - BooleanModel, - FloatModel, - IntegerModel, ObjectModel, - ReferenceModel, - StringModel, - TupleModel, - ArrayModel, - UnionModel, EnumModel, - DictionaryModel, MetaModel, - ObjectPropertyModel, - EnumValueModel + ObjectPropertyModel } from '../models/MetaModel'; -import { getTypeFromMapping, TypeMapping } from './TypeHelpers'; +import { applyTypes } from './ConstrainedTypes'; +import { metaModelFactory } from './MetaModelToConstrained'; +import { TypeMapping } from './TypeHelpers'; export type ConstrainContext< Options, @@ -106,499 +94,6 @@ export interface Constraints { constant: ConstantConstraint; } -const placeHolderConstrainedObject = new ConstrainedAnyModel( - '', - undefined, - {}, - '' -); - -function getConstrainedMetaModelOptions( - metaModel: MetaModel -): ConstrainedMetaModelOptions { - const options: ConstrainedMetaModelOptions = {}; - - options.const = metaModel.options.const; - options.isNullable = metaModel.options.isNullable; - options.discriminator = metaModel.options.discriminator; - options.format = metaModel.options.format; - options.isExtended = metaModel.options.isExtended; - - return options; -} - -function constrainReferenceModel< - Options, - DependencyManager extends AbstractDependencyManager ->( - typeMapping: TypeMapping, - constrainRules: Constraints, - context: ConstrainContext, - alreadySeenModels: Map -): ConstrainedReferenceModel { - const constrainedModel = new ConstrainedReferenceModel( - context.constrainedName, - context.metaModel.originalInput, - getConstrainedMetaModelOptions(context.metaModel), - '', - placeHolderConstrainedObject - ); - alreadySeenModels.set(context.metaModel, constrainedModel); - - const constrainedRefModel = constrainMetaModel( - typeMapping, - constrainRules, - { ...context, metaModel: context.metaModel.ref, partOfProperty: undefined }, - alreadySeenModels - ); - constrainedModel.ref = constrainedRefModel; - constrainedModel.type = getTypeFromMapping(typeMapping, { - constrainedModel, - options: context.options, - partOfProperty: context.partOfProperty, - dependencyManager: context.dependencyManager - }); - - if (constrainedModel.options.const) { - const constrainedConstant = constrainRules.constant({ - constrainedMetaModel: constrainedModel, - options: context.options - }); - constrainedModel.options.const.value = constrainedConstant; - } - - return constrainedModel; -} -function constrainAnyModel< - Options, - DependencyManager extends AbstractDependencyManager ->( - typeMapping: TypeMapping, - context: ConstrainContext -): ConstrainedAnyModel { - const constrainedModel = new ConstrainedAnyModel( - context.constrainedName, - context.metaModel.originalInput, - getConstrainedMetaModelOptions(context.metaModel), - '' - ); - constrainedModel.type = getTypeFromMapping(typeMapping, { - constrainedModel, - options: context.options, - partOfProperty: context.partOfProperty, - dependencyManager: context.dependencyManager - }); - return constrainedModel; -} -function constrainFloatModel< - Options, - DependencyManager extends AbstractDependencyManager ->( - typeMapping: TypeMapping, - context: ConstrainContext -): ConstrainedFloatModel { - const constrainedModel = new ConstrainedFloatModel( - context.constrainedName, - context.metaModel.originalInput, - getConstrainedMetaModelOptions(context.metaModel), - '' - ); - constrainedModel.type = getTypeFromMapping(typeMapping, { - constrainedModel, - options: context.options, - partOfProperty: context.partOfProperty, - dependencyManager: context.dependencyManager - }); - return constrainedModel; -} -function constrainIntegerModel< - Options, - DependencyManager extends AbstractDependencyManager ->( - typeMapping: TypeMapping, - context: ConstrainContext -): ConstrainedIntegerModel { - const constrainedModel = new ConstrainedIntegerModel( - context.constrainedName, - context.metaModel.originalInput, - getConstrainedMetaModelOptions(context.metaModel), - '' - ); - constrainedModel.type = getTypeFromMapping(typeMapping, { - constrainedModel, - options: context.options, - partOfProperty: context.partOfProperty, - dependencyManager: context.dependencyManager - }); - return constrainedModel; -} -function constrainStringModel< - Options, - DependencyManager extends AbstractDependencyManager ->( - typeMapping: TypeMapping, - context: ConstrainContext -): ConstrainedStringModel { - const constrainedModel = new ConstrainedStringModel( - context.constrainedName, - context.metaModel.originalInput, - getConstrainedMetaModelOptions(context.metaModel), - '' - ); - constrainedModel.type = getTypeFromMapping(typeMapping, { - constrainedModel, - options: context.options, - partOfProperty: context.partOfProperty, - dependencyManager: context.dependencyManager - }); - return constrainedModel; -} -function constrainBooleanModel< - Options, - DependencyManager extends AbstractDependencyManager ->( - typeMapping: TypeMapping, - context: ConstrainContext -): ConstrainedBooleanModel { - const constrainedModel = new ConstrainedBooleanModel( - context.constrainedName, - context.metaModel.originalInput, - getConstrainedMetaModelOptions(context.metaModel), - '' - ); - constrainedModel.type = getTypeFromMapping(typeMapping, { - constrainedModel, - options: context.options, - partOfProperty: context.partOfProperty, - dependencyManager: context.dependencyManager - }); - return constrainedModel; -} -function constrainTupleModel< - Options, - DependencyManager extends AbstractDependencyManager ->( - typeMapping: TypeMapping, - constrainRules: Constraints, - context: ConstrainContext, - alreadySeenModels: Map -): ConstrainedTupleModel { - const constrainedModel = new ConstrainedTupleModel( - context.constrainedName, - context.metaModel.originalInput, - getConstrainedMetaModelOptions(context.metaModel), - '', - [] - ); - alreadySeenModels.set(context.metaModel, constrainedModel); - - const constrainedTupleModels = context.metaModel.tuple.map((tupleValue) => { - const tupleType = constrainMetaModel( - typeMapping, - constrainRules, - { ...context, metaModel: tupleValue.value, partOfProperty: undefined }, - alreadySeenModels - ); - return new ConstrainedTupleValueModel(tupleValue.index, tupleType); - }); - constrainedModel.tuple = constrainedTupleModels; - constrainedModel.type = getTypeFromMapping(typeMapping, { - constrainedModel, - options: context.options, - partOfProperty: context.partOfProperty, - dependencyManager: context.dependencyManager - }); - return constrainedModel; -} -function constrainArrayModel< - Options, - DependencyManager extends AbstractDependencyManager ->( - typeMapping: TypeMapping, - constrainRules: Constraints, - context: ConstrainContext, - alreadySeenModels: Map -): ConstrainedArrayModel { - const constrainedModel = new ConstrainedArrayModel( - context.constrainedName, - context.metaModel.originalInput, - getConstrainedMetaModelOptions(context.metaModel), - '', - placeHolderConstrainedObject - ); - alreadySeenModels.set(context.metaModel, constrainedModel); - const constrainedValueModel = constrainMetaModel( - typeMapping, - constrainRules, - { - ...context, - metaModel: context.metaModel.valueModel, - partOfProperty: undefined - }, - alreadySeenModels - ); - constrainedModel.valueModel = constrainedValueModel; - constrainedModel.type = getTypeFromMapping(typeMapping, { - constrainedModel, - options: context.options, - partOfProperty: context.partOfProperty, - dependencyManager: context.dependencyManager - }); - return constrainedModel; -} -function addDiscriminatorTypeToUnionModel( - constrainedModel: ConstrainedUnionModel -) { - if (!constrainedModel.options.discriminator) { - return; - } - - const propertyTypes = new Set(); - - for (const union of constrainedModel.union) { - if (union instanceof ConstrainedReferenceModel) { - const ref = union.ref; - - if (ref instanceof ConstrainedObjectModel) { - const discriminatorProp = Object.values(ref.properties).find( - (model) => - model.unconstrainedPropertyName === - constrainedModel.options.discriminator?.discriminator - ); - - if (discriminatorProp) { - propertyTypes.add(discriminatorProp.property.type); - } - } - } - } - - if (propertyTypes.size === 1) { - constrainedModel.options.discriminator.type = propertyTypes - .keys() - .next().value; - } -} -function constrainUnionModel< - Options, - DependencyManager extends AbstractDependencyManager ->( - typeMapping: TypeMapping, - constrainRules: Constraints, - context: ConstrainContext, - alreadySeenModels: Map -): ConstrainedUnionModel { - const constrainedModel = new ConstrainedUnionModel( - context.constrainedName, - context.metaModel.originalInput, - getConstrainedMetaModelOptions(context.metaModel), - '', - [] - ); - alreadySeenModels.set(context.metaModel, constrainedModel); - - const constrainedUnionModels = context.metaModel.union.map((unionValue) => { - return constrainMetaModel( - typeMapping, - constrainRules, - { ...context, metaModel: unionValue, partOfProperty: undefined }, - alreadySeenModels - ); - }); - constrainedModel.union = constrainedUnionModels; - - constrainedModel.type = getTypeFromMapping(typeMapping, { - constrainedModel, - options: context.options, - partOfProperty: context.partOfProperty, - dependencyManager: context.dependencyManager - }); - - addDiscriminatorTypeToUnionModel(constrainedModel); - - return constrainedModel; -} -function constrainDictionaryModel< - Options, - DependencyManager extends AbstractDependencyManager ->( - typeMapping: TypeMapping, - constrainRules: Constraints, - context: ConstrainContext, - alreadySeenModels: Map -): ConstrainedDictionaryModel { - const constrainedModel = new ConstrainedDictionaryModel( - context.constrainedName, - context.metaModel.originalInput, - getConstrainedMetaModelOptions(context.metaModel), - '', - placeHolderConstrainedObject, - placeHolderConstrainedObject, - context.metaModel.serializationType - ); - alreadySeenModels.set(context.metaModel, constrainedModel); - - const keyModel = constrainMetaModel( - typeMapping, - constrainRules, - { ...context, metaModel: context.metaModel.key, partOfProperty: undefined }, - alreadySeenModels - ); - constrainedModel.key = keyModel; - const valueModel = constrainMetaModel( - typeMapping, - constrainRules, - { - ...context, - metaModel: context.metaModel.value, - partOfProperty: undefined - }, - alreadySeenModels - ); - constrainedModel.value = valueModel; - constrainedModel.type = getTypeFromMapping(typeMapping, { - constrainedModel, - options: context.options, - partOfProperty: context.partOfProperty, - dependencyManager: context.dependencyManager - }); - return constrainedModel; -} - -function constrainObjectModel< - Options, - DependencyManager extends AbstractDependencyManager ->( - typeMapping: TypeMapping, - constrainRules: Constraints, - context: ConstrainContext, - alreadySeenModels: Map -): ConstrainedObjectModel { - const options = getConstrainedMetaModelOptions(context.metaModel); - - if (context.metaModel.options.extend?.length) { - options.extend = []; - - for (const extend of context.metaModel.options.extend) { - options.extend.push( - constrainMetaModel( - typeMapping, - constrainRules, - { - ...context, - metaModel: extend, - partOfProperty: undefined - }, - alreadySeenModels - ) - ); - } - } - - const constrainedModel = new ConstrainedObjectModel( - context.constrainedName, - context.metaModel.originalInput, - options, - '', - {} - ); - alreadySeenModels.set(context.metaModel, constrainedModel); - - for (const propertyMetaModel of Object.values(context.metaModel.properties)) { - const constrainedPropertyModel = new ConstrainedObjectPropertyModel( - '', - propertyMetaModel.propertyName, - propertyMetaModel.required, - constrainedModel - ); - const constrainedPropertyName = constrainRules.propertyKey({ - objectPropertyModel: propertyMetaModel, - constrainedObjectPropertyModel: constrainedPropertyModel, - constrainedObjectModel: constrainedModel, - objectModel: context.metaModel, - options: context.options - }); - constrainedPropertyModel.propertyName = constrainedPropertyName; - const constrainedProperty = constrainMetaModel( - typeMapping, - constrainRules, - { - ...context, - metaModel: propertyMetaModel.property, - partOfProperty: constrainedPropertyModel - }, - alreadySeenModels - ); - constrainedPropertyModel.property = constrainedProperty; - - constrainedModel.properties[String(constrainedPropertyName)] = - constrainedPropertyModel; - } - - constrainedModel.type = getTypeFromMapping(typeMapping, { - constrainedModel, - options: context.options, - partOfProperty: context.partOfProperty, - dependencyManager: context.dependencyManager - }); - return constrainedModel; -} - -function ConstrainEnumModel< - Options, - DependencyManager extends AbstractDependencyManager ->( - typeMapping: TypeMapping, - constrainRules: Constraints, - context: ConstrainContext -): ConstrainedEnumModel { - const constrainedModel = new ConstrainedEnumModel( - context.constrainedName, - context.metaModel.originalInput, - getConstrainedMetaModelOptions(context.metaModel), - '', - [] - ); - - const enumValueToConstrainedEnumValueModel = ( - enumValue: EnumValueModel - ): ConstrainedEnumValueModel => { - const constrainedEnumKey = constrainRules.enumKey({ - enumKey: String(enumValue.key), - enumModel: context.metaModel, - constrainedEnumModel: constrainedModel, - options: context.options - }); - const constrainedEnumValue = constrainRules.enumValue({ - enumValue: enumValue.value, - enumModel: context.metaModel, - constrainedEnumModel: constrainedModel, - options: context.options - }); - return new ConstrainedEnumValueModel( - constrainedEnumKey, - constrainedEnumValue, - enumValue.value - ); - }; - - for (const enumValue of context.metaModel.values) { - constrainedModel.values.push( - enumValueToConstrainedEnumValueModel(enumValue) - ); - } - - constrainedModel.type = getTypeFromMapping(typeMapping, { - constrainedModel, - options: context.options, - partOfProperty: context.partOfProperty, - dependencyManager: context.dependencyManager - }); - - return constrainedModel; -} - -// eslint-disable-next-line sonarjs/cognitive-complexity export function constrainMetaModel< Options, DependencyManager extends AbstractDependencyManager @@ -606,105 +101,31 @@ export function constrainMetaModel< typeMapping: TypeMapping, constrainRules: Constraints, context: ConstrainContext, - alreadySeenModels: Map = new Map() + safeTypes: any[] = [ + ConstrainedAnyModel, + ConstrainedBooleanModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, + ConstrainedStringModel, + ConstrainedReferenceModel, + ConstrainedObjectModel, + ConstrainedEnumModel, + ConstrainedObjectModel, + ConstrainedUnionModel, + ConstrainedArrayModel, + ConstrainedDictionaryModel, + ConstrainedTupleModel + ] ): ConstrainedMetaModel { - if (alreadySeenModels.has(context.metaModel)) { - return alreadySeenModels.get(context.metaModel) as ConstrainedMetaModel; - } - const constrainedName = constrainRules.modelName({ - modelName: context.metaModel.name, - options: context.options + const constrainedModel = metaModelFactory(constrainRules, context, new Map()); + applyTypes({ + constrainedModel, + generatorOptions: context.options, + typeMapping, + alreadySeenModels: new Map(), + safeTypes, + dependencyManager: context.dependencyManager, + constrainRules }); - const newContext = { ...context, constrainedName }; - - if (newContext.metaModel instanceof ObjectModel) { - return constrainObjectModel( - typeMapping, - constrainRules, - { ...newContext, metaModel: newContext.metaModel }, - alreadySeenModels - ); - } else if (newContext.metaModel instanceof ReferenceModel) { - return constrainReferenceModel( - typeMapping, - constrainRules, - { ...newContext, metaModel: newContext.metaModel }, - alreadySeenModels - ); - } else if (newContext.metaModel instanceof DictionaryModel) { - return constrainDictionaryModel( - typeMapping, - constrainRules, - { ...newContext, metaModel: newContext.metaModel }, - alreadySeenModels - ); - } else if (newContext.metaModel instanceof TupleModel) { - return constrainTupleModel( - typeMapping, - constrainRules, - { ...newContext, metaModel: newContext.metaModel }, - alreadySeenModels - ); - } else if (newContext.metaModel instanceof ArrayModel) { - return constrainArrayModel( - typeMapping, - constrainRules, - { ...newContext, metaModel: newContext.metaModel }, - alreadySeenModels - ); - } else if (newContext.metaModel instanceof UnionModel) { - return constrainUnionModel( - typeMapping, - constrainRules, - { ...newContext, metaModel: newContext.metaModel }, - alreadySeenModels - ); - } - // Simple models are those who does not have properties that contain other MetaModels. - let simpleModel: ConstrainedMetaModel | undefined; - if (newContext.metaModel instanceof EnumModel) { - simpleModel = ConstrainEnumModel(typeMapping, constrainRules, { - ...newContext, - metaModel: newContext.metaModel - }); - } else if (newContext.metaModel instanceof BooleanModel) { - simpleModel = constrainBooleanModel(typeMapping, { - ...newContext, - metaModel: newContext.metaModel - }); - } else if (newContext.metaModel instanceof AnyModel) { - simpleModel = constrainAnyModel(typeMapping, { - ...newContext, - metaModel: newContext.metaModel - }); - } else if (newContext.metaModel instanceof FloatModel) { - simpleModel = constrainFloatModel(typeMapping, { - ...newContext, - metaModel: newContext.metaModel - }); - } else if (newContext.metaModel instanceof IntegerModel) { - simpleModel = constrainIntegerModel(typeMapping, { - ...newContext, - metaModel: newContext.metaModel - }); - } else if (newContext.metaModel instanceof StringModel) { - simpleModel = constrainStringModel(typeMapping, { - ...newContext, - metaModel: newContext.metaModel - }); - } - if (simpleModel !== undefined) { - if (simpleModel.options.const) { - const constrainedConstant = constrainRules.constant({ - constrainedMetaModel: simpleModel, - options: context.options - }); - simpleModel.options.const.value = constrainedConstant; - } - - alreadySeenModels.set(context.metaModel, simpleModel); - return simpleModel; - } - - throw new Error('Could not constrain model'); + return constrainedModel; } diff --git a/src/helpers/ConstrainedTypes.ts b/src/helpers/ConstrainedTypes.ts new file mode 100644 index 0000000000..d0fd53ad8f --- /dev/null +++ b/src/helpers/ConstrainedTypes.ts @@ -0,0 +1,326 @@ +import { AbstractDependencyManager } from '../generators/AbstractDependencyManager'; +import { + ConstrainedAnyModel, + ConstrainedArrayModel, + ConstrainedDictionaryModel, + ConstrainedMetaModel, + ConstrainedObjectModel, + ConstrainedObjectPropertyModel, + ConstrainedReferenceModel, + ConstrainedTupleModel, + ConstrainedUnionModel +} from '../models/ConstrainedMetaModel'; +import { Constraints } from './MetaModelToConstrained'; + +import { TypeMapping, getTypeFromMapping } from './TypeHelpers'; + +export interface ApplyingTypesOptions< + GeneratorOptions, + DependencyManager extends AbstractDependencyManager +> { + /** + * Constrained Model types that are safe to constrain the type for. + * This varies per language as they are split out differently from each other. + * + * A safe type, means that it does not depend on other meta models to determine it's type. + */ + safeTypes: any[]; + constrainedModel: ConstrainedMetaModel; + typeMapping: TypeMapping; + alreadySeenModels: Map; + generatorOptions: GeneratorOptions; + partOfProperty?: ConstrainedObjectPropertyModel; + dependencyManager: DependencyManager; + shouldWalkNode?: boolean; + constrainRules: Constraints; +} + +/** + * Applying types through cyclic analysis (https://en.wikipedia.org/wiki/Cycle_(graph_theory)) + * to detect and adapt unmanageable models that are cyclic where needed. + * + * For example; + * Model a: Union model with Model b + * Model b: Union model with Model a + * In such case we are unable to render the type, cause + */ +export function applyTypes< + GeneratorOptions, + DependencyManager extends AbstractDependencyManager +>( + context: ApplyingTypesOptions +): ConstrainedMetaModel | undefined { + const { + constrainedModel, + safeTypes, + typeMapping, + partOfProperty, + dependencyManager, + generatorOptions, + alreadySeenModels, + shouldWalkNode = true, + constrainRules + } = context; + const isCyclicModel = + alreadySeenModels.has(constrainedModel) && + alreadySeenModels.get(constrainedModel) === undefined; + const hasBeenSolved = alreadySeenModels.has(constrainedModel); + + if (isCyclicModel) { + //Cyclic models detected, having to make the edge (right before cyclic occur) to use any meta model (most open type) + //With the same information as the node we walk to + const anyModel = new ConstrainedAnyModel( + constrainedModel.name, + constrainedModel.originalInput, + constrainedModel.options, + '' + ); + anyModel.type = getTypeFromMapping(typeMapping, { + constrainedModel: anyModel, + options: generatorOptions, + partOfProperty, + dependencyManager + }); + + if (anyModel.options.const) { + const constrainedConstant = constrainRules.constant({ + constrainedMetaModel: anyModel, + options: generatorOptions + }); + anyModel.options.const.value = constrainedConstant; + } + + alreadySeenModels.set(anyModel, anyModel.type); + return anyModel; + } else if (hasBeenSolved) { + return undefined; + } + + //Mark the model as having been walked but does not have a type yet + alreadySeenModels.set(constrainedModel, undefined); + + //Walk over all safe models that can determine it's type right away + for (const safeType of safeTypes) { + if (constrainedModel instanceof safeType) { + constrainedModel.type = getTypeFromMapping(typeMapping, { + constrainedModel, + options: generatorOptions, + partOfProperty, + dependencyManager + }); + + if (constrainedModel.options.const) { + const constrainedConstant = constrainRules.constant({ + constrainedMetaModel: constrainedModel, + options: generatorOptions + }); + constrainedModel.options.const.value = constrainedConstant; + } + alreadySeenModels.set(constrainedModel, constrainedModel.type); + break; + } + } + if (shouldWalkNode) { + walkNode(context); + } +} + +/** + * A node is a model that can contain other models and is not a safe type to constrain i.e. a meta model that is not split out. + */ +function walkNode< + GeneratorOptions, + DependencyManager extends AbstractDependencyManager +>(context: ApplyingTypesOptions) { + const { constrainedModel, constrainRules, generatorOptions } = context; + + if (constrainedModel instanceof ConstrainedObjectModel) { + walkObjectNode(context); + } else if (constrainedModel instanceof ConstrainedDictionaryModel) { + walkDictionaryNode(context); + } else if (constrainedModel instanceof ConstrainedTupleModel) { + walkTupleNode(context); + } else if (constrainedModel instanceof ConstrainedArrayModel) { + walkArrayNode(context); + } else if (constrainedModel instanceof ConstrainedUnionModel) { + walkUnionNode(context); + } else if (constrainedModel instanceof ConstrainedReferenceModel) { + walkReferenceNode(context); + } + if (constrainedModel.type === '') { + constrainedModel.type = getTypeFromMapping(context.typeMapping, { + constrainedModel, + options: context.generatorOptions, + partOfProperty: context.partOfProperty, + dependencyManager: context.dependencyManager + }); + context.alreadySeenModels.set(constrainedModel, constrainedModel.type); + } + + if (constrainedModel instanceof ConstrainedReferenceModel) { + if (constrainedModel.options.const) { + const constrainedConstant = constrainRules.constant({ + constrainedMetaModel: constrainedModel, + options: generatorOptions + }); + constrainedModel.options.const.value = constrainedConstant; + } + } + + if (constrainedModel instanceof ConstrainedUnionModel) { + addDiscriminatorTypeToUnionModel(constrainedModel); + } + +} + +function walkObjectNode< + GeneratorOptions, + DependencyManager extends AbstractDependencyManager +>(context: ApplyingTypesOptions) { + const objectModel = context.constrainedModel as ConstrainedObjectModel; + + for (const [propertyKey, propertyModel] of Object.entries({ + ...objectModel.properties + })) { + const overWriteModel = applyTypes({ + ...context, + constrainedModel: propertyModel.property, + partOfProperty: propertyModel + }); + if (overWriteModel) { + // eslint-disable-next-line security/detect-object-injection + objectModel.properties[propertyKey].property = overWriteModel; + } + } +} + +function walkDictionaryNode< + GeneratorOptions, + DependencyManager extends AbstractDependencyManager +>(context: ApplyingTypesOptions) { + const dictionaryModel = + context.constrainedModel as ConstrainedDictionaryModel; + + const overwriteKeyModel = applyTypes({ + ...context, + constrainedModel: dictionaryModel.key, + partOfProperty: undefined + }); + if (overwriteKeyModel) { + dictionaryModel.key = overwriteKeyModel; + } + const overWriteValueModel = applyTypes({ + ...context, + constrainedModel: dictionaryModel.value, + partOfProperty: undefined + }); + if (overWriteValueModel) { + dictionaryModel.value = overWriteValueModel; + } +} +function walkTupleNode< + GeneratorOptions, + DependencyManager extends AbstractDependencyManager +>(context: ApplyingTypesOptions) { + const tupleModel = context.constrainedModel as ConstrainedTupleModel; + + for (const [index, tupleMetaModel] of [...tupleModel.tuple].entries()) { + const overwriteTupleModel = applyTypes({ + ...context, + constrainedModel: tupleMetaModel.value, + partOfProperty: undefined + }); + if (overwriteTupleModel) { + // eslint-disable-next-line security/detect-object-injection + tupleModel.tuple[index].value = overwriteTupleModel; + } + } +} + +function walkArrayNode< + GeneratorOptions, + DependencyManager extends AbstractDependencyManager +>(context: ApplyingTypesOptions) { + const arrayModel = context.constrainedModel as ConstrainedArrayModel; + const overWriteArrayModel = applyTypes({ + ...context, + constrainedModel: arrayModel.valueModel, + partOfProperty: undefined, + shouldWalkNode: true + }); + + if (overWriteArrayModel) { + arrayModel.valueModel = overWriteArrayModel; + } +} + +function walkUnionNode< + GeneratorOptions, + DependencyManager extends AbstractDependencyManager +>(context: ApplyingTypesOptions) { + const unionModel = context.constrainedModel as ConstrainedUnionModel; + //If all union value models have type, we can go ahead and get the type for the union as well. + for (const [index, unionValueModel] of [...unionModel.union].entries()) { + const overwriteUnionModel = applyTypes({ + ...context, + constrainedModel: unionValueModel, + partOfProperty: undefined, + shouldWalkNode: true + }); + + if (overwriteUnionModel) { + // eslint-disable-next-line security/detect-object-injection + unionModel.union[index] = overwriteUnionModel; + } + } +} + +function walkReferenceNode< + GeneratorOptions, + DependencyManager extends AbstractDependencyManager +>(context: ApplyingTypesOptions) { + const referenceModel = context.constrainedModel as ConstrainedReferenceModel; + const overwriteReference = applyTypes({ + ...context, + constrainedModel: referenceModel.ref, + partOfProperty: undefined + }); + + if (overwriteReference) { + referenceModel.ref = overwriteReference; + } +} + +function addDiscriminatorTypeToUnionModel( + constrainedModel: ConstrainedUnionModel +) { + if (!constrainedModel.options.discriminator) { + return; + } + + const propertyTypes = new Set(); + + for (const union of constrainedModel.union) { + if (union instanceof ConstrainedReferenceModel) { + const ref = union.ref; + + if (ref instanceof ConstrainedObjectModel) { + const discriminatorProp = Object.values(ref.properties).find( + (model) => + model.unconstrainedPropertyName === + constrainedModel.options.discriminator?.discriminator + ); + + if (discriminatorProp) { + propertyTypes.add(discriminatorProp.property.type); + } + } + } + } + + if (propertyTypes.size === 1) { + constrainedModel.options.discriminator.type = propertyTypes + .keys() + .next().value; + } +} \ No newline at end of file diff --git a/src/helpers/MetaModelIterator.ts b/src/helpers/MetaModelIterator.ts new file mode 100644 index 0000000000..df0417db04 --- /dev/null +++ b/src/helpers/MetaModelIterator.ts @@ -0,0 +1,153 @@ +// import { +// ConstrainedArrayModel, +// ConstrainedDictionaryModel, +// ConstrainedMetaModel, +// ConstrainedObjectModel, +// ConstrainedObjectPropertyModel, +// ConstrainedReferenceModel, +// ConstrainedTupleModel, +// ConstrainedUnionModel +// } from '../models'; + +// export type callbackType = (argument: { +// partOfProperty: ConstrainedObjectPropertyModel; +// constrainedModel: ConstrainedMetaModel; +// parentConstrainedModel: ConstrainedMetaModel; +// context: any; +// }) => { continue: boolean }; + +// function iterateMetaModel({ +// callback, +// constrainedModel, +// context +// }: { +// constrainedModel: ConstrainedMetaModel; +// callback: callbackType; +// context: any; +// }) { +// if (constrainedModel instanceof ConstrainedObjectModel) { +// for (const propertyModel of Object.values({ +// ...constrainedModel.properties +// })) { +// const returnType = callback({ +// constrainedModel: propertyModel.property, +// parentConstrainedModel: constrainedModel, +// partOfProperty: propertyModel, +// context +// }); +// if (returnType.continue) { +// iterateMetaModel({ +// callback, +// constrainedModel: propertyModel.property, +// context +// }); +// } +// } +// } else if (constrainedModel instanceof ConstrainedDictionaryModel) { +// } else if (constrainedModel instanceof ConstrainedTupleModel) { +// walkTupleNode(context); +// } else if (constrainedModel instanceof ConstrainedArrayModel) { +// walkArrayNode(context); +// } else if (constrainedModel instanceof ConstrainedUnionModel) { +// walkUnionNode(context); +// } else if (constrainedModel instanceof ConstrainedReferenceModel) { +// walkReferenceNode(context); +// } +// } + +// function walkDictionaryNode< +// GeneratorOptions, +// DependencyManager extends AbstractDependencyManager +// >(context: ApplyingTypesOptions) { +// const dictionaryModel = +// context.constrainedModel as ConstrainedDictionaryModel; + +// const overwriteKeyModel = applyTypes({ +// ...context, +// constrainedModel: dictionaryModel.key, +// partOfProperty: undefined +// }); +// if (overwriteKeyModel) { +// dictionaryModel.key = overwriteKeyModel; +// } +// const overWriteValueModel = applyTypes({ +// ...context, +// constrainedModel: dictionaryModel.value, +// partOfProperty: undefined +// }); +// if (overWriteValueModel) { +// dictionaryModel.value = overWriteValueModel; +// } +// } +// function walkTupleNode< +// GeneratorOptions, +// DependencyManager extends AbstractDependencyManager +// >(context: ApplyingTypesOptions) { +// const tupleModel = context.constrainedModel as ConstrainedTupleModel; + +// for (const [index, tupleMetaModel] of [...tupleModel.tuple].entries()) { +// const overwriteTupleModel = applyTypes({ +// ...context, +// constrainedModel: tupleMetaModel.value, +// partOfProperty: undefined +// }); +// if (overwriteTupleModel) { +// // eslint-disable-next-line security/detect-object-injection +// tupleModel.tuple[index].value = overwriteTupleModel; +// } +// } +// } + +// function walkArrayNode< +// GeneratorOptions, +// DependencyManager extends AbstractDependencyManager +// >(context: ApplyingTypesOptions) { +// const arrayModel = context.constrainedModel as ConstrainedArrayModel; +// const overWriteArrayModel = applyTypes({ +// ...context, +// constrainedModel: arrayModel.valueModel, +// partOfProperty: undefined, +// shouldWalkNode: true +// }); + +// if (overWriteArrayModel) { +// arrayModel.valueModel = overWriteArrayModel; +// } +// } + +// function walkUnionNode< +// GeneratorOptions, +// DependencyManager extends AbstractDependencyManager +// >(context: ApplyingTypesOptions) { +// const unionModel = context.constrainedModel as ConstrainedUnionModel; +// //If all union value models have type, we can go ahead and get the type for the union as well. +// for (const [index, unionValueModel] of [...unionModel.union].entries()) { +// const overwriteUnionModel = applyTypes({ +// ...context, +// constrainedModel: unionValueModel, +// partOfProperty: undefined, +// shouldWalkNode: true +// }); + +// if (overwriteUnionModel) { +// // eslint-disable-next-line security/detect-object-injection +// unionModel.union[index] = overwriteUnionModel; +// } +// } +// } + +// function walkReferenceNode< +// GeneratorOptions, +// DependencyManager extends AbstractDependencyManager +// >(context: ApplyingTypesOptions) { +// const referenceModel = context.constrainedModel as ConstrainedReferenceModel; +// const overwriteReference = applyTypes({ +// ...context, +// constrainedModel: referenceModel.ref, +// partOfProperty: undefined +// }); + +// if (overwriteReference) { +// referenceModel.ref = overwriteReference; +// } +// } diff --git a/src/helpers/MetaModelToConstrained.ts b/src/helpers/MetaModelToConstrained.ts new file mode 100644 index 0000000000..d90e696399 --- /dev/null +++ b/src/helpers/MetaModelToConstrained.ts @@ -0,0 +1,555 @@ +import { AbstractDependencyManager } from '../generators/AbstractDependencyManager'; +import { + ConstrainedAnyModel, + ConstrainedBooleanModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, + ConstrainedMetaModel, + ConstrainedObjectModel, + ConstrainedReferenceModel, + ConstrainedStringModel, + ConstrainedTupleModel, + ConstrainedArrayModel, + ConstrainedUnionModel, + ConstrainedEnumModel, + ConstrainedDictionaryModel, + ConstrainedEnumValueModel, + ConstrainedObjectPropertyModel, + ConstrainedMetaModelOptions, + ConstrainedTupleValueModel +} from '../models/ConstrainedMetaModel'; +import { + AnyModel, + BooleanModel, + FloatModel, + IntegerModel, + ObjectModel, + ReferenceModel, + StringModel, + TupleModel, + ArrayModel, + UnionModel, + EnumModel, + DictionaryModel, + MetaModel, + ObjectPropertyModel, + EnumValueModel +} from '../models/MetaModel'; + +export type ConstrainContext< + Options, + M extends MetaModel, + DependencyManager extends AbstractDependencyManager +> = { + partOfProperty?: ConstrainedObjectPropertyModel; + metaModel: M; + constrainedName: string; + options: Options; + dependencyManager: DependencyManager; +}; + +export type EnumKeyContext = { + enumKey: string; + constrainedEnumModel: ConstrainedEnumModel; + enumModel: EnumModel; + options: Options; +}; +export type EnumKeyConstraint = ( + context: EnumKeyContext +) => string; + +export type EnumValueContext = { + enumValue: any; + constrainedEnumModel: ConstrainedEnumModel; + enumModel: EnumModel; + options: Options; +}; +export type EnumValueConstraint = ( + context: EnumValueContext +) => any; + +export type ModelNameContext = { + modelName: string; + options: Options; +}; +export type ModelNameConstraint = ( + context: ModelNameContext +) => string; + +export type PropertyKeyContext = { + constrainedObjectPropertyModel: ConstrainedObjectPropertyModel; + objectPropertyModel: ObjectPropertyModel; + constrainedObjectModel: ConstrainedObjectModel; + objectModel: ObjectModel; + options: Options; +}; + +export type PropertyKeyConstraint = ( + context: PropertyKeyContext +) => string; + +export type ConstantContext = { + constrainedMetaModel: ConstrainedMetaModel; + options: Options; +}; + +export type ConstantConstraint = ( + context: ConstantContext +) => unknown; + +export interface Constraints { + enumKey: EnumKeyConstraint; + enumValue: EnumValueConstraint; + modelName: ModelNameConstraint; + propertyKey: PropertyKeyConstraint; + constant: ConstantConstraint; +} + +const placeHolderConstrainedObject = new ConstrainedAnyModel( + '', + undefined, + {}, + '' +); + +function getConstrainedMetaModelOptions( + metaModel: MetaModel +): ConstrainedMetaModelOptions { + const options: ConstrainedMetaModelOptions = {}; + + options.const = metaModel.options.const; + options.isNullable = metaModel.options.isNullable; + options.discriminator = metaModel.options.discriminator; + options.format = metaModel.options.format; + options.isExtended = metaModel.options.isExtended; + + return options; +} + +function referenceModelFactory< + Options, + DependencyManager extends AbstractDependencyManager +>( + constrainRules: Constraints, + context: ConstrainContext, + alreadySeenModels: Map +): ConstrainedReferenceModel { + const constrainedModel = new ConstrainedReferenceModel( + context.constrainedName, + context.metaModel.originalInput, + getConstrainedMetaModelOptions(context.metaModel), + '', + placeHolderConstrainedObject + ); + alreadySeenModels.set(context.metaModel, constrainedModel); + + const constrainedRefModel = metaModelFactory( + constrainRules, + { ...context, metaModel: context.metaModel.ref, partOfProperty: undefined }, + alreadySeenModels + ); + constrainedModel.ref = constrainedRefModel; + + return constrainedModel; +} +function anyModelFactory< + Options, + DependencyManager extends AbstractDependencyManager +>( + context: ConstrainContext +): ConstrainedAnyModel { + return new ConstrainedAnyModel( + context.constrainedName, + context.metaModel.originalInput, + getConstrainedMetaModelOptions(context.metaModel), + '' + ); +} +function floatModelFactory< + Options, + DependencyManager extends AbstractDependencyManager +>( + context: ConstrainContext +): ConstrainedFloatModel { + return new ConstrainedFloatModel( + context.constrainedName, + context.metaModel.originalInput, + getConstrainedMetaModelOptions(context.metaModel), + '' + ); +} +function integerModelFactory< + Options, + DependencyManager extends AbstractDependencyManager +>( + context: ConstrainContext +): ConstrainedIntegerModel { + return new ConstrainedIntegerModel( + context.constrainedName, + context.metaModel.originalInput, + getConstrainedMetaModelOptions(context.metaModel), + '' + ); +} +function stringModelFactory< + Options, + DependencyManager extends AbstractDependencyManager +>( + context: ConstrainContext +): ConstrainedStringModel { + return new ConstrainedStringModel( + context.constrainedName, + context.metaModel.originalInput, + getConstrainedMetaModelOptions(context.metaModel), + '' + ); +} +function booleanModelFactory< + Options, + DependencyManager extends AbstractDependencyManager +>( + context: ConstrainContext +): ConstrainedBooleanModel { + return new ConstrainedBooleanModel( + context.constrainedName, + context.metaModel.originalInput, + getConstrainedMetaModelOptions(context.metaModel), + '' + ); +} +function tupleModelFactory< + Options, + DependencyManager extends AbstractDependencyManager +>( + constrainRules: Constraints, + context: ConstrainContext, + alreadySeenModels: Map +): ConstrainedTupleModel { + const constrainedModel = new ConstrainedTupleModel( + context.constrainedName, + context.metaModel.originalInput, + getConstrainedMetaModelOptions(context.metaModel), + '', + [] + ); + alreadySeenModels.set(context.metaModel, constrainedModel); + + const constrainedTupleModels = context.metaModel.tuple.map((tupleValue) => { + const tupleType = metaModelFactory( + constrainRules, + { ...context, metaModel: tupleValue.value, partOfProperty: undefined }, + alreadySeenModels + ); + return new ConstrainedTupleValueModel(tupleValue.index, tupleType); + }); + constrainedModel.tuple = constrainedTupleModels; + + return constrainedModel; +} +function arrayModelFactory< + Options, + DependencyManager extends AbstractDependencyManager +>( + constrainRules: Constraints, + context: ConstrainContext, + alreadySeenModels: Map +): ConstrainedArrayModel { + const constrainedModel = new ConstrainedArrayModel( + context.constrainedName, + context.metaModel.originalInput, + getConstrainedMetaModelOptions(context.metaModel), + '', + placeHolderConstrainedObject + ); + alreadySeenModels.set(context.metaModel, constrainedModel); + const constrainedValueModel = metaModelFactory( + constrainRules, + { + ...context, + metaModel: context.metaModel.valueModel, + partOfProperty: undefined + }, + alreadySeenModels + ); + constrainedModel.valueModel = constrainedValueModel; + + return constrainedModel; +} + +function unionModelFactory< + Options, + DependencyManager extends AbstractDependencyManager +>( + constrainRules: Constraints, + context: ConstrainContext, + alreadySeenModels: Map +): ConstrainedUnionModel { + const constrainedModel = new ConstrainedUnionModel( + context.constrainedName, + context.metaModel.originalInput, + getConstrainedMetaModelOptions(context.metaModel), + '', + [] + ); + alreadySeenModels.set(context.metaModel, constrainedModel); + + const constrainedUnionModels = context.metaModel.union.map((unionValue) => { + return metaModelFactory( + constrainRules, + { ...context, metaModel: unionValue, partOfProperty: undefined }, + alreadySeenModels + ); + }); + constrainedModel.union = constrainedUnionModels; + + return constrainedModel; +} +function dictionaryModelFactory< + Options, + DependencyManager extends AbstractDependencyManager +>( + constrainRules: Constraints, + context: ConstrainContext, + alreadySeenModels: Map +): ConstrainedDictionaryModel { + const constrainedModel = new ConstrainedDictionaryModel( + context.constrainedName, + context.metaModel.originalInput, + getConstrainedMetaModelOptions(context.metaModel), + '', + placeHolderConstrainedObject, + placeHolderConstrainedObject, + context.metaModel.serializationType + ); + alreadySeenModels.set(context.metaModel, constrainedModel); + + const keyModel = metaModelFactory( + constrainRules, + { ...context, metaModel: context.metaModel.key, partOfProperty: undefined }, + alreadySeenModels + ); + constrainedModel.key = keyModel; + const valueModel = metaModelFactory( + constrainRules, + { + ...context, + metaModel: context.metaModel.value, + partOfProperty: undefined + }, + alreadySeenModels + ); + constrainedModel.value = valueModel; + + return constrainedModel; +} + +function objectModelFactory< + Options, + DependencyManager extends AbstractDependencyManager +>( + constrainRules: Constraints, + context: ConstrainContext, + alreadySeenModels: Map +): ConstrainedObjectModel { + const options = getConstrainedMetaModelOptions(context.metaModel); + + if (context.metaModel.options.extend?.length) { + options.extend = []; + + for (const extend of context.metaModel.options.extend) { + options.extend.push( + metaModelFactory( + constrainRules, + { + ...context, + metaModel: extend, + partOfProperty: undefined + }, + alreadySeenModels + ) + ); + } + } + + const constrainedModel = new ConstrainedObjectModel( + context.constrainedName, + context.metaModel.originalInput, + options, + '', + {} + ); + alreadySeenModels.set(context.metaModel, constrainedModel); + + for (const propertyMetaModel of Object.values(context.metaModel.properties)) { + const constrainedPropertyModel = new ConstrainedObjectPropertyModel( + '', + propertyMetaModel.propertyName, + propertyMetaModel.required, + constrainedModel + ); + const constrainedPropertyName = constrainRules.propertyKey({ + objectPropertyModel: propertyMetaModel, + constrainedObjectPropertyModel: constrainedPropertyModel, + constrainedObjectModel: constrainedModel, + objectModel: context.metaModel, + options: context.options + }); + constrainedPropertyModel.propertyName = constrainedPropertyName; + const constrainedProperty = metaModelFactory( + constrainRules, + { + ...context, + metaModel: propertyMetaModel.property, + partOfProperty: constrainedPropertyModel + }, + alreadySeenModels + ); + constrainedPropertyModel.property = constrainedProperty; + + constrainedModel.properties[String(constrainedPropertyName)] = + constrainedPropertyModel; + } + return constrainedModel; +} + +function enumModelFactory< + Options, + DependencyManager extends AbstractDependencyManager +>( + constrainRules: Constraints, + context: ConstrainContext +): ConstrainedEnumModel { + const constrainedModel = new ConstrainedEnumModel( + context.constrainedName, + context.metaModel.originalInput, + getConstrainedMetaModelOptions(context.metaModel), + '', + [] + ); + + const enumValueToConstrainedEnumValueModel = ( + enumValue: EnumValueModel + ): ConstrainedEnumValueModel => { + const constrainedEnumKey = constrainRules.enumKey({ + enumKey: String(enumValue.key), + enumModel: context.metaModel, + constrainedEnumModel: constrainedModel, + options: context.options + }); + const constrainedEnumValue = constrainRules.enumValue({ + enumValue: enumValue.value, + enumModel: context.metaModel, + constrainedEnumModel: constrainedModel, + options: context.options + }); + return new ConstrainedEnumValueModel( + constrainedEnumKey, + constrainedEnumValue, + enumValue.value + ); + }; + + for (const enumValue of context.metaModel.values) { + constrainedModel.values.push( + enumValueToConstrainedEnumValueModel(enumValue) + ); + } + + return constrainedModel; +} + +// eslint-disable-next-line sonarjs/cognitive-complexity +export function metaModelFactory< + Options, + DependencyManager extends AbstractDependencyManager +>( + constrainRules: Constraints, + context: ConstrainContext, + alreadySeenModels: Map = new Map() +): ConstrainedMetaModel { + if (alreadySeenModels.has(context.metaModel)) { + return alreadySeenModels.get(context.metaModel) as ConstrainedMetaModel; + } + const constrainedName = constrainRules.modelName({ + modelName: context.metaModel.name, + options: context.options + }); + const newContext = { ...context, constrainedName }; + + if (newContext.metaModel instanceof ObjectModel) { + return objectModelFactory( + constrainRules, + { ...newContext, metaModel: newContext.metaModel }, + alreadySeenModels + ); + } else if (newContext.metaModel instanceof ReferenceModel) { + return referenceModelFactory( + constrainRules, + { ...newContext, metaModel: newContext.metaModel }, + alreadySeenModels + ); + } else if (newContext.metaModel instanceof DictionaryModel) { + return dictionaryModelFactory( + constrainRules, + { ...newContext, metaModel: newContext.metaModel }, + alreadySeenModels + ); + } else if (newContext.metaModel instanceof TupleModel) { + return tupleModelFactory( + constrainRules, + { ...newContext, metaModel: newContext.metaModel }, + alreadySeenModels + ); + } else if (newContext.metaModel instanceof ArrayModel) { + return arrayModelFactory( + constrainRules, + { ...newContext, metaModel: newContext.metaModel }, + alreadySeenModels + ); + } else if (newContext.metaModel instanceof UnionModel) { + return unionModelFactory( + constrainRules, + { ...newContext, metaModel: newContext.metaModel }, + alreadySeenModels + ); + } + // Simple models are those who does not have properties that can contain other MetaModels. + let simpleModel: ConstrainedMetaModel | undefined; + if (newContext.metaModel instanceof EnumModel) { + simpleModel = enumModelFactory(constrainRules, { + ...newContext, + metaModel: newContext.metaModel + }); + } else if (newContext.metaModel instanceof BooleanModel) { + simpleModel = booleanModelFactory({ + ...newContext, + metaModel: newContext.metaModel + }); + } else if (newContext.metaModel instanceof AnyModel) { + simpleModel = anyModelFactory({ + ...newContext, + metaModel: newContext.metaModel + }); + } else if (newContext.metaModel instanceof FloatModel) { + simpleModel = floatModelFactory({ + ...newContext, + metaModel: newContext.metaModel + }); + } else if (newContext.metaModel instanceof IntegerModel) { + simpleModel = integerModelFactory({ + ...newContext, + metaModel: newContext.metaModel + }); + } else if (newContext.metaModel instanceof StringModel) { + simpleModel = stringModelFactory({ + ...newContext, + metaModel: newContext.metaModel + }); + } + if (simpleModel !== undefined) { + alreadySeenModels.set(context.metaModel, simpleModel); + return simpleModel; + } + + throw new Error('Could not constrain model'); +} diff --git a/src/processors/JsonSchemaInputProcessor.ts b/src/processors/JsonSchemaInputProcessor.ts index 6f1d8f5f60..6982b7a47b 100644 --- a/src/processors/JsonSchemaInputProcessor.ts +++ b/src/processors/JsonSchemaInputProcessor.ts @@ -224,6 +224,7 @@ export class JsonSchemaInputProcessor extends AbstractInputProcessor { dereference: { circular: true, excludedPathMatcher: (path: string) => { + // References inside examples should not be de-referenced, unless they are a property. return ( path.includes('/examples/') && !path.includes('/properties/examples/') diff --git a/test/generators/cplusplus/__snapshots__/CplusplusGenerator.spec.ts.snap b/test/generators/cplusplus/__snapshots__/CplusplusGenerator.spec.ts.snap index 231763d998..53e8274cc9 100644 --- a/test/generators/cplusplus/__snapshots__/CplusplusGenerator.spec.ts.snap +++ b/test/generators/cplusplus/__snapshots__/CplusplusGenerator.spec.ts.snap @@ -14,7 +14,7 @@ exports[`CplusplusGenerator Class should render \`class\` type 1`] = ` double house_number; std::optional marriage; std::optional> members; - std::vector> array_type; + std::tuple array_type; std::optional>> additional_properties; };" `; diff --git a/test/generators/csharp/__snapshots__/CSharpGenerator.spec.ts.snap b/test/generators/csharp/__snapshots__/CSharpGenerator.spec.ts.snap index ca19fd4373..579d5b2a14 100644 --- a/test/generators/csharp/__snapshots__/CSharpGenerator.spec.ts.snap +++ b/test/generators/csharp/__snapshots__/CSharpGenerator.spec.ts.snap @@ -68,7 +68,7 @@ exports[`CSharpGenerator should render \`class\` type 1`] = ` private double houseNumber; private bool? marriage; private dynamic? members; - private dynamic[]? tupleType; + private (string, double)? tupleType; private string[] arrayType; private Dictionary? additionalProperties; @@ -108,7 +108,7 @@ exports[`CSharpGenerator should render \`class\` type 1`] = ` set { members = value; } } - public dynamic[]? TupleType + public (string, double)? TupleType { get { return tupleType; } set { tupleType = value; } @@ -178,7 +178,7 @@ exports[`CSharpGenerator should render \`record\` type if chosen 1`] = ` public required double HouseNumber { get; init; } public bool? Marriage { get; init; } public dynamic? Members { get; init; } - public dynamic[]? TupleType { get; init; } + public (string, double)? TupleType { get; init; } public required string[] ArrayType { get; init; } public Dictionary? AdditionalProperties { get; init; } }" @@ -238,7 +238,7 @@ exports[`CSharpGenerator should render models and their dependencies 1`] = ` private double houseNumber; private bool? marriage; private dynamic? members; - private dynamic[] arrayType; + private (string, double) arrayType; private OtherModel? otherModel; private Dictionary? additionalProperties; @@ -278,7 +278,7 @@ exports[`CSharpGenerator should render models and their dependencies 1`] = ` set { members = value; } } - public dynamic[] ArrayType + public (string, double) ArrayType { get { return arrayType; } set { arrayType = value; } @@ -335,7 +335,7 @@ exports[`CSharpGenerator should render null-forgiving operator if handleNullable private HouseType houseType; private TerraceType? terraceType; private dynamic? members; - private dynamic[]? tupleType; + private (string, double)? tupleType; private string[] arrayType = null!; private Dictionary? additionalProperties; @@ -387,7 +387,7 @@ exports[`CSharpGenerator should render null-forgiving operator if handleNullable set { members = value; } } - public dynamic[]? TupleType + public (string, double)? TupleType { get { return tupleType; } set { tupleType = value; } diff --git a/test/generators/java/__snapshots__/JavaGenerator.spec.ts.snap b/test/generators/java/__snapshots__/JavaGenerator.spec.ts.snap index f031f078bf..0fb174bed1 100644 --- a/test/generators/java/__snapshots__/JavaGenerator.spec.ts.snap +++ b/test/generators/java/__snapshots__/JavaGenerator.spec.ts.snap @@ -1204,7 +1204,6 @@ exports[`JavaGenerator should not render reserved keyword 1`] = ` exports[`JavaGenerator should render \`class\` type 1`] = ` Array [ - "", "", "", "public class Address { diff --git a/test/generators/kotlin/__snapshots__/KotlinGenerator.spec.ts.snap b/test/generators/kotlin/__snapshots__/KotlinGenerator.spec.ts.snap index 73030d4757..d0e6a16b7d 100644 --- a/test/generators/kotlin/__snapshots__/KotlinGenerator.spec.ts.snap +++ b/test/generators/kotlin/__snapshots__/KotlinGenerator.spec.ts.snap @@ -19,7 +19,7 @@ exports[`KotlinGenerator should render \`data class\` type 1`] = ` val time: java.time.OffsetTime? = null, val dateTime: java.time.OffsetDateTime? = null, val binary: ByteArray? = null, - val additionalProperties: Map? = null, + val additionalProperties: Map<, >? = null, )" `; @@ -55,7 +55,7 @@ exports[`KotlinGenerator should render \`enum\` type (union type) 1`] = ` exports[`KotlinGenerator should render List type for collections 1`] = ` "data class CustomClass( - val arrayType: List? = null, + val arrayType: List<>? = null, )" `; @@ -82,7 +82,7 @@ data class Address( val members: Any? = null, val arrayType: List, val otherModel: OtherModel? = null, - val additionalProperties: Map? = null, + val additionalProperties: Map<, >? = null, )" `; @@ -92,6 +92,6 @@ exports[`KotlinGenerator should render models and their dependencies 2`] = ` data class OtherModel( val streetName: String? = null, - val additionalProperties: Map? = null, + val additionalProperties: Map<, >? = null, )" `; diff --git a/test/generators/kotlin/presets/__snapshots__/ConstraintsPreset.spec.ts.snap b/test/generators/kotlin/presets/__snapshots__/ConstraintsPreset.spec.ts.snap index e47ccb4e5d..955bd36288 100644 --- a/test/generators/kotlin/presets/__snapshots__/ConstraintsPreset.spec.ts.snap +++ b/test/generators/kotlin/presets/__snapshots__/ConstraintsPreset.spec.ts.snap @@ -9,10 +9,10 @@ exports[`KOTLIN_CONSTRAINTS_PRESET should render constraints annotations 1`] = ` @get:Max(99) val maxNumberProp: Double, @get:Size(min=2, max=3) - val arrayProp: List? = null, + val arrayProp: List<>? = null, @get:Pattern(regexp=\\"^I_\\") @get:Size(min=3) val stringProp: String? = null, - val additionalProperties: Map? = null, + val additionalProperties: Map<, >? = null, )" `; diff --git a/test/generators/kotlin/presets/__snapshots__/DescriptionPreset.spec.ts.snap b/test/generators/kotlin/presets/__snapshots__/DescriptionPreset.spec.ts.snap index 22bbeb3e4c..aeeb5b3bb9 100644 --- a/test/generators/kotlin/presets/__snapshots__/DescriptionPreset.spec.ts.snap +++ b/test/generators/kotlin/presets/__snapshots__/DescriptionPreset.spec.ts.snap @@ -12,7 +12,7 @@ exports[`KOTLIN_DESCRIPTION_PRESET should render description and examples for cl */ data class Clazz( val prop: String? = null, - val additionalProperties: Map? = null, + val additionalProperties: Map<, >? = null, )" `; diff --git a/test/generators/php/__snapshots__/PhpGenerator.spec.ts.snap b/test/generators/php/__snapshots__/PhpGenerator.spec.ts.snap index f4b876fa85..160138b024 100644 --- a/test/generators/php/__snapshots__/PhpGenerator.spec.ts.snap +++ b/test/generators/php/__snapshots__/PhpGenerator.spec.ts.snap @@ -24,7 +24,7 @@ exports[`PhpGenerator Class should render \`class\` type 1`] = ` private float $houseNumber; private ?bool $marriage; private mixed $members; - private array $arrayType; + private mixed $arrayType; private mixed $additionalProperties; public function getStreetName(): string { return $this->streetName; } @@ -45,8 +45,30 @@ exports[`PhpGenerator Class should render \`class\` type 1`] = ` public function getMembers(): mixed { return $this->members; } public function setMembers(mixed $members): void { $this->members = $members; } - public function getArrayType(): array { return $this->arrayType; } - public function setArrayType(array $arrayType): void { $this->arrayType = $arrayType; } + public function getArrayType(): mixed { return $this->arrayType; } + public function setArrayType(mixed $arrayType): void { $this->arrayType = $arrayType; } + + public function getAdditionalProperties(): mixed { return $this->additionalProperties; } + public function setAdditionalProperties(mixed $additionalProperties): void { $this->additionalProperties = $additionalProperties; } +} +" +`; + +exports[`PhpGenerator Class should render complete model 1`] = ` +"property; } + public function setProperty(?string $property): void { $this->property = $property; } public function getAdditionalProperties(): mixed { return $this->additionalProperties; } public function setAdditionalProperties(mixed $additionalProperties): void { $this->additionalProperties = $additionalProperties; } @@ -98,28 +120,6 @@ exports[`PhpGenerator Class should work with custom preset for \`class\` type 1` " `; -exports[`PhpGenerator Class should render complete model 1`] = ` -"property; } - public function setProperty(?string $property): void { $this->property = $property; } - - public function getAdditionalProperties(): mixed { return $this->additionalProperties; } - public function setAdditionalProperties(mixed $additionalProperties): void { $this->additionalProperties = $additionalProperties; } -} -" -`; - exports[`PhpGenerator Enum should render \`enum\` with mixed types (union type) 1`] = ` "enum Things { diff --git a/test/generators/python/PythonGenerator.spec.ts b/test/generators/python/PythonGenerator.spec.ts index 649df024b4..f9beca3e79 100644 --- a/test/generators/python/PythonGenerator.spec.ts +++ b/test/generators/python/PythonGenerator.spec.ts @@ -114,24 +114,6 @@ describe('PythonGenerator', () => { allOf: [ { $ref: '#/definitions/json-schema-draft-07-schema' - }, - { - properties: { - items: { - anyOf: [ - { - $ref: '#' - }, - { - type: 'array', - minItems: 1, - items: { - $ref: '#' - } - } - ] - } - } } ], definitions: { @@ -163,12 +145,11 @@ describe('PythonGenerator', () => { } } } - }, - "$id": "root" + } }; const models = await generator.generate(doc); - expect(models).toHaveLength(1); - expect(models[0].result).toMatchSnapshot(); + //expect(models).toHaveLength(3); + expect(models.map((model) => model.result)).toMatchSnapshot(); }); test('should render `class` type', async () => { const doc = { diff --git a/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap b/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap index 545f57d6a4..12a6bb702c 100644 --- a/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap +++ b/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap @@ -4,53 +4,119 @@ exports[`PythonGenerator Class should handle self reference models 1`] = ` "class Address: def __init__(self, input: Dict): if 'self_model' in input: - self._self_model: Address = Address(input['self_model']) + self._self_model: Address.Address = Address.Address(input['self_model']) if 'array_model' in input: - self._array_model: List[Address] = input['array_model'] + self._array_model: List[Address.Address] = input['array_model'] if 'tuple_model' in input: - self._tuple_model: tuple[Address] = input['tuple_model'] + self._tuple_model: tuple[Address.Address] = input['tuple_model'] if 'map_model' in input: - self._map_model: dict[str, Address] = input['map_model'] + self._map_model: dict[str, Address.Address] = input['map_model'] if 'union_model' in input: - self._union_model: Address = input['union_model'] + self._union_model: Address.Address = input['union_model'] @property - def self_model(self) -> 'Address': + def self_model(self) -> 'Address'.Address: return self._self_model @self_model.setter - def self_model(self, self_model: 'Address'): + def self_model(self, self_model: 'Address'.Address): self._self_model = self_model @property - def array_model(self) -> List['Address']: + def array_model(self) -> List['Address'.Address]: return self._array_model @array_model.setter - def array_model(self, array_model: List['Address']): + def array_model(self, array_model: List['Address'.Address]): self._array_model = array_model @property - def tuple_model(self) -> tuple['Address']: + def tuple_model(self) -> tuple['Address'.Address]: return self._tuple_model @tuple_model.setter - def tuple_model(self, tuple_model: tuple['Address']): + def tuple_model(self, tuple_model: tuple['Address'.Address]): self._tuple_model = tuple_model @property - def map_model(self) -> dict[str, 'Address']: + def map_model(self) -> dict[str, 'Address'.Address]: return self._map_model @map_model.setter - def map_model(self, map_model: dict[str, 'Address']): + def map_model(self, map_model: dict[str, 'Address'.Address]): self._map_model = map_model @property - def union_model(self) -> 'Address': + def union_model(self) -> 'Address'.Address: return self._union_model @union_model.setter - def union_model(self, union_model: 'Address'): + def union_model(self, union_model: 'Address'.Address): self._union_model = union_model " `; +exports[`PythonGenerator Class should handle weird self reference models 1`] = ` +Array [ + "", + "class RootObject: + def __init__(self, input: Dict): + if 'additional_items' in input: + self._additional_items: CoreSchemaMetaMinusSchemaObject.CoreSchemaMetaMinusSchemaObject | bool = input['additional_items'] + if 'items' in input: + self._items: Any | List[Any] = input['items'] + if 'additional_properties' in input: + self._additional_properties: dict[str, Any] = input['additional_properties'] + + @property + def additional_items(self) -> CoreSchemaMetaMinusSchemaObject.CoreSchemaMetaMinusSchemaObject | bool: + return self._additional_items + @additional_items.setter + def additional_items(self, additional_items: CoreSchemaMetaMinusSchemaObject.CoreSchemaMetaMinusSchemaObject | bool): + self._additional_items = additional_items + + @property + def items(self) -> Any | List[Any]: + return self._items + @items.setter + def items(self, items: Any | List[Any]): + self._items = items + + @property + def additional_properties(self) -> dict[str, Any]: + return self._additional_properties + @additional_properties.setter + def additional_properties(self, additional_properties: dict[str, Any]): + self._additional_properties = additional_properties +", + "class CoreSchemaMetaMinusSchemaObject: + def __init__(self, input: Dict): + if 'additional_items' in input: + self._additional_items: CoreSchemaMetaMinusSchemaObject.CoreSchemaMetaMinusSchemaObject | bool = input['additional_items'] + if 'items' in input: + self._items: CoreSchemaMetaMinusSchemaObject.CoreSchemaMetaMinusSchemaObject | bool | List[CoreSchemaMetaMinusSchemaObject.CoreSchemaMetaMinusSchemaObject | bool] = input['items'] + if 'additional_properties' in input: + self._additional_properties: dict[str, Any] = input['additional_properties'] + + @property + def additional_items(self) -> CoreSchemaMetaMinusSchemaObject.CoreSchemaMetaMinusSchemaObject | bool: + return self._additional_items + @additional_items.setter + def additional_items(self, additional_items: CoreSchemaMetaMinusSchemaObject.CoreSchemaMetaMinusSchemaObject | bool): + self._additional_items = additional_items + + @property + def items(self) -> CoreSchemaMetaMinusSchemaObject.CoreSchemaMetaMinusSchemaObject | bool | List[CoreSchemaMetaMinusSchemaObject.CoreSchemaMetaMinusSchemaObject | bool]: + return self._items + @items.setter + def items(self, items: CoreSchemaMetaMinusSchemaObject.CoreSchemaMetaMinusSchemaObject | bool | List[CoreSchemaMetaMinusSchemaObject.CoreSchemaMetaMinusSchemaObject | bool]): + self._items = items + + @property + def additional_properties(self) -> dict[str, Any]: + return self._additional_properties + @additional_properties.setter + def additional_properties(self, additional_properties: dict[str, Any]): + self._additional_properties = additional_properties +", +] +`; + exports[`PythonGenerator Class should not render reserved keyword 1`] = ` "class Address: def __init__(self, input: Dict): @@ -77,7 +143,7 @@ exports[`PythonGenerator Class should render \`class\` type 1`] = ` self._marriage: bool = input['marriage'] if 'members' in input: self._members: str | float | bool = input['members'] - self._array_type: List[str | float | Any] = input['array_type'] + self._array_type: tuple[str, float] = input['array_type'] if 'additional_properties' in input: self._additional_properties: dict[str, Any | str] = input['additional_properties'] @@ -124,10 +190,10 @@ exports[`PythonGenerator Class should render \`class\` type 1`] = ` self._members = members @property - def array_type(self) -> List[str | float | Any]: + def array_type(self) -> tuple[str, float]: return self._array_type @array_type.setter - def array_type(self, array_type: List[str | float | Any]): + def array_type(self, array_type: tuple[str, float]): self._array_type = array_type @property diff --git a/test/generators/python/presets/__snapshots__/Pydantic.spec.ts.snap b/test/generators/python/presets/__snapshots__/Pydantic.spec.ts.snap index 54c8db33c8..221e9e0e6e 100644 --- a/test/generators/python/presets/__snapshots__/Pydantic.spec.ts.snap +++ b/test/generators/python/presets/__snapshots__/Pydantic.spec.ts.snap @@ -13,7 +13,7 @@ exports[`PYTHON_PYDANTIC_PRESET should render pydantic for class 1`] = ` exports[`PYTHON_PYDANTIC_PRESET should render union to support Python < 3.10 1`] = ` Array [ "class UnionTest(BaseModel): - union_test: Optional[Union[Union1, Union2]] = Field(default=None, serialization_alias='unionTest') + union_test: Optional[Union[Union1.Union1, Union2.Union2]] = Field(default=None, serialization_alias='unionTest') additional_properties: Optional[dict[str, Any]] = Field(default=None, serialization_alias='additionalProperties', exclude=True) ", "class Union1(BaseModel): diff --git a/test/generators/typescript/__snapshots__/TypeScriptGenerator.spec.ts.snap b/test/generators/typescript/__snapshots__/TypeScriptGenerator.spec.ts.snap index cdacd89e0c..3ca4a13860 100644 --- a/test/generators/typescript/__snapshots__/TypeScriptGenerator.spec.ts.snap +++ b/test/generators/typescript/__snapshots__/TypeScriptGenerator.spec.ts.snap @@ -479,7 +479,7 @@ exports[`TypeScriptGenerator should render \`class\` type 1`] = ` private _marriage?: boolean; private _members?: string | number | boolean; private _tupleType?: [string, number]; - private _tupleTypeWithAdditionalItems?: (string | number | any)[]; + private _tupleTypeWithAdditionalItems?: [string, number]; private _arrayType: string[]; private _additionalProperties?: Map; @@ -491,7 +491,7 @@ exports[`TypeScriptGenerator should render \`class\` type 1`] = ` marriage?: boolean, members?: string | number | boolean, tupleType?: [string, number], - tupleTypeWithAdditionalItems?: (string | number | any)[], + tupleTypeWithAdditionalItems?: [string, number], arrayType: string[], additionalProperties?: Map, }) { @@ -528,8 +528,8 @@ exports[`TypeScriptGenerator should render \`class\` type 1`] = ` get tupleType(): [string, number] | undefined { return this._tupleType; } set tupleType(tupleType: [string, number] | undefined) { this._tupleType = tupleType; } - get tupleTypeWithAdditionalItems(): (string | number | any)[] | undefined { return this._tupleTypeWithAdditionalItems; } - set tupleTypeWithAdditionalItems(tupleTypeWithAdditionalItems: (string | number | any)[] | undefined) { this._tupleTypeWithAdditionalItems = tupleTypeWithAdditionalItems; } + get tupleTypeWithAdditionalItems(): [string, number] | undefined { return this._tupleTypeWithAdditionalItems; } + set tupleTypeWithAdditionalItems(tupleTypeWithAdditionalItems: [string, number] | undefined) { this._tupleTypeWithAdditionalItems = tupleTypeWithAdditionalItems; } get arrayType(): string[] { return this._arrayType; } set arrayType(arrayType: string[]) { this._arrayType = arrayType; } @@ -558,7 +558,7 @@ exports[`TypeScriptGenerator should render \`interface\` type 1`] = ` marriage?: boolean; members?: string | number | boolean; tupleType?: [string, number]; - tupleTypeWithAdditionalItems?: (string | number | any)[]; + tupleTypeWithAdditionalItems?: [string, number]; arrayType: string[]; additionalProperties?: Map; }" @@ -603,7 +603,7 @@ class Address { private _houseNumber: number; private _marriage?: boolean; private _members?: string | number | boolean; - private _arrayType: (string | number | any)[]; + private _arrayType: [string, number]; private _otherModel?: OtherModel; private _additionalProperties?: Map; @@ -614,7 +614,7 @@ class Address { houseNumber: number, marriage?: boolean, members?: string | number | boolean, - arrayType: (string | number | any)[], + arrayType: [string, number], otherModel?: OtherModel, additionalProperties?: Map, }) { @@ -647,8 +647,8 @@ class Address { get members(): string | number | boolean | undefined { return this._members; } set members(members: string | number | boolean | undefined) { this._members = members; } - get arrayType(): (string | number | any)[] { return this._arrayType; } - set arrayType(arrayType: (string | number | any)[]) { this._arrayType = arrayType; } + get arrayType(): [string, number] { return this._arrayType; } + set arrayType(arrayType: [string, number]) { this._arrayType = arrayType; } get otherModel(): OtherModel | undefined { return this._otherModel; } set otherModel(otherModel: OtherModel | undefined) { this._otherModel = otherModel; } @@ -691,7 +691,7 @@ class Address { private _houseNumber: number; private _marriage?: boolean; private _members?: string | number | boolean; - private _arrayType: (string | number | any)[]; + private _arrayType: [string, number]; private _otherModel?: OtherModel; private _additionalProperties?: Map; @@ -702,7 +702,7 @@ class Address { houseNumber: number, marriage?: boolean, members?: string | number | boolean, - arrayType: (string | number | any)[], + arrayType: [string, number], otherModel?: OtherModel, additionalProperties?: Map, }) { @@ -735,8 +735,8 @@ class Address { get members(): string | number | boolean | undefined { return this._members; } set members(members: string | number | boolean | undefined) { this._members = members; } - get arrayType(): (string | number | any)[] { return this._arrayType; } - set arrayType(arrayType: (string | number | any)[]) { this._arrayType = arrayType; } + get arrayType(): [string, number] { return this._arrayType; } + set arrayType(arrayType: [string, number]) { this._arrayType = arrayType; } get otherModel(): OtherModel | undefined { return this._otherModel; } set otherModel(otherModel: OtherModel | undefined) { this._otherModel = otherModel; } @@ -779,7 +779,7 @@ class Address { private _houseNumber: number; private _marriage?: boolean; private _members?: string | number | boolean; - private _arrayType: (string | number | any)[]; + private _arrayType: [string, number]; private _otherModel?: OtherModel; private _additionalProperties?: Map; @@ -790,7 +790,7 @@ class Address { houseNumber: number, marriage?: boolean, members?: string | number | boolean, - arrayType: (string | number | any)[], + arrayType: [string, number], otherModel?: OtherModel, additionalProperties?: Map, }) { @@ -823,8 +823,8 @@ class Address { get members(): string | number | boolean | undefined { return this._members; } set members(members: string | number | boolean | undefined) { this._members = members; } - get arrayType(): (string | number | any)[] { return this._arrayType; } - set arrayType(arrayType: (string | number | any)[]) { this._arrayType = arrayType; } + get arrayType(): [string, number] { return this._arrayType; } + set arrayType(arrayType: [string, number]) { this._arrayType = arrayType; } get otherModel(): OtherModel | undefined { return this._otherModel; } set otherModel(otherModel: OtherModel | undefined) { this._otherModel = otherModel; } @@ -869,7 +869,7 @@ class Address { private _houseNumber: number; private _marriage?: boolean; private _members?: string | number | boolean; - private _arrayType: (string | number | any)[]; + private _arrayType: [string, number]; private _otherModel?: OtherModel; private _additionalProperties?: Map; @@ -880,7 +880,7 @@ class Address { houseNumber: number, marriage?: boolean, members?: string | number | boolean, - arrayType: (string | number | any)[], + arrayType: [string, number], otherModel?: OtherModel, additionalProperties?: Map, }) { @@ -913,8 +913,8 @@ class Address { get members(): string | number | boolean | undefined { return this._members; } set members(members: string | number | boolean | undefined) { this._members = members; } - get arrayType(): (string | number | any)[] { return this._arrayType; } - set arrayType(arrayType: (string | number | any)[]) { this._arrayType = arrayType; } + get arrayType(): [string, number] { return this._arrayType; } + set arrayType(arrayType: [string, number]) { this._arrayType = arrayType; } get otherModel(): OtherModel | undefined { return this._otherModel; } set otherModel(otherModel: OtherModel | undefined) { this._otherModel = otherModel; } diff --git a/test/models/ConstrainedMetaModel.spec.ts b/test/models/ConstrainedMetaModel.spec.ts index 6b49ca4bbb..710344e7c6 100644 --- a/test/models/ConstrainedMetaModel.spec.ts +++ b/test/models/ConstrainedMetaModel.spec.ts @@ -47,7 +47,7 @@ describe('ConstrainedMetaModel', () => { const constrainedTestModel = new ConstrainedTestModel( '', undefined, - metaModel.options, + metaModel.options as any, '' ); From adcc152058daa11408aede97c17b32589f8dc845 Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Mon, 29 Apr 2024 08:37:44 +0200 Subject: [PATCH 09/15] update snaps --- src/generators/kotlin/KotlinGenerator.ts | 4 +- src/helpers/MetaModelIterator.ts | 153 ------------------ .../KotlinGenerator.spec.ts.snap | 8 +- .../ConstraintsPreset.spec.ts.snap | 4 +- .../DescriptionPreset.spec.ts.snap | 2 +- .../PythonGenerator.spec.ts.snap | 55 ++----- .../__snapshots__/Pydantic.spec.ts.snap | 4 +- 7 files changed, 20 insertions(+), 210 deletions(-) delete mode 100644 src/helpers/MetaModelIterator.ts diff --git a/src/generators/kotlin/KotlinGenerator.ts b/src/generators/kotlin/KotlinGenerator.ts index 43665a28be..a3c23565af 100644 --- a/src/generators/kotlin/KotlinGenerator.ts +++ b/src/generators/kotlin/KotlinGenerator.ts @@ -75,10 +75,8 @@ const SAFE_MODEL_TYPES: any[] = [ ConstrainedStringModel, ConstrainedBooleanModel, ConstrainedTupleModel, - ConstrainedArrayModel, ConstrainedEnumModel, - ConstrainedUnionModel, - ConstrainedDictionaryModel + ConstrainedUnionModel ]; export class KotlinGenerator extends AbstractGenerator< diff --git a/src/helpers/MetaModelIterator.ts b/src/helpers/MetaModelIterator.ts deleted file mode 100644 index df0417db04..0000000000 --- a/src/helpers/MetaModelIterator.ts +++ /dev/null @@ -1,153 +0,0 @@ -// import { -// ConstrainedArrayModel, -// ConstrainedDictionaryModel, -// ConstrainedMetaModel, -// ConstrainedObjectModel, -// ConstrainedObjectPropertyModel, -// ConstrainedReferenceModel, -// ConstrainedTupleModel, -// ConstrainedUnionModel -// } from '../models'; - -// export type callbackType = (argument: { -// partOfProperty: ConstrainedObjectPropertyModel; -// constrainedModel: ConstrainedMetaModel; -// parentConstrainedModel: ConstrainedMetaModel; -// context: any; -// }) => { continue: boolean }; - -// function iterateMetaModel({ -// callback, -// constrainedModel, -// context -// }: { -// constrainedModel: ConstrainedMetaModel; -// callback: callbackType; -// context: any; -// }) { -// if (constrainedModel instanceof ConstrainedObjectModel) { -// for (const propertyModel of Object.values({ -// ...constrainedModel.properties -// })) { -// const returnType = callback({ -// constrainedModel: propertyModel.property, -// parentConstrainedModel: constrainedModel, -// partOfProperty: propertyModel, -// context -// }); -// if (returnType.continue) { -// iterateMetaModel({ -// callback, -// constrainedModel: propertyModel.property, -// context -// }); -// } -// } -// } else if (constrainedModel instanceof ConstrainedDictionaryModel) { -// } else if (constrainedModel instanceof ConstrainedTupleModel) { -// walkTupleNode(context); -// } else if (constrainedModel instanceof ConstrainedArrayModel) { -// walkArrayNode(context); -// } else if (constrainedModel instanceof ConstrainedUnionModel) { -// walkUnionNode(context); -// } else if (constrainedModel instanceof ConstrainedReferenceModel) { -// walkReferenceNode(context); -// } -// } - -// function walkDictionaryNode< -// GeneratorOptions, -// DependencyManager extends AbstractDependencyManager -// >(context: ApplyingTypesOptions) { -// const dictionaryModel = -// context.constrainedModel as ConstrainedDictionaryModel; - -// const overwriteKeyModel = applyTypes({ -// ...context, -// constrainedModel: dictionaryModel.key, -// partOfProperty: undefined -// }); -// if (overwriteKeyModel) { -// dictionaryModel.key = overwriteKeyModel; -// } -// const overWriteValueModel = applyTypes({ -// ...context, -// constrainedModel: dictionaryModel.value, -// partOfProperty: undefined -// }); -// if (overWriteValueModel) { -// dictionaryModel.value = overWriteValueModel; -// } -// } -// function walkTupleNode< -// GeneratorOptions, -// DependencyManager extends AbstractDependencyManager -// >(context: ApplyingTypesOptions) { -// const tupleModel = context.constrainedModel as ConstrainedTupleModel; - -// for (const [index, tupleMetaModel] of [...tupleModel.tuple].entries()) { -// const overwriteTupleModel = applyTypes({ -// ...context, -// constrainedModel: tupleMetaModel.value, -// partOfProperty: undefined -// }); -// if (overwriteTupleModel) { -// // eslint-disable-next-line security/detect-object-injection -// tupleModel.tuple[index].value = overwriteTupleModel; -// } -// } -// } - -// function walkArrayNode< -// GeneratorOptions, -// DependencyManager extends AbstractDependencyManager -// >(context: ApplyingTypesOptions) { -// const arrayModel = context.constrainedModel as ConstrainedArrayModel; -// const overWriteArrayModel = applyTypes({ -// ...context, -// constrainedModel: arrayModel.valueModel, -// partOfProperty: undefined, -// shouldWalkNode: true -// }); - -// if (overWriteArrayModel) { -// arrayModel.valueModel = overWriteArrayModel; -// } -// } - -// function walkUnionNode< -// GeneratorOptions, -// DependencyManager extends AbstractDependencyManager -// >(context: ApplyingTypesOptions) { -// const unionModel = context.constrainedModel as ConstrainedUnionModel; -// //If all union value models have type, we can go ahead and get the type for the union as well. -// for (const [index, unionValueModel] of [...unionModel.union].entries()) { -// const overwriteUnionModel = applyTypes({ -// ...context, -// constrainedModel: unionValueModel, -// partOfProperty: undefined, -// shouldWalkNode: true -// }); - -// if (overwriteUnionModel) { -// // eslint-disable-next-line security/detect-object-injection -// unionModel.union[index] = overwriteUnionModel; -// } -// } -// } - -// function walkReferenceNode< -// GeneratorOptions, -// DependencyManager extends AbstractDependencyManager -// >(context: ApplyingTypesOptions) { -// const referenceModel = context.constrainedModel as ConstrainedReferenceModel; -// const overwriteReference = applyTypes({ -// ...context, -// constrainedModel: referenceModel.ref, -// partOfProperty: undefined -// }); - -// if (overwriteReference) { -// referenceModel.ref = overwriteReference; -// } -// } diff --git a/test/generators/kotlin/__snapshots__/KotlinGenerator.spec.ts.snap b/test/generators/kotlin/__snapshots__/KotlinGenerator.spec.ts.snap index d0e6a16b7d..73030d4757 100644 --- a/test/generators/kotlin/__snapshots__/KotlinGenerator.spec.ts.snap +++ b/test/generators/kotlin/__snapshots__/KotlinGenerator.spec.ts.snap @@ -19,7 +19,7 @@ exports[`KotlinGenerator should render \`data class\` type 1`] = ` val time: java.time.OffsetTime? = null, val dateTime: java.time.OffsetDateTime? = null, val binary: ByteArray? = null, - val additionalProperties: Map<, >? = null, + val additionalProperties: Map? = null, )" `; @@ -55,7 +55,7 @@ exports[`KotlinGenerator should render \`enum\` type (union type) 1`] = ` exports[`KotlinGenerator should render List type for collections 1`] = ` "data class CustomClass( - val arrayType: List<>? = null, + val arrayType: List? = null, )" `; @@ -82,7 +82,7 @@ data class Address( val members: Any? = null, val arrayType: List, val otherModel: OtherModel? = null, - val additionalProperties: Map<, >? = null, + val additionalProperties: Map? = null, )" `; @@ -92,6 +92,6 @@ exports[`KotlinGenerator should render models and their dependencies 2`] = ` data class OtherModel( val streetName: String? = null, - val additionalProperties: Map<, >? = null, + val additionalProperties: Map? = null, )" `; diff --git a/test/generators/kotlin/presets/__snapshots__/ConstraintsPreset.spec.ts.snap b/test/generators/kotlin/presets/__snapshots__/ConstraintsPreset.spec.ts.snap index 955bd36288..e47ccb4e5d 100644 --- a/test/generators/kotlin/presets/__snapshots__/ConstraintsPreset.spec.ts.snap +++ b/test/generators/kotlin/presets/__snapshots__/ConstraintsPreset.spec.ts.snap @@ -9,10 +9,10 @@ exports[`KOTLIN_CONSTRAINTS_PRESET should render constraints annotations 1`] = ` @get:Max(99) val maxNumberProp: Double, @get:Size(min=2, max=3) - val arrayProp: List<>? = null, + val arrayProp: List? = null, @get:Pattern(regexp=\\"^I_\\") @get:Size(min=3) val stringProp: String? = null, - val additionalProperties: Map<, >? = null, + val additionalProperties: Map? = null, )" `; diff --git a/test/generators/kotlin/presets/__snapshots__/DescriptionPreset.spec.ts.snap b/test/generators/kotlin/presets/__snapshots__/DescriptionPreset.spec.ts.snap index aeeb5b3bb9..22bbeb3e4c 100644 --- a/test/generators/kotlin/presets/__snapshots__/DescriptionPreset.spec.ts.snap +++ b/test/generators/kotlin/presets/__snapshots__/DescriptionPreset.spec.ts.snap @@ -12,7 +12,7 @@ exports[`KOTLIN_DESCRIPTION_PRESET should render description and examples for cl */ data class Clazz( val prop: String? = null, - val additionalProperties: Map<, >? = null, + val additionalProperties: Map? = null, )" `; diff --git a/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap b/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap index 422db1f7ec..1d71794237 100644 --- a/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap +++ b/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap @@ -4,49 +4,14 @@ exports[`PythonGenerator Class should handle self reference models 1`] = ` "class Address: def __init__(self, input: Dict): if 'self_model' in input: - self._self_model: Address.Address = Address.Address(input['self_model']) + self._self_model: Address = Address(input['self_model']) if 'array_model' in input: - self._array_model: List[Address.Address] = input['array_model'] + self._array_model: List[Address] = input['array_model'] if 'tuple_model' in input: - self._tuple_model: tuple[Address.Address] = input['tuple_mode + self._tuple_model: tuple[Address, str] = input['tuple_model'] if 'map_model' in input: - self._map_model: dict[str, Address.Address] = input['map_model'] + self._map_model: dict[str, Address] = input['map_model'] if 'union_model' in input: - self._union_model: Address.Address = input['union_model'] - - @property - def self_model(self) -> 'Address'.Address: - return self._self_model - @self_model.setter - def self_model(self, self_model: 'Address'.Address): - self._self_model = self_model - - @property - def array_model(self) -> List['Address'.Address]: - return self._array_model - @array_model.setter - def array_model(self, array_model: List['Address'.Address]): - self._array_model = array_model - - @property - def tuple_model(self) -> tuple['Address'.Address]: - return self._tuple_model - @tuple_model.setter - def tuple_model(self, tuple_model: tuple['Address'.Address]): - self._tuple_model = tuple_model - - @property - def map_model(self) -> dict[str, 'Address'.Address]: - return self._map_model - @map_model.setter - def map_model(self, map_model: dict[str, 'Address'.Address]): - self._map_model = map_model - - @property - def union_model(self) -> 'Address'.Address: - return self._union_model - @union_model.setter - def union_model(self, union_model: 'Address'.Address): self._union_model: Address | str = input['union_model'] @property @@ -122,24 +87,24 @@ Array [ "class CoreSchemaMetaMinusSchemaObject: def __init__(self, input: Dict): if 'additional_items' in input: - self._additional_items: CoreSchemaMetaMinusSchemaObject.CoreSchemaMetaMinusSchemaObject | bool = input['additional_items'] + self._additional_items: CoreSchemaMetaMinusSchemaObject | bool = input['additional_items'] if 'items' in input: - self._items: CoreSchemaMetaMinusSchemaObject.CoreSchemaMetaMinusSchemaObject | bool | List[CoreSchemaMetaMinusSchemaObject.CoreSchemaMetaMinusSchemaObject | bool] = input['items'] + self._items: CoreSchemaMetaMinusSchemaObject | bool | List[CoreSchemaMetaMinusSchemaObject | bool] = input['items'] if 'additional_properties' in input: self._additional_properties: dict[str, Any] = input['additional_properties'] @property - def additional_items(self) -> CoreSchemaMetaMinusSchemaObject.CoreSchemaMetaMinusSchemaObject | bool: + def additional_items(self) -> CoreSchemaMetaMinusSchemaObject | bool: return self._additional_items @additional_items.setter - def additional_items(self, additional_items: CoreSchemaMetaMinusSchemaObject.CoreSchemaMetaMinusSchemaObject | bool): + def additional_items(self, additional_items: CoreSchemaMetaMinusSchemaObject | bool): self._additional_items = additional_items @property - def items(self) -> CoreSchemaMetaMinusSchemaObject.CoreSchemaMetaMinusSchemaObject | bool | List[CoreSchemaMetaMinusSchemaObject.CoreSchemaMetaMinusSchemaObject | bool]: + def items(self) -> CoreSchemaMetaMinusSchemaObject | bool | List[CoreSchemaMetaMinusSchemaObject | bool]: return self._items @items.setter - def items(self, items: CoreSchemaMetaMinusSchemaObject.CoreSchemaMetaMinusSchemaObject | bool | List[CoreSchemaMetaMinusSchemaObject.CoreSchemaMetaMinusSchemaObject | bool]): + def items(self, items: CoreSchemaMetaMinusSchemaObject | bool | List[CoreSchemaMetaMinusSchemaObject | bool]): self._items = items @property diff --git a/test/generators/python/presets/__snapshots__/Pydantic.spec.ts.snap b/test/generators/python/presets/__snapshots__/Pydantic.spec.ts.snap index b0624581b7..ece4fb83fc 100644 --- a/test/generators/python/presets/__snapshots__/Pydantic.spec.ts.snap +++ b/test/generators/python/presets/__snapshots__/Pydantic.spec.ts.snap @@ -13,8 +13,8 @@ exports[`PYTHON_PYDANTIC_PRESET should render pydantic for class 1`] = ` exports[`PYTHON_PYDANTIC_PRESET should render union to support Python < 3.10 1`] = ` Array [ "class UnionTest(BaseModel): - union_test: Optional[Union[Union1.Union1, Union2.Union2]] = Field(default=None, serialization_alias='unionTest') - additional_properties: Optional[dict[str, Any]] = Field(default=None, serialization_alias='additionalProperties', exclude=True) + union_test: Optional[Union[Union1.Union1, Union2.Union2]] = Field(default=None, alias='''unionTest''') + additional_properties: Optional[dict[str, Any]] = Field(exclude=True, default=None, alias='''additionalProperties''') ", "class Union1(BaseModel): test_prop1: Optional[str] = Field(default=None, alias='''testProp1''') From 173675799100aba463aebe129398bc173d3c6e07 Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Mon, 29 Apr 2024 08:59:55 +0200 Subject: [PATCH 10/15] update tests --- test/generators/cplusplus/CplusplusGenerator.spec.ts | 11 +++++++++-- .../__snapshots__/CplusplusGenerator.spec.ts.snap | 3 ++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/test/generators/cplusplus/CplusplusGenerator.spec.ts b/test/generators/cplusplus/CplusplusGenerator.spec.ts index b1a3aff1a0..29418d1720 100644 --- a/test/generators/cplusplus/CplusplusGenerator.spec.ts +++ b/test/generators/cplusplus/CplusplusGenerator.spec.ts @@ -84,9 +84,15 @@ describe('CplusplusGenerator', () => { members: { oneOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }] }, - array_type: { + tuple_type: { type: 'array', items: [{ type: 'string' }, { type: 'number' }] + }, + array_type: { + type: 'array', + items: { + oneOf: [{ type: 'string' }, { type: 'number' }] + } } }, patternProperties: { @@ -100,8 +106,9 @@ describe('CplusplusGenerator', () => { '#include ', '#include ', '#include ', - '#include ', + '#include ', '#include ', + '#include ', '#include ' ]; const models = await generator.generate(doc); diff --git a/test/generators/cplusplus/__snapshots__/CplusplusGenerator.spec.ts.snap b/test/generators/cplusplus/__snapshots__/CplusplusGenerator.spec.ts.snap index 53e8274cc9..aa9e2ecf4e 100644 --- a/test/generators/cplusplus/__snapshots__/CplusplusGenerator.spec.ts.snap +++ b/test/generators/cplusplus/__snapshots__/CplusplusGenerator.spec.ts.snap @@ -14,7 +14,8 @@ exports[`CplusplusGenerator Class should render \`class\` type 1`] = ` double house_number; std::optional marriage; std::optional> members; - std::tuple array_type; + std::optional> tuple_type; + std::vector> array_type; std::optional>> additional_properties; };" `; From 16990b4c31be32a68884c4509cb0fba403d45fd1 Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Mon, 29 Apr 2024 09:17:10 +0200 Subject: [PATCH 11/15] update tests --- .../__snapshots__/index.spec.ts.snap | 4 +- examples/file-uri-input/index.spec.ts | 37 ++++++++-------- modelina-cli/package-lock.json | 4 +- src/interpreter/InterpretAdditionalItems.ts | 13 +++--- .../cplusplus/CplusplusGenerator.spec.ts | 3 +- .../CplusplusGenerator.spec.ts.snap | 2 +- .../CSharpGenerator.spec.ts.snap | 14 +++---- .../__snapshots__/JavaGenerator.spec.ts.snap | 1 + .../__snapshots__/PhpGenerator.spec.ts.snap | 6 +-- .../PythonGenerator.spec.ts.snap | 6 +-- .../TypeScriptGenerator.spec.ts.snap | 42 +++++++++---------- test/helpers/ConstrainHelpers.spec.ts | 2 +- 12 files changed, 67 insertions(+), 67 deletions(-) diff --git a/examples/file-uri-input/__snapshots__/index.spec.ts.snap b/examples/file-uri-input/__snapshots__/index.spec.ts.snap index a563fe8ae8..5f4a652298 100644 --- a/examples/file-uri-input/__snapshots__/index.spec.ts.snap +++ b/examples/file-uri-input/__snapshots__/index.spec.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Should be able to generate models using file URI as input to output folder and should log expected output to console 1`] = ` +exports[`Should be able to render models using file URI as input with file generator and should log expected output to console 1`] = ` Array [ "class AnonymousSchema_1 { private _displayName?: string; @@ -29,7 +29,7 @@ Array [ ] `; -exports[`Should be able to render models using file URI as input and should log expected output to console 1`] = ` +exports[`Should be able to render models using file URI as input with regular generator and should log expected output to console 1`] = ` Array [ "class AnonymousSchema_1 { private _displayName?: string; diff --git a/examples/file-uri-input/index.spec.ts b/examples/file-uri-input/index.spec.ts index 428b0cf504..d077776074 100644 --- a/examples/file-uri-input/index.spec.ts +++ b/examples/file-uri-input/index.spec.ts @@ -4,23 +4,26 @@ const spy = jest.spyOn(global.console, 'log').mockImplementation(() => { import { generate, generateToFiles } from './index'; describe('Should be able to render models using file URI as input', () => { - afterAll(() => { - jest.restoreAllMocks(); + describe('with regular generator', () => { + afterAll(() => { + jest.restoreAllMocks(); + }); + test('and should log expected output to console', async () => { + await generate(); + expect(spy.mock.calls.length).toEqual(1); + expect(spy.mock.calls[0]).toMatchSnapshot(); + }); }); - test('and should log expected output to console', async () => { - await generate(); - expect(spy.mock.calls.length).toEqual(1); - expect(spy.mock.calls[0]).toMatchSnapshot(); - }); -}); -describe('Should be able to generate models using file URI as input to output folder', () => { - afterAll(() => { - jest.restoreAllMocks(); - }); - test('and should log expected output to console', async () => { - await generateToFiles(); - expect(spy.mock.calls.length).toEqual(1); - expect(spy.mock.calls[0]).toMatchSnapshot(); + describe('with file generator', () => { + afterAll(() => { + jest.restoreAllMocks(); + }); + test('and should log expected output to console', async () => { + await generateToFiles(); + expect(spy.mock.calls.length).toEqual(1); + expect(spy.mock.calls[0]).toMatchSnapshot(); + }); }); -}); + +}); \ No newline at end of file diff --git a/modelina-cli/package-lock.json b/modelina-cli/package-lock.json index eb0101a834..d456e0cda2 100644 --- a/modelina-cli/package-lock.json +++ b/modelina-cli/package-lock.json @@ -185,9 +185,9 @@ } }, "node_modules/@asyncapi/modelina": { - "version": "4.0.0-next.34", + "version": "4.0.0-next.35", "resolved": "file:scripts/modelina-package/asyncapi-modelina.tgz", - "integrity": "sha512-VCa+z+UR34e2l4LHSR3+OEhcg1eVtpfaz86eRurFIK5ehgZzTMru2v9iC7lhCF9ohWesVNvhkqDJ9NWicbs3UQ==", + "integrity": "sha512-obL4nusJo8o6YseNm1g93dc3K0c+CDIbx6fEKRKIah9X3FGu6omP847om+o/5pMQHCrZd4xjr238QoEC/cudSg==", "license": "Apache-2.0", "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.1.0", diff --git a/src/interpreter/InterpretAdditionalItems.ts b/src/interpreter/InterpretAdditionalItems.ts index dde3bbda5e..712cbf84b1 100644 --- a/src/interpreter/InterpretAdditionalItems.ts +++ b/src/interpreter/InterpretAdditionalItems.ts @@ -19,18 +19,15 @@ export default function interpretAdditionalItems( interpreter: Interpreter, interpreterOptions: InterpreterOptions = Interpreter.defaultInterpreterOptions ): void { - const isTuple = model.items !== undefined && Array.isArray(model.items); - if ( - typeof schema === 'boolean' || - model.type?.includes('array') === false || - !isTuple - ) { + if (typeof schema === 'boolean' || model.type?.includes('array') === false) { return; } const hasArrayTypes = schema.items !== undefined; let defaultAdditionalItems = true; - if (hasArrayTypes && interpreterOptions.ignoreAdditionalItems === true) { - defaultAdditionalItems = false; + if (hasArrayTypes && interpreterOptions.ignoreAdditionalItems !== undefined) { + defaultAdditionalItems = interpreterOptions.ignoreAdditionalItems + ? false + : true; } const additionalItemsModel = interpreter.interpret( diff --git a/test/generators/cplusplus/CplusplusGenerator.spec.ts b/test/generators/cplusplus/CplusplusGenerator.spec.ts index 29418d1720..8cc33aa3bd 100644 --- a/test/generators/cplusplus/CplusplusGenerator.spec.ts +++ b/test/generators/cplusplus/CplusplusGenerator.spec.ts @@ -106,9 +106,8 @@ describe('CplusplusGenerator', () => { '#include ', '#include ', '#include ', - '#include ', - '#include ', '#include ', + '#include ', '#include ' ]; const models = await generator.generate(doc); diff --git a/test/generators/cplusplus/__snapshots__/CplusplusGenerator.spec.ts.snap b/test/generators/cplusplus/__snapshots__/CplusplusGenerator.spec.ts.snap index aa9e2ecf4e..7022e35dd1 100644 --- a/test/generators/cplusplus/__snapshots__/CplusplusGenerator.spec.ts.snap +++ b/test/generators/cplusplus/__snapshots__/CplusplusGenerator.spec.ts.snap @@ -14,7 +14,7 @@ exports[`CplusplusGenerator Class should render \`class\` type 1`] = ` double house_number; std::optional marriage; std::optional> members; - std::optional> tuple_type; + std::optional>> tuple_type; std::vector> array_type; std::optional>> additional_properties; };" diff --git a/test/generators/csharp/__snapshots__/CSharpGenerator.spec.ts.snap b/test/generators/csharp/__snapshots__/CSharpGenerator.spec.ts.snap index 5f646a886d..0485cde0a3 100644 --- a/test/generators/csharp/__snapshots__/CSharpGenerator.spec.ts.snap +++ b/test/generators/csharp/__snapshots__/CSharpGenerator.spec.ts.snap @@ -68,7 +68,7 @@ exports[`CSharpGenerator should render \`class\` type 1`] = ` private double houseNumber; private bool? marriage; private dynamic? members; - private (string, double)? tupleType; + private dynamic[]? tupleType; private string[] arrayType; private Dictionary? additionalProperties; @@ -108,7 +108,7 @@ exports[`CSharpGenerator should render \`class\` type 1`] = ` set { this.members = value; } } - public (string, double)? TupleType + public dynamic[]? TupleType { get { return tupleType; } set { this.tupleType = value; } @@ -178,7 +178,7 @@ exports[`CSharpGenerator should render \`record\` type if chosen 1`] = ` public required double HouseNumber { get; init; } public bool? Marriage { get; init; } public dynamic? Members { get; init; } - public (string, double)? TupleType { get; init; } + public dynamic[]? TupleType { get; init; } public required string[] ArrayType { get; init; } public Dictionary? AdditionalProperties { get; init; } }" @@ -238,7 +238,7 @@ exports[`CSharpGenerator should render models and their dependencies 1`] = ` private double houseNumber; private bool? marriage; private dynamic? members; - private (string, double) arrayType; + private dynamic[] arrayType; private OtherModel? otherModel; private Dictionary? additionalProperties; @@ -278,7 +278,7 @@ exports[`CSharpGenerator should render models and their dependencies 1`] = ` set { this.members = value; } } - public (string, double) ArrayType + public dynamic[] ArrayType { get { return arrayType; } set { this.arrayType = value; } @@ -335,7 +335,7 @@ exports[`CSharpGenerator should render null-forgiving operator if handleNullable private HouseType houseType; private TerraceType? terraceType; private dynamic? members; - private (string, double)? tupleType; + private dynamic[]? tupleType; private string[] arrayType = null!; private Dictionary? additionalProperties; @@ -387,7 +387,7 @@ exports[`CSharpGenerator should render null-forgiving operator if handleNullable set { this.members = value; } } - public (string, double)? TupleType + public dynamic[]? TupleType { get { return tupleType; } set { this.tupleType = value; } diff --git a/test/generators/java/__snapshots__/JavaGenerator.spec.ts.snap b/test/generators/java/__snapshots__/JavaGenerator.spec.ts.snap index 0fb174bed1..f031f078bf 100644 --- a/test/generators/java/__snapshots__/JavaGenerator.spec.ts.snap +++ b/test/generators/java/__snapshots__/JavaGenerator.spec.ts.snap @@ -1204,6 +1204,7 @@ exports[`JavaGenerator should not render reserved keyword 1`] = ` exports[`JavaGenerator should render \`class\` type 1`] = ` Array [ + "", "", "", "public class Address { diff --git a/test/generators/php/__snapshots__/PhpGenerator.spec.ts.snap b/test/generators/php/__snapshots__/PhpGenerator.spec.ts.snap index 160138b024..43cfb72677 100644 --- a/test/generators/php/__snapshots__/PhpGenerator.spec.ts.snap +++ b/test/generators/php/__snapshots__/PhpGenerator.spec.ts.snap @@ -24,7 +24,7 @@ exports[`PhpGenerator Class should render \`class\` type 1`] = ` private float $houseNumber; private ?bool $marriage; private mixed $members; - private mixed $arrayType; + private array $arrayType; private mixed $additionalProperties; public function getStreetName(): string { return $this->streetName; } @@ -45,8 +45,8 @@ exports[`PhpGenerator Class should render \`class\` type 1`] = ` public function getMembers(): mixed { return $this->members; } public function setMembers(mixed $members): void { $this->members = $members; } - public function getArrayType(): mixed { return $this->arrayType; } - public function setArrayType(mixed $arrayType): void { $this->arrayType = $arrayType; } + public function getArrayType(): array { return $this->arrayType; } + public function setArrayType(array $arrayType): void { $this->arrayType = $arrayType; } public function getAdditionalProperties(): mixed { return $this->additionalProperties; } public function setAdditionalProperties(mixed $additionalProperties): void { $this->additionalProperties = $additionalProperties; } diff --git a/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap b/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap index 1d71794237..cef720df59 100644 --- a/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap +++ b/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap @@ -143,7 +143,7 @@ exports[`PythonGenerator Class should render \`class\` type 1`] = ` self._marriage: bool = input['marriage'] if 'members' in input: self._members: str | float | bool = input['members'] - self._array_type: tuple[str, float] = input['array_type'] + self._array_type: List[str | float | Any] = input['array_type'] if 'additional_properties' in input: self._additional_properties: dict[str, Any | str] = input['additional_properties'] @@ -190,10 +190,10 @@ exports[`PythonGenerator Class should render \`class\` type 1`] = ` self._members = members @property - def array_type(self) -> tuple[str, float]: + def array_type(self) -> List[str | float | Any]: return self._array_type @array_type.setter - def array_type(self, array_type: tuple[str, float]): + def array_type(self, array_type: List[str | float | Any]): self._array_type = array_type @property diff --git a/test/generators/typescript/__snapshots__/TypeScriptGenerator.spec.ts.snap b/test/generators/typescript/__snapshots__/TypeScriptGenerator.spec.ts.snap index 3ca4a13860..cdacd89e0c 100644 --- a/test/generators/typescript/__snapshots__/TypeScriptGenerator.spec.ts.snap +++ b/test/generators/typescript/__snapshots__/TypeScriptGenerator.spec.ts.snap @@ -479,7 +479,7 @@ exports[`TypeScriptGenerator should render \`class\` type 1`] = ` private _marriage?: boolean; private _members?: string | number | boolean; private _tupleType?: [string, number]; - private _tupleTypeWithAdditionalItems?: [string, number]; + private _tupleTypeWithAdditionalItems?: (string | number | any)[]; private _arrayType: string[]; private _additionalProperties?: Map; @@ -491,7 +491,7 @@ exports[`TypeScriptGenerator should render \`class\` type 1`] = ` marriage?: boolean, members?: string | number | boolean, tupleType?: [string, number], - tupleTypeWithAdditionalItems?: [string, number], + tupleTypeWithAdditionalItems?: (string | number | any)[], arrayType: string[], additionalProperties?: Map, }) { @@ -528,8 +528,8 @@ exports[`TypeScriptGenerator should render \`class\` type 1`] = ` get tupleType(): [string, number] | undefined { return this._tupleType; } set tupleType(tupleType: [string, number] | undefined) { this._tupleType = tupleType; } - get tupleTypeWithAdditionalItems(): [string, number] | undefined { return this._tupleTypeWithAdditionalItems; } - set tupleTypeWithAdditionalItems(tupleTypeWithAdditionalItems: [string, number] | undefined) { this._tupleTypeWithAdditionalItems = tupleTypeWithAdditionalItems; } + get tupleTypeWithAdditionalItems(): (string | number | any)[] | undefined { return this._tupleTypeWithAdditionalItems; } + set tupleTypeWithAdditionalItems(tupleTypeWithAdditionalItems: (string | number | any)[] | undefined) { this._tupleTypeWithAdditionalItems = tupleTypeWithAdditionalItems; } get arrayType(): string[] { return this._arrayType; } set arrayType(arrayType: string[]) { this._arrayType = arrayType; } @@ -558,7 +558,7 @@ exports[`TypeScriptGenerator should render \`interface\` type 1`] = ` marriage?: boolean; members?: string | number | boolean; tupleType?: [string, number]; - tupleTypeWithAdditionalItems?: [string, number]; + tupleTypeWithAdditionalItems?: (string | number | any)[]; arrayType: string[]; additionalProperties?: Map; }" @@ -603,7 +603,7 @@ class Address { private _houseNumber: number; private _marriage?: boolean; private _members?: string | number | boolean; - private _arrayType: [string, number]; + private _arrayType: (string | number | any)[]; private _otherModel?: OtherModel; private _additionalProperties?: Map; @@ -614,7 +614,7 @@ class Address { houseNumber: number, marriage?: boolean, members?: string | number | boolean, - arrayType: [string, number], + arrayType: (string | number | any)[], otherModel?: OtherModel, additionalProperties?: Map, }) { @@ -647,8 +647,8 @@ class Address { get members(): string | number | boolean | undefined { return this._members; } set members(members: string | number | boolean | undefined) { this._members = members; } - get arrayType(): [string, number] { return this._arrayType; } - set arrayType(arrayType: [string, number]) { this._arrayType = arrayType; } + get arrayType(): (string | number | any)[] { return this._arrayType; } + set arrayType(arrayType: (string | number | any)[]) { this._arrayType = arrayType; } get otherModel(): OtherModel | undefined { return this._otherModel; } set otherModel(otherModel: OtherModel | undefined) { this._otherModel = otherModel; } @@ -691,7 +691,7 @@ class Address { private _houseNumber: number; private _marriage?: boolean; private _members?: string | number | boolean; - private _arrayType: [string, number]; + private _arrayType: (string | number | any)[]; private _otherModel?: OtherModel; private _additionalProperties?: Map; @@ -702,7 +702,7 @@ class Address { houseNumber: number, marriage?: boolean, members?: string | number | boolean, - arrayType: [string, number], + arrayType: (string | number | any)[], otherModel?: OtherModel, additionalProperties?: Map, }) { @@ -735,8 +735,8 @@ class Address { get members(): string | number | boolean | undefined { return this._members; } set members(members: string | number | boolean | undefined) { this._members = members; } - get arrayType(): [string, number] { return this._arrayType; } - set arrayType(arrayType: [string, number]) { this._arrayType = arrayType; } + get arrayType(): (string | number | any)[] { return this._arrayType; } + set arrayType(arrayType: (string | number | any)[]) { this._arrayType = arrayType; } get otherModel(): OtherModel | undefined { return this._otherModel; } set otherModel(otherModel: OtherModel | undefined) { this._otherModel = otherModel; } @@ -779,7 +779,7 @@ class Address { private _houseNumber: number; private _marriage?: boolean; private _members?: string | number | boolean; - private _arrayType: [string, number]; + private _arrayType: (string | number | any)[]; private _otherModel?: OtherModel; private _additionalProperties?: Map; @@ -790,7 +790,7 @@ class Address { houseNumber: number, marriage?: boolean, members?: string | number | boolean, - arrayType: [string, number], + arrayType: (string | number | any)[], otherModel?: OtherModel, additionalProperties?: Map, }) { @@ -823,8 +823,8 @@ class Address { get members(): string | number | boolean | undefined { return this._members; } set members(members: string | number | boolean | undefined) { this._members = members; } - get arrayType(): [string, number] { return this._arrayType; } - set arrayType(arrayType: [string, number]) { this._arrayType = arrayType; } + get arrayType(): (string | number | any)[] { return this._arrayType; } + set arrayType(arrayType: (string | number | any)[]) { this._arrayType = arrayType; } get otherModel(): OtherModel | undefined { return this._otherModel; } set otherModel(otherModel: OtherModel | undefined) { this._otherModel = otherModel; } @@ -869,7 +869,7 @@ class Address { private _houseNumber: number; private _marriage?: boolean; private _members?: string | number | boolean; - private _arrayType: [string, number]; + private _arrayType: (string | number | any)[]; private _otherModel?: OtherModel; private _additionalProperties?: Map; @@ -880,7 +880,7 @@ class Address { houseNumber: number, marriage?: boolean, members?: string | number | boolean, - arrayType: [string, number], + arrayType: (string | number | any)[], otherModel?: OtherModel, additionalProperties?: Map, }) { @@ -913,8 +913,8 @@ class Address { get members(): string | number | boolean | undefined { return this._members; } set members(members: string | number | boolean | undefined) { this._members = members; } - get arrayType(): [string, number] { return this._arrayType; } - set arrayType(arrayType: [string, number]) { this._arrayType = arrayType; } + get arrayType(): (string | number | any)[] { return this._arrayType; } + set arrayType(arrayType: (string | number | any)[]) { this._arrayType = arrayType; } get otherModel(): OtherModel | undefined { return this._otherModel; } set otherModel(otherModel: OtherModel | undefined) { this._otherModel = otherModel; } diff --git a/test/helpers/ConstrainHelpers.spec.ts b/test/helpers/ConstrainHelpers.spec.ts index 8e64b34f83..c8fb7b0172 100644 --- a/test/helpers/ConstrainHelpers.spec.ts +++ b/test/helpers/ConstrainHelpers.spec.ts @@ -146,7 +146,7 @@ describe('ConstrainHelpers', () => { constrainedModel.options.extend?.at(0) instanceof ConstrainedObjectModel ).toEqual(true); expect(mockedConstraints.modelName).toHaveBeenCalledTimes(2); - expect(mockedTypeMapping.Object).toHaveBeenCalledTimes(2); + expect(mockedTypeMapping.Object).toHaveBeenCalledTimes(1); }); }); describe('constrain ReferenceModel', () => { From 7f7d03a75d4423d92211a2d767a6ece6a4936819 Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Mon, 29 Apr 2024 10:10:02 +0200 Subject: [PATCH 12/15] update implementation --- examples/file-uri-input/index.spec.ts | 3 +- src/generators/kotlin/KotlinGenerator.ts | 2 - src/helpers/ConstrainHelpers.ts | 4 +- src/helpers/ConstrainedTypes.ts | 127 +++++++++++------------ 4 files changed, 66 insertions(+), 70 deletions(-) diff --git a/examples/file-uri-input/index.spec.ts b/examples/file-uri-input/index.spec.ts index d077776074..1190c6ee0e 100644 --- a/examples/file-uri-input/index.spec.ts +++ b/examples/file-uri-input/index.spec.ts @@ -25,5 +25,4 @@ describe('Should be able to render models using file URI as input', () => { expect(spy.mock.calls[0]).toMatchSnapshot(); }); }); - -}); \ No newline at end of file +}); diff --git a/src/generators/kotlin/KotlinGenerator.ts b/src/generators/kotlin/KotlinGenerator.ts index a3c23565af..1dc703353c 100644 --- a/src/generators/kotlin/KotlinGenerator.ts +++ b/src/generators/kotlin/KotlinGenerator.ts @@ -7,9 +7,7 @@ import { } from '../AbstractGenerator'; import { ConstrainedAnyModel, - ConstrainedArrayModel, ConstrainedBooleanModel, - ConstrainedDictionaryModel, ConstrainedEnumModel, ConstrainedFloatModel, ConstrainedIntegerModel, diff --git a/src/helpers/ConstrainHelpers.ts b/src/helpers/ConstrainHelpers.ts index e43b9ee645..adb310dedb 100644 --- a/src/helpers/ConstrainHelpers.ts +++ b/src/helpers/ConstrainHelpers.ts @@ -21,7 +21,7 @@ import { MetaModel, ObjectPropertyModel } from '../models/MetaModel'; -import { applyTypes } from './ConstrainedTypes'; +import { applyTypesAndConst } from './ConstrainedTypes'; import { metaModelFactory } from './MetaModelToConstrained'; import { TypeMapping } from './TypeHelpers'; @@ -118,7 +118,7 @@ export function constrainMetaModel< ] ): ConstrainedMetaModel { const constrainedModel = metaModelFactory(constrainRules, context, new Map()); - applyTypes({ + applyTypesAndConst({ constrainedModel, generatorOptions: context.options, typeMapping, diff --git a/src/helpers/ConstrainedTypes.ts b/src/helpers/ConstrainedTypes.ts index d0fd53ad8f..2709d264a9 100644 --- a/src/helpers/ConstrainedTypes.ts +++ b/src/helpers/ConstrainedTypes.ts @@ -1,3 +1,4 @@ +import { Logger } from 'utils'; import { AbstractDependencyManager } from '../generators/AbstractDependencyManager'; import { ConstrainedAnyModel, @@ -20,7 +21,7 @@ export interface ApplyingTypesOptions< > { /** * Constrained Model types that are safe to constrain the type for. - * This varies per language as they are split out differently from each other. + * This varies per language as they are split out differently from each other, and which types depend on the types of others. * * A safe type, means that it does not depend on other meta models to determine it's type. */ @@ -31,20 +32,25 @@ export interface ApplyingTypesOptions< generatorOptions: GeneratorOptions; partOfProperty?: ConstrainedObjectPropertyModel; dependencyManager: DependencyManager; - shouldWalkNode?: boolean; constrainRules: Constraints; } /** - * Applying types through cyclic analysis (https://en.wikipedia.org/wiki/Cycle_(graph_theory)) - * to detect and adapt unmanageable models that are cyclic where needed. + * Applying types and const through cyclic analysis (https://en.wikipedia.org/wiki/Cycle_(graph_theory)) + * to detect and adapt unmanageable cyclic models where we cant determine types as normal. * * For example; * Model a: Union model with Model b * Model b: Union model with Model a - * In such case we are unable to render the type, cause + * In such case we are unable to render the type, cause each depend on each other to determine the type. + * + * The algorithm currently adapts the models so we end up with; + * Model a: Union model with Model b + * Model b: Union model with any model + * + * Additionally (regretfully, but for now) we also apply `constant` values here, because they depend on types in most cases. */ -export function applyTypes< +export function applyTypesAndConst< GeneratorOptions, DependencyManager extends AbstractDependencyManager >( @@ -58,75 +64,70 @@ export function applyTypes< dependencyManager, generatorOptions, alreadySeenModels, - shouldWalkNode = true, constrainRules } = context; const isCyclicModel = alreadySeenModels.has(constrainedModel) && alreadySeenModels.get(constrainedModel) === undefined; const hasBeenSolved = alreadySeenModels.has(constrainedModel); - - if (isCyclicModel) { - //Cyclic models detected, having to make the edge (right before cyclic occur) to use any meta model (most open type) - //With the same information as the node we walk to - const anyModel = new ConstrainedAnyModel( - constrainedModel.name, - constrainedModel.originalInput, - constrainedModel.options, - '' - ); - anyModel.type = getTypeFromMapping(typeMapping, { - constrainedModel: anyModel, + const applyTypeAndConst = (model: ConstrainedMetaModel) => { + model.type = getTypeFromMapping(typeMapping, { + constrainedModel: model, options: generatorOptions, partOfProperty, dependencyManager }); - if (anyModel.options.const) { + if (model.options.const) { const constrainedConstant = constrainRules.constant({ - constrainedMetaModel: anyModel, + constrainedMetaModel: model, options: generatorOptions }); - anyModel.options.const.value = constrainedConstant; + model.options.const.value = constrainedConstant; } + alreadySeenModels.set(model, model.type); + }; - alreadySeenModels.set(anyModel, anyModel.type); + if (isCyclicModel) { + //Cyclic models detected, having to make the edge (right before cyclic occur) to use AnyModel (most open type we have) + //With the same information as the node we are currently on. + //This is to open up the cycle so we can finish determining types. + Logger.warn( + `Cyclic models detected, we have to replace ${JSON.stringify( + constrainedModel + )} with AnyModel...` + ); + const anyModel = new ConstrainedAnyModel( + constrainedModel.name, + constrainedModel.originalInput, + constrainedModel.options, + '' + ); + applyTypeAndConst(anyModel); return anyModel; } else if (hasBeenSolved) { return undefined; } - //Mark the model as having been walked but does not have a type yet + //Mark the model as having been walked but has not been given a type yet alreadySeenModels.set(constrainedModel, undefined); //Walk over all safe models that can determine it's type right away for (const safeType of safeTypes) { if (constrainedModel instanceof safeType) { - constrainedModel.type = getTypeFromMapping(typeMapping, { - constrainedModel, - options: generatorOptions, - partOfProperty, - dependencyManager - }); - - if (constrainedModel.options.const) { - const constrainedConstant = constrainRules.constant({ - constrainedMetaModel: constrainedModel, - options: generatorOptions - }); - constrainedModel.options.const.value = constrainedConstant; - } - alreadySeenModels.set(constrainedModel, constrainedModel.type); + applyTypeAndConst(constrainedModel); break; } } - if (shouldWalkNode) { - walkNode(context); - } + + //Walk over all nested models + walkNode(context); } /** - * A node is a model that can contain other models and is not a safe type to constrain i.e. a meta model that is not split out. + * A node is a model that can contain other models. + * + * This function walks over all of them, and if cyclic models are detected open up it up. */ function walkNode< GeneratorOptions, @@ -157,20 +158,20 @@ function walkNode< context.alreadySeenModels.set(constrainedModel, constrainedModel.type); } - if (constrainedModel instanceof ConstrainedReferenceModel) { - if (constrainedModel.options.const) { - const constrainedConstant = constrainRules.constant({ - constrainedMetaModel: constrainedModel, - options: generatorOptions - }); - constrainedModel.options.const.value = constrainedConstant; - } + if ( + constrainedModel instanceof ConstrainedReferenceModel && + constrainedModel.options.const + ) { + const constrainedConstant = constrainRules.constant({ + constrainedMetaModel: constrainedModel, + options: generatorOptions + }); + constrainedModel.options.const.value = constrainedConstant; } if (constrainedModel instanceof ConstrainedUnionModel) { addDiscriminatorTypeToUnionModel(constrainedModel); } - } function walkObjectNode< @@ -182,7 +183,7 @@ function walkObjectNode< for (const [propertyKey, propertyModel] of Object.entries({ ...objectModel.properties })) { - const overWriteModel = applyTypes({ + const overWriteModel = applyTypesAndConst({ ...context, constrainedModel: propertyModel.property, partOfProperty: propertyModel @@ -201,7 +202,7 @@ function walkDictionaryNode< const dictionaryModel = context.constrainedModel as ConstrainedDictionaryModel; - const overwriteKeyModel = applyTypes({ + const overwriteKeyModel = applyTypesAndConst({ ...context, constrainedModel: dictionaryModel.key, partOfProperty: undefined @@ -209,7 +210,7 @@ function walkDictionaryNode< if (overwriteKeyModel) { dictionaryModel.key = overwriteKeyModel; } - const overWriteValueModel = applyTypes({ + const overWriteValueModel = applyTypesAndConst({ ...context, constrainedModel: dictionaryModel.value, partOfProperty: undefined @@ -225,7 +226,7 @@ function walkTupleNode< const tupleModel = context.constrainedModel as ConstrainedTupleModel; for (const [index, tupleMetaModel] of [...tupleModel.tuple].entries()) { - const overwriteTupleModel = applyTypes({ + const overwriteTupleModel = applyTypesAndConst({ ...context, constrainedModel: tupleMetaModel.value, partOfProperty: undefined @@ -242,11 +243,10 @@ function walkArrayNode< DependencyManager extends AbstractDependencyManager >(context: ApplyingTypesOptions) { const arrayModel = context.constrainedModel as ConstrainedArrayModel; - const overWriteArrayModel = applyTypes({ + const overWriteArrayModel = applyTypesAndConst({ ...context, constrainedModel: arrayModel.valueModel, - partOfProperty: undefined, - shouldWalkNode: true + partOfProperty: undefined }); if (overWriteArrayModel) { @@ -261,11 +261,10 @@ function walkUnionNode< const unionModel = context.constrainedModel as ConstrainedUnionModel; //If all union value models have type, we can go ahead and get the type for the union as well. for (const [index, unionValueModel] of [...unionModel.union].entries()) { - const overwriteUnionModel = applyTypes({ + const overwriteUnionModel = applyTypesAndConst({ ...context, constrainedModel: unionValueModel, - partOfProperty: undefined, - shouldWalkNode: true + partOfProperty: undefined }); if (overwriteUnionModel) { @@ -280,7 +279,7 @@ function walkReferenceNode< DependencyManager extends AbstractDependencyManager >(context: ApplyingTypesOptions) { const referenceModel = context.constrainedModel as ConstrainedReferenceModel; - const overwriteReference = applyTypes({ + const overwriteReference = applyTypesAndConst({ ...context, constrainedModel: referenceModel.ref, partOfProperty: undefined @@ -323,4 +322,4 @@ function addDiscriminatorTypeToUnionModel( .keys() .next().value; } -} \ No newline at end of file +} From 1829b619b0831e6f8066b1da64f437aa334054e7 Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Mon, 29 Apr 2024 10:21:20 +0200 Subject: [PATCH 13/15] update snapshot --- src/helpers/ConstrainedTypes.ts | 2 +- test/generators/cplusplus/CplusplusGenerator.spec.ts | 8 +------- .../__snapshots__/CplusplusGenerator.spec.ts.snap | 3 +-- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/helpers/ConstrainedTypes.ts b/src/helpers/ConstrainedTypes.ts index 2709d264a9..c2d204185d 100644 --- a/src/helpers/ConstrainedTypes.ts +++ b/src/helpers/ConstrainedTypes.ts @@ -1,4 +1,4 @@ -import { Logger } from 'utils'; +import { Logger } from '../utils'; import { AbstractDependencyManager } from '../generators/AbstractDependencyManager'; import { ConstrainedAnyModel, diff --git a/test/generators/cplusplus/CplusplusGenerator.spec.ts b/test/generators/cplusplus/CplusplusGenerator.spec.ts index 8cc33aa3bd..b1a3aff1a0 100644 --- a/test/generators/cplusplus/CplusplusGenerator.spec.ts +++ b/test/generators/cplusplus/CplusplusGenerator.spec.ts @@ -84,15 +84,9 @@ describe('CplusplusGenerator', () => { members: { oneOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }] }, - tuple_type: { - type: 'array', - items: [{ type: 'string' }, { type: 'number' }] - }, array_type: { type: 'array', - items: { - oneOf: [{ type: 'string' }, { type: 'number' }] - } + items: [{ type: 'string' }, { type: 'number' }] } }, patternProperties: { diff --git a/test/generators/cplusplus/__snapshots__/CplusplusGenerator.spec.ts.snap b/test/generators/cplusplus/__snapshots__/CplusplusGenerator.spec.ts.snap index 7022e35dd1..231763d998 100644 --- a/test/generators/cplusplus/__snapshots__/CplusplusGenerator.spec.ts.snap +++ b/test/generators/cplusplus/__snapshots__/CplusplusGenerator.spec.ts.snap @@ -14,8 +14,7 @@ exports[`CplusplusGenerator Class should render \`class\` type 1`] = ` double house_number; std::optional marriage; std::optional> members; - std::optional>> tuple_type; - std::vector> array_type; + std::vector> array_type; std::optional>> additional_properties; };" `; From 7f3cf53d9a7e196ce8524180d08786c24c798e74 Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Mon, 29 Apr 2024 12:23:25 +0200 Subject: [PATCH 14/15] update test --- src/helpers/ConstrainedTypes.ts | 6 +- test/TestUtils/TestConstrainer.ts | 26 ++++---- .../generators/python/PythonGenerator.spec.ts | 42 ------------ .../PythonGenerator.spec.ts.snap | 66 ------------------- test/helpers/ConstrainHelpers.spec.ts | 46 ++++++++++++- 5 files changed, 59 insertions(+), 127 deletions(-) diff --git a/src/helpers/ConstrainedTypes.ts b/src/helpers/ConstrainedTypes.ts index c2d204185d..7be6362838 100644 --- a/src/helpers/ConstrainedTypes.ts +++ b/src/helpers/ConstrainedTypes.ts @@ -48,7 +48,7 @@ export interface ApplyingTypesOptions< * Model a: Union model with Model b * Model b: Union model with any model * - * Additionally (regretfully, but for now) we also apply `constant` values here, because they depend on types in most cases. + * Additionally (regretfully, but for now) we also constrain `constant` and `discriminator` values here, because they depend on types in most cases and cant be determined before then. */ export function applyTypesAndConst< GeneratorOptions, @@ -93,9 +93,7 @@ export function applyTypesAndConst< //With the same information as the node we are currently on. //This is to open up the cycle so we can finish determining types. Logger.warn( - `Cyclic models detected, we have to replace ${JSON.stringify( - constrainedModel - )} with AnyModel...` + `Cyclic models detected, we have to replace ${constrainedModel.originalInput} with AnyModel...` ); const anyModel = new ConstrainedAnyModel( constrainedModel.name, diff --git a/test/TestUtils/TestConstrainer.ts b/test/TestUtils/TestConstrainer.ts index 28dfd72155..597b9f90dc 100644 --- a/test/TestUtils/TestConstrainer.ts +++ b/test/TestUtils/TestConstrainer.ts @@ -1,21 +1,21 @@ import { Constraints, TypeMapping } from '../../src/helpers'; export const mockedTypeMapping: TypeMapping = { - Object: jest.fn().mockReturnValue('test'), - Reference: jest.fn().mockReturnValue('test'), - Any: jest.fn().mockReturnValue('test'), - Float: jest.fn().mockReturnValue('test'), - Integer: jest.fn().mockReturnValue('test'), - String: jest.fn().mockReturnValue('test'), - Boolean: jest.fn().mockReturnValue('test'), - Tuple: jest.fn().mockReturnValue('test'), - Array: jest.fn().mockReturnValue('test'), - Enum: jest.fn().mockReturnValue('test'), - Union: jest.fn().mockReturnValue('test'), - Dictionary: jest.fn().mockReturnValue('test') + Object: jest.fn().mockReturnValue('object'), + Reference: jest.fn().mockReturnValue('reference'), + Any: jest.fn().mockReturnValue('any'), + Float: jest.fn().mockReturnValue('float'), + Integer: jest.fn().mockReturnValue('integer'), + String: jest.fn().mockReturnValue('string'), + Boolean: jest.fn().mockReturnValue('boolean'), + Tuple: jest.fn().mockReturnValue('tuple'), + Array: jest.fn().mockReturnValue('array'), + Enum: jest.fn().mockReturnValue('enum'), + Union: jest.fn().mockReturnValue('union'), + Dictionary: jest.fn().mockReturnValue('dictionary') }; -export const mockedConstraints: Constraints = { +export const mockedConstraints: Constraints = { enumKey: jest.fn().mockImplementation(({ enumKey }) => enumKey), enumValue: jest.fn().mockImplementation(({ enumValue }) => enumValue), modelName: jest.fn().mockImplementation(({ modelName }) => modelName), diff --git a/test/generators/python/PythonGenerator.spec.ts b/test/generators/python/PythonGenerator.spec.ts index 228c8f7b0d..8a7bbc44a8 100644 --- a/test/generators/python/PythonGenerator.spec.ts +++ b/test/generators/python/PythonGenerator.spec.ts @@ -109,48 +109,6 @@ describe('PythonGenerator', () => { expect(models).toHaveLength(1); expect(models[0].result).toMatchSnapshot(); }); - test('should handle weird self reference models', async () => { - const doc = { - allOf: [ - { - $ref: '#/definitions/json-schema-draft-07-schema' - } - ], - definitions: { - 'json-schema-draft-07-schema': { - title: 'Core schema meta-schema', - definitions: { - schemaArray: { - type: 'array', - minItems: 1, - items: { - $ref: '#/definitions/json-schema-draft-07-schema' - } - } - }, - type: ['object', 'boolean'], - properties: { - additionalItems: { - $ref: '#/definitions/json-schema-draft-07-schema' - }, - items: { - anyOf: [ - { - $ref: '#/definitions/json-schema-draft-07-schema' - }, - { - $ref: '#/definitions/json-schema-draft-07-schema/definitions/schemaArray' - } - ] - } - } - } - } - }; - const models = await generator.generate(doc); - //expect(models).toHaveLength(3); - expect(models.map((model) => model.result)).toMatchSnapshot(); - }); test('should render `class` type', async () => { const doc = { $id: 'Address', diff --git a/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap b/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap index cef720df59..4905b3c66d 100644 --- a/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap +++ b/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap @@ -51,72 +51,6 @@ exports[`PythonGenerator Class should handle self reference models 1`] = ` " `; -exports[`PythonGenerator Class should handle weird self reference models 1`] = ` -Array [ - "", - "class RootObject: - def __init__(self, input: Dict): - if 'additional_items' in input: - self._additional_items: CoreSchemaMetaMinusSchemaObject.CoreSchemaMetaMinusSchemaObject | bool = input['additional_items'] - if 'items' in input: - self._items: Any | List[Any] = input['items'] - if 'additional_properties' in input: - self._additional_properties: dict[str, Any] = input['additional_properties'] - - @property - def additional_items(self) -> CoreSchemaMetaMinusSchemaObject.CoreSchemaMetaMinusSchemaObject | bool: - return self._additional_items - @additional_items.setter - def additional_items(self, additional_items: CoreSchemaMetaMinusSchemaObject.CoreSchemaMetaMinusSchemaObject | bool): - self._additional_items = additional_items - - @property - def items(self) -> Any | List[Any]: - return self._items - @items.setter - def items(self, items: Any | List[Any]): - self._items = items - - @property - def additional_properties(self) -> dict[str, Any]: - return self._additional_properties - @additional_properties.setter - def additional_properties(self, additional_properties: dict[str, Any]): - self._additional_properties = additional_properties -", - "class CoreSchemaMetaMinusSchemaObject: - def __init__(self, input: Dict): - if 'additional_items' in input: - self._additional_items: CoreSchemaMetaMinusSchemaObject | bool = input['additional_items'] - if 'items' in input: - self._items: CoreSchemaMetaMinusSchemaObject | bool | List[CoreSchemaMetaMinusSchemaObject | bool] = input['items'] - if 'additional_properties' in input: - self._additional_properties: dict[str, Any] = input['additional_properties'] - - @property - def additional_items(self) -> CoreSchemaMetaMinusSchemaObject | bool: - return self._additional_items - @additional_items.setter - def additional_items(self, additional_items: CoreSchemaMetaMinusSchemaObject | bool): - self._additional_items = additional_items - - @property - def items(self) -> CoreSchemaMetaMinusSchemaObject | bool | List[CoreSchemaMetaMinusSchemaObject | bool]: - return self._items - @items.setter - def items(self, items: CoreSchemaMetaMinusSchemaObject | bool | List[CoreSchemaMetaMinusSchemaObject | bool]): - self._items = items - - @property - def additional_properties(self) -> dict[str, Any]: - return self._additional_properties - @additional_properties.setter - def additional_properties(self, additional_properties: dict[str, Any]): - self._additional_properties = additional_properties -", -] -`; - exports[`PythonGenerator Class should not render reserved keyword 1`] = ` "class Address: def __init__(self, input: Dict): diff --git a/test/helpers/ConstrainHelpers.spec.ts b/test/helpers/ConstrainHelpers.spec.ts index c8fb7b0172..d75ee68700 100644 --- a/test/helpers/ConstrainHelpers.spec.ts +++ b/test/helpers/ConstrainHelpers.spec.ts @@ -289,7 +289,6 @@ describe('ConstrainHelpers', () => { expect(mockedTypeMapping.Boolean).toHaveBeenCalledTimes(1); }); }); - describe('constrain TupleModel', () => { test('should constrain correctly', () => { const stringModel = new StringModel('', undefined, {}); @@ -439,7 +438,7 @@ describe('ConstrainHelpers', () => { } ); expect(constrainedModel instanceof ConstrainedUnionModel).toEqual(true); - expect(constrainedModel.options.discriminator?.type).toEqual('test'); + expect(constrainedModel.options.discriminator?.type).toEqual('string'); }); }); describe('constrain EnumModel', () => { @@ -531,4 +530,47 @@ describe('ConstrainHelpers', () => { ); }); }); + + describe('constrain circular models', () => { + test('should handle recursive models', () => { + const stringModel = new StringModel('c', undefined, {}); + const unionA = new UnionModel('a', undefined, {}, [stringModel]); + const unionB = new UnionModel('b', undefined, {}, [stringModel]); + unionA.union.push(unionB); + unionB.union.push(unionA); + const constrainedModel = constrainMetaModel( + { + ...mockedTypeMapping, + Union: ({ constrainedModel }) => { + return constrainedModel.union + .map((value) => value.type) + .join(' and '); + } + }, + mockedConstraints, + { + metaModel: unionA, + options: {}, + constrainedName: '', + dependencyManager: undefined as never + }, + [ + ConstrainedAnyModel, + ConstrainedBooleanModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, + ConstrainedStringModel, + ConstrainedReferenceModel, + ConstrainedObjectModel, + ConstrainedEnumModel, + ConstrainedObjectModel, + ConstrainedArrayModel, + ConstrainedDictionaryModel, + ConstrainedTupleModel + ] + ); + expect(constrainedModel instanceof ConstrainedUnionModel).toEqual(true); + expect(constrainedModel.type).toEqual('string and string and any'); + }); + }); }); From feac4aff59338398b12a76cdad2539a3966948f9 Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Mon, 29 Apr 2024 12:42:07 +0200 Subject: [PATCH 15/15] update implementation --- docs/migrations/version-3-to-4.md | 7 --- modelina-cli/package-lock.json | 2 +- src/helpers/ConstrainedTypes.ts | 91 ++++++++++++++----------------- 3 files changed, 42 insertions(+), 58 deletions(-) diff --git a/docs/migrations/version-3-to-4.md b/docs/migrations/version-3-to-4.md index d2581755d1..525b445ba1 100644 --- a/docs/migrations/version-3-to-4.md +++ b/docs/migrations/version-3-to-4.md @@ -2,13 +2,6 @@ This document contain all the breaking changes and migrations guidelines for adapting your code to the new version. -## Rewrite of how types are determined - -In v3 and below, there was a edge case where a type could not be determined IF it was a cyclic model that was NOT split out into separate models. This change affects all generators to various degrees. - -For example - - ## Deprecation of `processor.interpreter` Since the early days we had the option to set `processorOptions.interpreter` options to change how JSON Schema is interpreted to Meta models. However, these options are more accurately part of the `processorOptions.jsonSchema` options. diff --git a/modelina-cli/package-lock.json b/modelina-cli/package-lock.json index d456e0cda2..f6eb206c81 100644 --- a/modelina-cli/package-lock.json +++ b/modelina-cli/package-lock.json @@ -187,7 +187,7 @@ "node_modules/@asyncapi/modelina": { "version": "4.0.0-next.35", "resolved": "file:scripts/modelina-package/asyncapi-modelina.tgz", - "integrity": "sha512-obL4nusJo8o6YseNm1g93dc3K0c+CDIbx6fEKRKIah9X3FGu6omP847om+o/5pMQHCrZd4xjr238QoEC/cudSg==", + "integrity": "sha512-yYSfE0UIuCrzcxDYkUygbm1nFjSGrx16KyT12QofnSQJG8aqGHchuS3JvperfPUU6HYVJ/fJ2LrojkdLGLQeyg==", "license": "Apache-2.0", "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.1.0", diff --git a/src/helpers/ConstrainedTypes.ts b/src/helpers/ConstrainedTypes.ts index 7be6362838..0602f99b40 100644 --- a/src/helpers/ConstrainedTypes.ts +++ b/src/helpers/ConstrainedTypes.ts @@ -12,7 +12,6 @@ import { ConstrainedUnionModel } from '../models/ConstrainedMetaModel'; import { Constraints } from './MetaModelToConstrained'; - import { TypeMapping, getTypeFromMapping } from './TypeHelpers'; export interface ApplyingTypesOptions< @@ -35,6 +34,42 @@ export interface ApplyingTypesOptions< constrainRules: Constraints; } +/** + * Apply const and type to models from the constraint rules + */ +function applyTypeAndConst< + GeneratorOptions, + DependencyManager extends AbstractDependencyManager +>(context: ApplyingTypesOptions) { + const { + constrainedModel, + typeMapping, + partOfProperty, + dependencyManager, + generatorOptions, + alreadySeenModels, + constrainRules + } = context; + if (constrainedModel.type !== '') { + return; + } + constrainedModel.type = getTypeFromMapping(typeMapping, { + constrainedModel, + options: generatorOptions, + partOfProperty, + dependencyManager + }); + + if (constrainedModel.options.const) { + const constrainedConstant = constrainRules.constant({ + constrainedMetaModel: constrainedModel, + options: generatorOptions + }); + constrainedModel.options.const.value = constrainedConstant; + } + alreadySeenModels.set(constrainedModel, constrainedModel.type); +} + /** * Applying types and const through cyclic analysis (https://en.wikipedia.org/wiki/Cycle_(graph_theory)) * to detect and adapt unmanageable cyclic models where we cant determine types as normal. @@ -56,37 +91,11 @@ export function applyTypesAndConst< >( context: ApplyingTypesOptions ): ConstrainedMetaModel | undefined { - const { - constrainedModel, - safeTypes, - typeMapping, - partOfProperty, - dependencyManager, - generatorOptions, - alreadySeenModels, - constrainRules - } = context; + const { constrainedModel, safeTypes, alreadySeenModels } = context; const isCyclicModel = alreadySeenModels.has(constrainedModel) && alreadySeenModels.get(constrainedModel) === undefined; const hasBeenSolved = alreadySeenModels.has(constrainedModel); - const applyTypeAndConst = (model: ConstrainedMetaModel) => { - model.type = getTypeFromMapping(typeMapping, { - constrainedModel: model, - options: generatorOptions, - partOfProperty, - dependencyManager - }); - - if (model.options.const) { - const constrainedConstant = constrainRules.constant({ - constrainedMetaModel: model, - options: generatorOptions - }); - model.options.const.value = constrainedConstant; - } - alreadySeenModels.set(model, model.type); - }; if (isCyclicModel) { //Cyclic models detected, having to make the edge (right before cyclic occur) to use AnyModel (most open type we have) @@ -101,7 +110,7 @@ export function applyTypesAndConst< constrainedModel.options, '' ); - applyTypeAndConst(anyModel); + applyTypeAndConst({ ...context, constrainedModel: anyModel }); return anyModel; } else if (hasBeenSolved) { return undefined; @@ -113,7 +122,7 @@ export function applyTypesAndConst< //Walk over all safe models that can determine it's type right away for (const safeType of safeTypes) { if (constrainedModel instanceof safeType) { - applyTypeAndConst(constrainedModel); + applyTypeAndConst({ ...context, constrainedModel }); break; } } @@ -131,7 +140,7 @@ function walkNode< GeneratorOptions, DependencyManager extends AbstractDependencyManager >(context: ApplyingTypesOptions) { - const { constrainedModel, constrainRules, generatorOptions } = context; + const { constrainedModel } = context; if (constrainedModel instanceof ConstrainedObjectModel) { walkObjectNode(context); @@ -146,26 +155,8 @@ function walkNode< } else if (constrainedModel instanceof ConstrainedReferenceModel) { walkReferenceNode(context); } - if (constrainedModel.type === '') { - constrainedModel.type = getTypeFromMapping(context.typeMapping, { - constrainedModel, - options: context.generatorOptions, - partOfProperty: context.partOfProperty, - dependencyManager: context.dependencyManager - }); - context.alreadySeenModels.set(constrainedModel, constrainedModel.type); - } - if ( - constrainedModel instanceof ConstrainedReferenceModel && - constrainedModel.options.const - ) { - const constrainedConstant = constrainRules.constant({ - constrainedMetaModel: constrainedModel, - options: generatorOptions - }); - constrainedModel.options.const.value = constrainedConstant; - } + applyTypeAndConst({ ...context, constrainedModel }); if (constrainedModel instanceof ConstrainedUnionModel) { addDiscriminatorTypeToUnionModel(constrainedModel);