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..1190c6ee0e 100644 --- a/examples/file-uri-input/index.spec.ts +++ b/examples/file-uri-input/index.spec.ts @@ -4,23 +4,25 @@ 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(); + }); }); }); diff --git a/modelina-cli/package-lock.json b/modelina-cli/package-lock.json index eb0101a834..f6eb206c81 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-yYSfE0UIuCrzcxDYkUygbm1nFjSGrx16KyT12QofnSQJG8aqGHchuS3JvperfPUU6HYVJ/fJ2LrojkdLGLQeyg==", "license": "Apache-2.0", "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.1.0", 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 d89b486d5f..5b83545ac2 100644 --- a/src/generators/go/GoGenerator.ts +++ b/src/generators/go/GoGenerator.ts @@ -11,8 +11,15 @@ import { ConstrainedObjectModel, ConstrainedEnumModel, ConstrainedMetaModel, - ConstrainedUnionModel, - MetaModel + MetaModel, + ConstrainedAnyModel, + ConstrainedBooleanModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, + ConstrainedReferenceModel, + ConstrainedStringModel, + ConstrainedTupleModel, + ConstrainedUnionModel } from '../../models'; import { ConstantConstraint, @@ -59,6 +66,24 @@ 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 + */ export class GoGenerator extends AbstractGenerator< GoOptions, GoRenderCompleteModelOptions @@ -133,7 +158,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..1dc703353c 100644 --- a/src/generators/kotlin/KotlinGenerator.ts +++ b/src/generators/kotlin/KotlinGenerator.ts @@ -6,9 +6,17 @@ import { defaultGeneratorOptions } from '../AbstractGenerator'; import { + ConstrainedAnyModel, + ConstrainedBooleanModel, ConstrainedEnumModel, + ConstrainedFloatModel, + ConstrainedIntegerModel, ConstrainedMetaModel, ConstrainedObjectModel, + ConstrainedReferenceModel, + ConstrainedStringModel, + ConstrainedTupleModel, + ConstrainedUnionModel, InputMetaModel, MetaModel, RenderOutput @@ -52,6 +60,23 @@ 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, + ConstrainedEnumModel, + ConstrainedUnionModel +]; + export class KotlinGenerator extends AbstractGenerator< KotlinOptions, KotlinRenderCompleteModelOptions @@ -127,7 +152,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 753ad7f7a7..ac301f9010 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 @@ -56,6 +62,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 @@ -134,7 +154,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 c50215bdad..b6cc791659 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 8370f4ec9f..adb310dedb 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 { applyTypesAndConst } 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,104 +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()); + applyTypesAndConst({ + 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..0602f99b40 --- /dev/null +++ b/src/helpers/ConstrainedTypes.ts @@ -0,0 +1,314 @@ +import { Logger } from '../utils'; +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, 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. + */ + safeTypes: any[]; + constrainedModel: ConstrainedMetaModel; + typeMapping: TypeMapping; + alreadySeenModels: Map; + generatorOptions: GeneratorOptions; + partOfProperty?: ConstrainedObjectPropertyModel; + dependencyManager: DependencyManager; + 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. + * + * 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 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 constrain `constant` and `discriminator` values here, because they depend on types in most cases and cant be determined before then. + */ +export function applyTypesAndConst< + GeneratorOptions, + DependencyManager extends AbstractDependencyManager +>( + context: ApplyingTypesOptions +): ConstrainedMetaModel | undefined { + const { constrainedModel, safeTypes, alreadySeenModels } = 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 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 ${constrainedModel.originalInput} with AnyModel...` + ); + const anyModel = new ConstrainedAnyModel( + constrainedModel.name, + constrainedModel.originalInput, + constrainedModel.options, + '' + ); + applyTypeAndConst({ ...context, constrainedModel: anyModel }); + return anyModel; + } else if (hasBeenSolved) { + return undefined; + } + + //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) { + applyTypeAndConst({ ...context, constrainedModel }); + break; + } + } + + //Walk over all nested models + walkNode(context); +} + +/** + * 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, + DependencyManager extends AbstractDependencyManager +>(context: ApplyingTypesOptions) { + const { constrainedModel } = 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); + } + + applyTypeAndConst({ ...context, constrainedModel }); + + 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 = applyTypesAndConst({ + ...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 = applyTypesAndConst({ + ...context, + constrainedModel: dictionaryModel.key, + partOfProperty: undefined + }); + if (overwriteKeyModel) { + dictionaryModel.key = overwriteKeyModel; + } + const overWriteValueModel = applyTypesAndConst({ + ...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 = applyTypesAndConst({ + ...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 = applyTypesAndConst({ + ...context, + constrainedModel: arrayModel.valueModel, + partOfProperty: undefined + }); + + 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 = applyTypesAndConst({ + ...context, + constrainedModel: unionValueModel, + partOfProperty: undefined + }); + + 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 = applyTypesAndConst({ + ...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; + } +} 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 7ad1bd77d3..9bcc40de5e 100644 --- a/src/processors/JsonSchemaInputProcessor.ts +++ b/src/processors/JsonSchemaInputProcessor.ts @@ -227,6 +227,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/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/php/__snapshots__/PhpGenerator.spec.ts.snap b/test/generators/php/__snapshots__/PhpGenerator.spec.ts.snap index f4b876fa85..43cfb72677 100644 --- a/test/generators/php/__snapshots__/PhpGenerator.spec.ts.snap +++ b/test/generators/php/__snapshots__/PhpGenerator.spec.ts.snap @@ -54,6 +54,28 @@ exports[`PhpGenerator Class should render \`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 Class should render optional/nullable properties 1`] = ` "final class Address { @@ -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/helpers/ConstrainHelpers.spec.ts b/test/helpers/ConstrainHelpers.spec.ts index 8e64b34f83..d75ee68700 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', () => { @@ -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'); + }); + }); }); 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, '' );