From 60fcc07061ac8cbee7c03a66eba7a0469cb704eb Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Thu, 17 Jun 2021 15:00:14 +0200 Subject: [PATCH] feat: render additionalProperties (#240) --- .eslintrc | 10 +-- src/generators/java/JavaRenderer.ts | 10 +++ src/generators/java/presets/CommonPreset.ts | 11 ++-- .../java/presets/ConstraintsPreset.ts | 6 -- .../java/presets/DescriptioPreset.ts | 5 -- src/generators/java/presets/JacksonPreset.ts | 12 ++-- .../java/renderers/ClassRenderer.ts | 61 +++++++++++++------ .../javascript/JavaScriptRenderer.ts | 13 ++-- .../typescript/TypeScriptRenderer.ts | 31 ++++++++-- .../typescript/renderers/ClassRenderer.ts | 55 +++++++++++------ .../typescript/renderers/InterfaceRenderer.ts | 6 +- src/helpers/NameHelpers.ts | 23 +++++++ src/helpers/index.ts | 1 + src/models/Preset.ts | 5 ++ test/generators/java/JavaGenerator.spec.ts | 21 +++++-- .../java/presets/CommonPreset.spec.ts | 22 +++++-- .../java/presets/ConstraintsPreset.spec.ts | 7 ++- .../java/presets/DescriptionPreset.spec.ts | 7 ++- .../java/presets/JacksonPreset.spec.ts | 7 ++- .../javascript/JavaScriptGenerator.spec.ts | 2 + .../typescript/TypeScriptGenerator.spec.ts | 11 ++++ test/helpers/NameHelpers.spec.ts | 21 +++++++ 22 files changed, 260 insertions(+), 87 deletions(-) create mode 100644 src/helpers/NameHelpers.ts create mode 100644 test/helpers/NameHelpers.spec.ts diff --git a/.eslintrc b/.eslintrc index de151c1eb7..05f03a8794 100644 --- a/.eslintrc +++ b/.eslintrc @@ -48,12 +48,7 @@ "no-empty-character-class": 2, "no-self-compare": 2, "valid-typeof": 2, - "no-unused-vars": [ - 2, - { - "args": "none" - } - ], + "no-unused-vars": 0, "handle-callback-err": 2, "no-shadow-restricted-names": 2, "no-new-require": 2, @@ -187,7 +182,8 @@ "prefer-arrow-callback": 2, "prefer-const": 2, "prefer-spread": 2, - "prefer-template": 2 + "prefer-template": 2, + "@typescript-eslint/no-unused-vars": 2 }, "overrides": [ { diff --git a/src/generators/java/JavaRenderer.ts b/src/generators/java/JavaRenderer.ts index 206d3f6dfc..01e67452b7 100644 --- a/src/generators/java/JavaRenderer.ts +++ b/src/generators/java/JavaRenderer.ts @@ -19,6 +19,11 @@ export abstract class JavaRenderer extends AbstractRenderer { super(options, presets, model, inputModel); } + /** + * Renders model(s) to Java type(s). + * + * @param model + */ renderType(model: CommonModel | CommonModel[]): string { if (Array.isArray(model) || Array.isArray(model.type)) { return 'Object'; // fallback @@ -30,6 +35,11 @@ export abstract class JavaRenderer extends AbstractRenderer { return this.toClassType(this.toJavaType(format || model.type, model)); } + /** + * Returns the Java corresponding type from CommonModel type or JSON schema format + * @param type + * @param model + */ toJavaType(type: string | undefined, model: CommonModel): string { switch (type) { case 'integer': diff --git a/src/generators/java/presets/CommonPreset.ts b/src/generators/java/presets/CommonPreset.ts index 02a6732a8a..78f9036936 100644 --- a/src/generators/java/presets/CommonPreset.ts +++ b/src/generators/java/presets/CommonPreset.ts @@ -1,7 +1,7 @@ import { JavaRenderer } from '../JavaRenderer'; import { JavaPreset } from '../JavaPreset'; -import { FormatHelpers } from '../../../helpers'; +import { getUniquePropertyName, FormatHelpers, DefaultPropertyNames } from '../../../helpers'; import { CommonModel } from '../../../models'; export interface JavaCommonPresetOptions { @@ -19,7 +19,8 @@ function renderEqual({ renderer, model }: { }): string { const formattedModelName = model.$id && FormatHelpers.toPascalCase(model.$id); const properties = model.properties || {}; - const equalProperties = Object.keys(properties).map(prop => { + const propertyKeys = [...Object.keys(properties), getUniquePropertyName(model, DefaultPropertyNames.additionalProperties)]; + const equalProperties = propertyKeys.map(prop => { const camelCasedProp = FormatHelpers.toCamelCase(prop); return `Objects.equals(this.${camelCasedProp}, self.${camelCasedProp})`; }).join(' &&\n'); @@ -46,7 +47,8 @@ function renderHashCode({ renderer, model }: { model: CommonModel, }): string { const properties = model.properties || {}; - const hashProperties = Object.keys(properties).map(prop => FormatHelpers.toCamelCase(prop)).join(', '); + const propertyKeys = [...Object.keys(properties), getUniquePropertyName(model, DefaultPropertyNames.additionalProperties)]; + const hashProperties = propertyKeys.map(prop => FormatHelpers.toCamelCase(prop)).join(', '); return `${renderer.renderAnnotation('Override')} public int hashCode() { @@ -63,7 +65,8 @@ function renderToString({ renderer, model }: { }): string { const formattedModelName = model.$id && FormatHelpers.toPascalCase(model.$id); const properties = model.properties || {}; - const toStringProperties = Object.keys(properties).map(prop => + const propertyKeys = [...Object.keys(properties), getUniquePropertyName(model, DefaultPropertyNames.additionalProperties)]; + const toStringProperties = propertyKeys.map(prop => `" ${prop}: " + toIndentedString(${FormatHelpers.toCamelCase(prop)}) + "\\n" +` ); diff --git a/src/generators/java/presets/ConstraintsPreset.ts b/src/generators/java/presets/ConstraintsPreset.ts index b32655fc80..2a4073c33e 100644 --- a/src/generators/java/presets/ConstraintsPreset.ts +++ b/src/generators/java/presets/ConstraintsPreset.ts @@ -1,7 +1,4 @@ import { JavaPreset } from '../JavaPreset'; - -import { CommonModel } from '../../../models'; - /** * Preset which extends class's getters with annotations from `javax.validation.constraints` package * @@ -14,9 +11,6 @@ export const JAVA_CONSTRAINTS_PRESET: JavaPreset = { return content; }, getter({ renderer, model, propertyName, property, content }) { - if (!(property instanceof CommonModel)) { - return content; - } const annotations: string[] = []; const isRequired = model.isRequired(propertyName); diff --git a/src/generators/java/presets/DescriptioPreset.ts b/src/generators/java/presets/DescriptioPreset.ts index 4710434612..8b49ef4ef7 100644 --- a/src/generators/java/presets/DescriptioPreset.ts +++ b/src/generators/java/presets/DescriptioPreset.ts @@ -1,6 +1,5 @@ import { JavaRenderer } from '../JavaRenderer'; import { JavaPreset } from '../JavaPreset'; - import { FormatHelpers } from '../../../helpers'; import { CommonModel } from '../../../models'; @@ -9,10 +8,6 @@ function renderDescription({ renderer, content, item }: { content: string, item: CommonModel, }): string { - if (!(item instanceof CommonModel)) { - return content; - } - let desc = item.getFromSchema('description'); const examples = item.getFromSchema('examples'); diff --git a/src/generators/java/presets/JacksonPreset.ts b/src/generators/java/presets/JacksonPreset.ts index 6895a162a0..2038ef6a10 100644 --- a/src/generators/java/presets/JacksonPreset.ts +++ b/src/generators/java/presets/JacksonPreset.ts @@ -1,7 +1,8 @@ +import { PropertyType } from '../../../models'; import { JavaPreset } from '../JavaPreset'; /** - * Preset which adds `com.fasterxml.jackson` related annotations to class's getters. + * Preset which adds `com.fasterxml.jackson` related annotations to class's property getters. * * @implements {JavaPreset} */ @@ -11,9 +12,12 @@ export const JAVA_JACKSON_PRESET: JavaPreset = { renderer.addDependency('import com.fasterxml.jackson.annotation.*;'); return content; }, - getter({ renderer, propertyName, content }) { - const annotation = renderer.renderAnnotation('JsonProperty', `"${propertyName}"`); - return renderer.renderBlock([annotation, content]); + getter({ renderer, propertyName, content, type }) { + if (type === PropertyType.property) { + const annotation = renderer.renderAnnotation('JsonProperty', `"${propertyName}"`); + return renderer.renderBlock([annotation, content]); + } + return renderer.renderBlock([content]); }, } }; diff --git a/src/generators/java/renderers/ClassRenderer.ts b/src/generators/java/renderers/ClassRenderer.ts index d1cb388bf3..35cb78cad0 100644 --- a/src/generators/java/renderers/ClassRenderer.ts +++ b/src/generators/java/renderers/ClassRenderer.ts @@ -1,7 +1,7 @@ import { JavaRenderer } from '../JavaRenderer'; -import { CommonModel, ClassPreset } from '../../../models'; -import { FormatHelpers } from '../../../helpers'; +import { CommonModel, ClassPreset, PropertyType } from '../../../models'; +import { DefaultPropertyNames, FormatHelpers, getUniquePropertyName } from '../../../helpers'; /** * Renderer for Java's `class` type @@ -16,6 +16,10 @@ export class ClassRenderer extends JavaRenderer { await this.renderAccessors(), await this.runAdditionalContentPreset(), ]; + + if (this.model.additionalProperties !== undefined) { + this.addDependency('import java.util.Map;'); + } const formattedName = this.model.$id && FormatHelpers.toPascalCase(this.model.$id); return `public class ${formattedName} { @@ -35,12 +39,18 @@ ${this.indent(this.renderBlock(content, 2))} const rendererProperty = await this.runPropertyPreset(propertyName, property); content.push(rendererProperty); } + + if (this.model.additionalProperties !== undefined) { + const propertyName = getUniquePropertyName(this.model, DefaultPropertyNames.additionalProperties); + const additionalProperty = await this.runPropertyPreset(propertyName, this.model.additionalProperties, PropertyType.additionalProperty); + content.push(additionalProperty); + } return this.renderBlock(content); } - runPropertyPreset(propertyName: string, property: CommonModel): Promise { - return this.runPreset('property', { propertyName, property }); + runPropertyPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise { + return this.runPreset('property', { propertyName, property, type}); } async renderAccessors(): Promise { @@ -53,15 +63,22 @@ ${this.indent(this.renderBlock(content, 2))} content.push(this.renderBlock([getter, setter])); } + if (this.model.additionalProperties !== undefined) { + const propertyName = getUniquePropertyName(this.model, DefaultPropertyNames.additionalProperties); + const getter = await this.runGetterPreset(propertyName, this.model.additionalProperties, PropertyType.additionalProperty); + const setter = await this.runSetterPreset(propertyName, this.model.additionalProperties, PropertyType.additionalProperty); + content.push(this.renderBlock([getter, setter])); + } + return this.renderBlock(content, 2); } - runGetterPreset(propertyName: string, property: CommonModel): Promise { - return this.runPreset('getter', { propertyName, property }); + runGetterPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise { + return this.runPreset('getter', { propertyName, property, type }); } - runSetterPreset(propertyName: string, property: CommonModel): Promise { - return this.runPreset('setter', { propertyName, property }); + runSetterPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise { + return this.runPreset('setter', { propertyName, property, type }); } } @@ -69,20 +86,30 @@ export const JAVA_DEFAULT_CLASS_PRESET: ClassPreset = { self({ renderer }) { return renderer.defaultSelf(); }, - property({ renderer, propertyName, property }) { + property({ renderer, propertyName, property, type }) { propertyName = FormatHelpers.toCamelCase(propertyName); - return `private ${renderer.renderType(property)} ${propertyName};`; + let propertyType = renderer.renderType(property); + if (type === PropertyType.additionalProperty) { + propertyType = `Map`; + } + return `private ${propertyType} ${propertyName};`; }, - getter({ renderer, propertyName, property }) { + getter({ renderer, propertyName, property, type }) { propertyName = FormatHelpers.toCamelCase(propertyName); - const getterName = FormatHelpers.toPascalCase(propertyName); - const type = renderer.renderType(property); - return `public ${type} get${getterName}() { return this.${propertyName}; }`; + const getterName = `get${FormatHelpers.toPascalCase(propertyName)}`; + let getterType = renderer.renderType(property); + if (type === PropertyType.additionalProperty) { + getterType = `Map`; + } + return `public ${getterType} ${getterName}() { return this.${propertyName}; }`; }, - setter({ renderer, propertyName, property }) { + setter({ renderer, propertyName, property, type }) { propertyName = FormatHelpers.toCamelCase(propertyName); const setterName = FormatHelpers.toPascalCase(propertyName); - const type = renderer.renderType(property); - return `public void set${setterName}(${type} ${propertyName}) { this.${propertyName} = ${propertyName}; }`; + let setterType = renderer.renderType(property); + if (type === PropertyType.additionalProperty) { + setterType = `Map`; + } + return `public void set${setterName}(${setterType} ${propertyName}) { this.${propertyName} = ${propertyName}; }`; }, }; diff --git a/src/generators/javascript/JavaScriptRenderer.ts b/src/generators/javascript/JavaScriptRenderer.ts index fb462f9e93..e9151a0d8d 100644 --- a/src/generators/javascript/JavaScriptRenderer.ts +++ b/src/generators/javascript/JavaScriptRenderer.ts @@ -1,8 +1,8 @@ import { AbstractRenderer } from '../AbstractRenderer'; import { JavaScriptOptions } from './JavaScriptGenerator'; -import { FormatHelpers } from '../../helpers'; -import { CommonModel, CommonInputModel, Preset } from '../../models'; +import { getUniquePropertyName, FormatHelpers, DefaultPropertyNames } from '../../helpers'; +import { CommonModel, CommonInputModel, Preset, PropertyType } from '../../models'; /** * Common renderer for JavaScript types @@ -36,10 +36,15 @@ ${content} content.push(rendererProperty); } + if (this.model.additionalProperties !== undefined) { + const propertyName = getUniquePropertyName(this.model, DefaultPropertyNames.additionalProperties); + const additionalProperty = await this.runPropertyPreset(propertyName, this.model.additionalProperties, PropertyType.additionalProperty); + content.push(additionalProperty); + } return this.renderBlock(content); } - runPropertyPreset(propertyName: string, property: CommonModel): Promise { - return this.runPreset('property', { propertyName, property }); + runPropertyPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise { + return this.runPreset('property', { propertyName, property, type}); } } diff --git a/src/generators/typescript/TypeScriptRenderer.ts b/src/generators/typescript/TypeScriptRenderer.ts index 3a324b73d6..4943b0053d 100644 --- a/src/generators/typescript/TypeScriptRenderer.ts +++ b/src/generators/typescript/TypeScriptRenderer.ts @@ -2,7 +2,8 @@ import { AbstractRenderer } from '../AbstractRenderer'; import { TypeScriptOptions } from './TypeScriptGenerator'; import { FormatHelpers } from '../../helpers'; -import { CommonModel, CommonInputModel, Preset } from '../../models'; +import { CommonModel, CommonInputModel, Preset, PropertyType } from '../../models'; +import { DefaultPropertyNames, getUniquePropertyName } from '../../helpers/NameHelpers'; /** * Common renderer for TypeScript types @@ -87,6 +88,9 @@ ${lines.map(line => ` * ${line}`).join('\n')} */`; } + /** + * Render all the properties for the model by calling the property preset per property. + */ async renderProperties(): Promise { const properties = this.model.properties || {}; const content: string[] = []; @@ -96,16 +100,31 @@ ${lines.map(line => ` * ${line}`).join('\n')} content.push(rendererProperty); } + if (this.model.additionalProperties !== undefined) { + const propertyName = getUniquePropertyName(this.model, DefaultPropertyNames.additionalProperties); + const additionalProperty = await this.runPropertyPreset(propertyName, this.model.additionalProperties, PropertyType.additionalProperty); + content.push(additionalProperty); + } + return this.renderBlock(content); } - renderProperty(propertyName: string, property: CommonModel): string { + renderProperty(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): string { const name = FormatHelpers.toCamelCase(propertyName); - const signature = this.renderTypeSignature(property, { isRequired: this.model.isRequired(propertyName) }); - return `${name}${signature};`; + let signature: string; + switch (type) { + case PropertyType.property: + signature = this.renderTypeSignature(property, { isRequired: this.model.isRequired(propertyName) }); + return `${name}${signature};`; + case PropertyType.additionalProperty: + signature = this.renderType(property); + return `${name}?: Map;`; + default: + return ''; + } } - runPropertyPreset(propertyName: string, property: CommonModel): Promise { - return this.runPreset('property', { propertyName, property }); + runPropertyPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise { + return this.runPreset('property', { propertyName, property, type }); } } diff --git a/src/generators/typescript/renderers/ClassRenderer.ts b/src/generators/typescript/renderers/ClassRenderer.ts index a5916de83a..2ace927f63 100644 --- a/src/generators/typescript/renderers/ClassRenderer.ts +++ b/src/generators/typescript/renderers/ClassRenderer.ts @@ -1,7 +1,7 @@ import { TypeScriptRenderer } from '../TypeScriptRenderer'; -import { CommonModel, ClassPreset } from '../../../models'; -import { FormatHelpers } from '../../../helpers'; +import { CommonModel, ClassPreset, PropertyType } from '../../../models'; +import { getUniquePropertyName, FormatHelpers, DefaultPropertyNames } from '../../../helpers'; /** * Renderer for TypeScript's `class` type @@ -36,16 +36,22 @@ ${this.indent(this.renderBlock(content, 2))} const setter = await this.runSetterPreset(propertyName, property); content.push(this.renderBlock([getter, setter])); } + if (this.model.additionalProperties !== undefined) { + const propertyName = getUniquePropertyName(this.model, DefaultPropertyNames.additionalProperties); + const getter = await this.runGetterPreset(propertyName, this.model.additionalProperties, PropertyType.additionalProperty); + const setter = await this.runSetterPreset(propertyName, this.model.additionalProperties, PropertyType.additionalProperty); + content.push(this.renderBlock([getter, setter])); + } return this.renderBlock(content, 2); } - runGetterPreset(propertyName: string, property: CommonModel): Promise { - return this.runPreset('getter', { propertyName, property }); + runGetterPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise { + return this.runPreset('getter', { propertyName, property, type }); } - runSetterPreset(propertyName: string, property: CommonModel): Promise { - return this.runPreset('setter', { propertyName, property }); + runSetterPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise { + return this.runPreset('setter', { propertyName, property, type }); } } @@ -55,7 +61,7 @@ export const TS_DEFAULT_CLASS_PRESET: ClassPreset = { }, ctor({ renderer, model }) : string { const properties = model.properties || {}; - const assigments = Object.keys(properties).map(property => { + const assignments = Object.keys(properties).map(property => { property = FormatHelpers.toCamelCase(property); return `this._${property} = input.${property};`; }); @@ -68,23 +74,34 @@ export const TS_DEFAULT_CLASS_PRESET: ClassPreset = { return `constructor(input: { ${renderer.indent(renderer.renderBlock(ctorProperties))} }) { -${renderer.indent(renderer.renderBlock(assigments))} +${renderer.indent(renderer.renderBlock(assignments))} }`; }, - property({ renderer, propertyName, property }): string { - return `private _${renderer.renderProperty(propertyName, property)}`; + property({ renderer, propertyName, property, type }): string { + return `private _${renderer.renderProperty(propertyName, property, type)}`; }, - getter({ renderer, model, propertyName, property }): string { + getter({ renderer, model, propertyName, property, type }): string { const isRequired = model.isRequired(propertyName); - const formattedName = FormatHelpers.toCamelCase(propertyName); - const signature = renderer.renderTypeSignature(property, { orUndefined: !isRequired }); - return `get ${formattedName}()${signature} { return this._${formattedName}; }`; + propertyName = FormatHelpers.toCamelCase(propertyName); + let signature = ''; + if (type === PropertyType.property) { + signature = renderer.renderTypeSignature(property, { orUndefined: !isRequired }); + } else if (type === PropertyType.additionalProperty) { + const additionalPropertyType = renderer.renderType(property); + signature = `: Map | undefined`; + } + return `get ${propertyName}()${signature} { return this._${propertyName}; }`; }, - setter({ renderer, model, propertyName, property }): string { + setter({ renderer, model, propertyName, property, type }): string { const isRequired = model.isRequired(propertyName); - const formattedName = FormatHelpers.toCamelCase(propertyName); - const signature = renderer.renderTypeSignature(property, { orUndefined: !isRequired }); - const arg = `${formattedName}${signature}`; - return `set ${formattedName}(${arg}) { this._${formattedName} = ${formattedName}; }`; + propertyName = FormatHelpers.toCamelCase(propertyName); + let signature = ''; + if (type === PropertyType.property) { + signature = renderer.renderTypeSignature(property, { orUndefined: !isRequired }); + } else if (type === PropertyType.additionalProperty) { + const additionalPropertyType = renderer.renderType(property); + signature = `: Map | undefined`; + } + return `set ${propertyName}(${propertyName}${signature}) { this._${propertyName} = ${propertyName}; }`; }, }; diff --git a/src/generators/typescript/renderers/InterfaceRenderer.ts b/src/generators/typescript/renderers/InterfaceRenderer.ts index 5616b2829f..cd421779b2 100644 --- a/src/generators/typescript/renderers/InterfaceRenderer.ts +++ b/src/generators/typescript/renderers/InterfaceRenderer.ts @@ -26,7 +26,7 @@ export const TS_DEFAULT_INTERFACE_PRESET: InterfacePreset = { async self({ renderer }) { return `export ${await renderer.defaultSelf()}`; }, - property({ renderer, propertyName, property }) { - return renderer.renderProperty(propertyName, property); - }, + property({ renderer, propertyName, property, type }) { + return renderer.renderProperty(propertyName, property, type); + } }; diff --git a/src/helpers/NameHelpers.ts b/src/helpers/NameHelpers.ts new file mode 100644 index 0000000000..f79d73abc3 --- /dev/null +++ b/src/helpers/NameHelpers.ts @@ -0,0 +1,23 @@ +import { CommonModel } from 'models'; + +/** + * Default property names for different aspects of the common model + */ +export enum DefaultPropertyNames { + additionalProperties = 'additionalProperties' +} + +/** + * Recursively find the proper property name for additionalProperties + * + * This function ensures that the property name for additionalProperties is unique + * + * @param rootModel + * @param propertyName + */ +export function getUniquePropertyName(rootModel: CommonModel, propertyName: string): string { + if (Object.keys(rootModel.properties || {}).includes(propertyName)) { + return getUniquePropertyName(rootModel, `_${propertyName}`); + } + return propertyName; +} diff --git a/src/helpers/index.ts b/src/helpers/index.ts index ae92ed1bb8..fc4d76bbc0 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -1,2 +1,3 @@ export * from './FormatHelpers'; export * from './TypeHelpers'; +export * from './NameHelpers'; diff --git a/src/models/Preset.ts b/src/models/Preset.ts index dbfaeb7b34..37a32be747 100644 --- a/src/models/Preset.ts +++ b/src/models/Preset.ts @@ -16,9 +16,14 @@ export interface CommonPreset) => Promise | string; } +export enum PropertyType { + property, + additionalProperty +} export interface PropertyArgs { propertyName: string; property: CommonModel; + type: PropertyType } export interface ClassPreset extends CommonPreset { diff --git a/test/generators/java/JavaGenerator.spec.ts b/test/generators/java/JavaGenerator.spec.ts index 35b0698352..27dd77bbe8 100644 --- a/test/generators/java/JavaGenerator.spec.ts +++ b/test/generators/java/JavaGenerator.spec.ts @@ -29,6 +29,7 @@ describe('JavaGenerator', () => { private Boolean marriage; private Object members; private Object[] arrayType; + private Map additionalProperties; public String getStreetName() { return this.streetName; } public void setStreetName(String streetName) { this.streetName = streetName; } @@ -50,18 +51,22 @@ describe('JavaGenerator', () => { public Object[] getArrayType() { return this.arrayType; } public void setArrayType(Object[] arrayType) { this.arrayType = arrayType; } + + public Map getAdditionalProperties() { return this.additionalProperties; } + public void setAdditionalProperties(Map additionalProperties) { this.additionalProperties = additionalProperties; } }`; const inputModel = await generator.process(doc); const model = inputModel.models['Address']; let classModel = await generator.renderClass(model, inputModel); + const expectedDependencies = ['import java.util.Map;']; expect(classModel.result).toEqual(expected); - expect(classModel.dependencies).toEqual([]); + expect(classModel.dependencies).toEqual(expectedDependencies); classModel = await generator.render(model, inputModel); expect(classModel.result).toEqual(expected); - expect(classModel.dependencies).toEqual([]); + expect(classModel.dependencies).toEqual(expectedDependencies); }); test('should work custom preset for `class` type', async () => { @@ -75,11 +80,18 @@ describe('JavaGenerator', () => { const expected = `public class CustomClass { @JsonProperty("property") private String property; + @JsonProperty("additionalProperties") + private Map additionalProperties; @JsonProperty("property") public String getProperty() { return this.property; } @JsonProperty("property") public void setProperty(String property) { this.property = property; } + + @JsonProperty("additionalProperties") + public Map getAdditionalProperties() { return this.additionalProperties; } + @JsonProperty("additionalProperties") + public void setAdditionalProperties(Map additionalProperties) { this.additionalProperties = additionalProperties; } }`; generator = new JavaGenerator({ presets: [ @@ -105,12 +117,13 @@ describe('JavaGenerator', () => { const model = inputModel.models['CustomClass']; let classModel = await generator.renderClass(model, inputModel); + const expectedDependencies = ['import java.util.Map;']; expect(classModel.result).toEqual(expected); - expect(classModel.dependencies).toEqual([]); + expect(classModel.dependencies).toEqual(expectedDependencies); classModel = await generator.render(model, inputModel); expect(classModel.result).toEqual(expected); - expect(classModel.dependencies).toEqual([]); + expect(classModel.dependencies).toEqual(expectedDependencies); }); test('should render `enum` type (string type)', async () => { diff --git a/test/generators/java/presets/CommonPreset.spec.ts b/test/generators/java/presets/CommonPreset.spec.ts index 532a9fc400..54a3f6a9b6 100644 --- a/test/generators/java/presets/CommonPreset.spec.ts +++ b/test/generators/java/presets/CommonPreset.spec.ts @@ -13,6 +13,7 @@ describe('JAVA_COMMON_PRESET', () => { const expected = `public class Clazz { private String stringProp; private Double numberProp; + private Map additionalProperties; public String getStringProp() { return this.stringProp; } public void setStringProp(String stringProp) { this.stringProp = stringProp; } @@ -20,6 +21,9 @@ describe('JAVA_COMMON_PRESET', () => { public Double getNumberProp() { return this.numberProp; } public void setNumberProp(Double numberProp) { this.numberProp = numberProp; } + public Map getAdditionalProperties() { return this.additionalProperties; } + public void setAdditionalProperties(Map additionalProperties) { this.additionalProperties = additionalProperties; } + @Override public boolean equals(Object o) { if (this == o) { @@ -31,12 +35,13 @@ describe('JAVA_COMMON_PRESET', () => { Clazz self = (Clazz) o; return Objects.equals(this.stringProp, self.stringProp) && - Objects.equals(this.numberProp, self.numberProp); + Objects.equals(this.numberProp, self.numberProp) && + Objects.equals(this.additionalProperties, self.additionalProperties); } @Override public int hashCode() { - return Objects.hash(stringProp, numberProp); + return Objects.hash(stringProp, numberProp, additionalProperties); } @Override @@ -44,6 +49,7 @@ describe('JAVA_COMMON_PRESET', () => { return "class Clazz {\\n" + " stringProp: " + toIndentedString(stringProp) + "\\n" + " numberProp: " + toIndentedString(numberProp) + "\\n" + + " additionalProperties: " + toIndentedString(additionalProperties) + "\\n" + "}"; } @@ -64,8 +70,9 @@ describe('JAVA_COMMON_PRESET', () => { const model = inputModel.models['Clazz']; const classModel = await generator.renderClass(model, inputModel); + const expectedDependencies = ['import java.util.Map;']; expect(classModel.result).toEqual(expected); - expect(classModel.dependencies).toEqual([]); + expect(classModel.dependencies).toEqual(expectedDependencies); }); test('should skip rendering of disabled functions', async () => { @@ -80,6 +87,7 @@ describe('JAVA_COMMON_PRESET', () => { const expected = `public class Clazz { private String stringProp; private Double numberProp; + private Map additionalProperties; public String getStringProp() { return this.stringProp; } public void setStringProp(String stringProp) { this.stringProp = stringProp; } @@ -87,9 +95,12 @@ describe('JAVA_COMMON_PRESET', () => { public Double getNumberProp() { return this.numberProp; } public void setNumberProp(Double numberProp) { this.numberProp = numberProp; } + public Map getAdditionalProperties() { return this.additionalProperties; } + public void setAdditionalProperties(Map additionalProperties) { this.additionalProperties = additionalProperties; } + @Override public int hashCode() { - return Objects.hash(stringProp, numberProp); + return Objects.hash(stringProp, numberProp, additionalProperties); } }`; @@ -104,7 +115,8 @@ describe('JAVA_COMMON_PRESET', () => { const model = inputModel.models['Clazz']; const classModel = await generator.renderClass(model, inputModel); + const expectedDependencies = ['import java.util.Map;']; expect(classModel.result).toEqual(expected); - expect(classModel.dependencies).toEqual([]); + expect(classModel.dependencies).toEqual(expectedDependencies); }); }); diff --git a/test/generators/java/presets/ConstraintsPreset.spec.ts b/test/generators/java/presets/ConstraintsPreset.spec.ts index 4e442ebb6e..99eec2ef32 100644 --- a/test/generators/java/presets/ConstraintsPreset.spec.ts +++ b/test/generators/java/presets/ConstraintsPreset.spec.ts @@ -23,6 +23,7 @@ describe('JAVA_CONSTRAINTS_PRESET', () => { private Double maxNumberProp; private Object[] arrayProp; private String stringProp; + private Map additionalProperties; @NotNull @Min(0) @@ -42,13 +43,17 @@ describe('JAVA_CONSTRAINTS_PRESET', () => { @Size(min=3) public String getStringProp() { return this.stringProp; } public void setStringProp(String stringProp) { this.stringProp = stringProp; } + + public Map getAdditionalProperties() { return this.additionalProperties; } + public void setAdditionalProperties(Map additionalProperties) { this.additionalProperties = additionalProperties; } }`; const inputModel = await generator.process(doc); const model = inputModel.models['Clazz']; const classModel = await generator.renderClass(model, inputModel); + const expectedDependencies = ['import java.util.Map;', 'import javax.validation.constraints.*;']; expect(classModel.result).toEqual(expected); - expect(classModel.dependencies).toEqual(['import javax.validation.constraints.*;']); + expect(classModel.dependencies).toEqual(expectedDependencies); }); }); diff --git a/test/generators/java/presets/DescriptionPreset.spec.ts b/test/generators/java/presets/DescriptionPreset.spec.ts index 8945058659..df59929f48 100644 --- a/test/generators/java/presets/DescriptionPreset.spec.ts +++ b/test/generators/java/presets/DescriptionPreset.spec.ts @@ -22,6 +22,7 @@ describe('JAVA_DESCRIPTION_PRESET', () => { */ public class Clazz { private String prop; + private Map additionalProperties; /** * Description for prop @@ -29,14 +30,18 @@ public class Clazz { */ public String getProp() { return this.prop; } public void setProp(String prop) { this.prop = prop; } + + public Map getAdditionalProperties() { return this.additionalProperties; } + public void setAdditionalProperties(Map additionalProperties) { this.additionalProperties = additionalProperties; } }`; const inputModel = await generator.process(doc); const model = inputModel.models['Clazz']; const classModel = await generator.renderClass(model, inputModel); + const expectedDependencies = ['import java.util.Map;']; expect(classModel.result).toEqual(expected); - expect(classModel.dependencies).toEqual([]); + expect(classModel.dependencies).toEqual(expectedDependencies); }); test('should render description and examples for enum', async () => { diff --git a/test/generators/java/presets/JacksonPreset.spec.ts b/test/generators/java/presets/JacksonPreset.spec.ts index 9cd828099f..cb198ddd15 100644 --- a/test/generators/java/presets/JacksonPreset.spec.ts +++ b/test/generators/java/presets/JacksonPreset.spec.ts @@ -18,6 +18,7 @@ describe('JAVA_DESCRIPTION_PRESET', () => { const expected = `public class Clazz { private Double minNumberProp; private Double maxNumberProp; + private Map additionalProperties; @JsonProperty("min_number_prop") public Double getMinNumberProp() { return this.minNumberProp; } @@ -26,13 +27,17 @@ describe('JAVA_DESCRIPTION_PRESET', () => { @JsonProperty("max_number_prop") public Double getMaxNumberProp() { return this.maxNumberProp; } public void setMaxNumberProp(Double maxNumberProp) { this.maxNumberProp = maxNumberProp; } + + public Map getAdditionalProperties() { return this.additionalProperties; } + public void setAdditionalProperties(Map additionalProperties) { this.additionalProperties = additionalProperties; } }`; const inputModel = await generator.process(doc); const model = inputModel.models['Clazz']; const classModel = await generator.renderClass(model, inputModel); + const expectedDependencies = ['import java.util.Map;', 'import com.fasterxml.jackson.annotation.*;']; expect(classModel.result).toEqual(expected); - expect(classModel.dependencies).toEqual(['import com.fasterxml.jackson.annotation.*;']); + expect(classModel.dependencies).toEqual(expectedDependencies); }); }); diff --git a/test/generators/javascript/JavaScriptGenerator.spec.ts b/test/generators/javascript/JavaScriptGenerator.spec.ts index c453dcdb72..9b149f9e28 100644 --- a/test/generators/javascript/JavaScriptGenerator.spec.ts +++ b/test/generators/javascript/JavaScriptGenerator.spec.ts @@ -29,6 +29,7 @@ describe('JavaScriptGenerator', () => { marriage; members; arrayType; + additionalProperties; constructor(input) { this.streetName = input.streetName; @@ -99,6 +100,7 @@ describe('JavaScriptGenerator', () => { }; const expected = `export class CustomClass { #property; + #additionalProperties; constructor(input) { this.#property = input.property; diff --git a/test/generators/typescript/TypeScriptGenerator.spec.ts b/test/generators/typescript/TypeScriptGenerator.spec.ts index d24ed09161..cefc24b991 100644 --- a/test/generators/typescript/TypeScriptGenerator.spec.ts +++ b/test/generators/typescript/TypeScriptGenerator.spec.ts @@ -30,6 +30,7 @@ describe('TypeScriptGenerator', () => { private _members?: string | number | boolean; private _tupleType?: [string, number]; private _arrayType: Array; + private _additionalProperties?: Map | boolean | null | number>; constructor(input: { streetName: string, @@ -74,6 +75,9 @@ describe('TypeScriptGenerator', () => { get arrayType(): Array { return this._arrayType; } set arrayType(arrayType: Array) { this._arrayType = arrayType; } + + get additionalProperties(): Map | boolean | null | number> | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Map | boolean | null | number> | undefined) { this._additionalProperties = additionalProperties; } }`; const inputModel = await generator.process(doc); @@ -99,6 +103,8 @@ describe('TypeScriptGenerator', () => { const expected = `export class CustomClass { @JsonProperty("property") private _property?: string; + @JsonProperty("additionalProperties") + private _additionalProperties?: Map | boolean | null | number>; constructor(input: { property?: string, @@ -108,6 +114,9 @@ describe('TypeScriptGenerator', () => { get property(): string | undefined { return this._property; } set property(property: string | undefined) { this._property = property; } + + get additionalProperties(): Map | boolean | null | number> | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Map | boolean | null | number> | undefined) { this._additionalProperties = additionalProperties; } }`; generator = new TypeScriptGenerator({ presets: [ @@ -154,6 +163,7 @@ ${content}`; members?: string | number | boolean; tupleType?: [string, number]; arrayType: Array; + additionalProperties?: Map | boolean | null | number>; }`; const interfaceGenerator = new TypeScriptGenerator({modelType: 'interface'}); @@ -175,6 +185,7 @@ ${content}`; }; const expected = `export interface CustomInterface { property?: string; + additionalProperties?: Map | boolean | null | number>; }`; generator = new TypeScriptGenerator({ presets: [ diff --git a/test/helpers/NameHelpers.spec.ts b/test/helpers/NameHelpers.spec.ts new file mode 100644 index 0000000000..d6fcd7a035 --- /dev/null +++ b/test/helpers/NameHelpers.spec.ts @@ -0,0 +1,21 @@ +import { DefaultPropertyNames, getUniquePropertyName } from '../../src/helpers'; +import { CommonModel } from '../../src/models'; + +describe('NameHelpers', () => { + describe('getUniquePropertyName', () => { + test('should return correct name for additionalProperties', () => { + const model = CommonModel.toCommonModel({}); + + const additionalPropertiesName = getUniquePropertyName(model, DefaultPropertyNames.additionalProperties); + + expect(additionalPropertiesName).toEqual('additionalProperties'); + }); + test('should handle duplicate names', () => { + const model = CommonModel.toCommonModel({properties: {additionalProperties: {}}}); + + const additionalPropertiesName = getUniquePropertyName(model, DefaultPropertyNames.additionalProperties); + + expect(additionalPropertiesName).toEqual('_additionalProperties'); + }); + }); +});