Skip to content

Commit

Permalink
feat: render additionalProperties (#240)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonaslagoni authored Jun 17, 2021
1 parent bae1675 commit 60fcc07
Show file tree
Hide file tree
Showing 22 changed files with 260 additions and 87 deletions.
10 changes: 3 additions & 7 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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": [
{
Expand Down
10 changes: 10 additions & 0 deletions src/generators/java/JavaRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export abstract class JavaRenderer extends AbstractRenderer<JavaOptions> {
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
Expand All @@ -30,6 +35,11 @@ export abstract class JavaRenderer extends AbstractRenderer<JavaOptions> {
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':
Expand Down
11 changes: 7 additions & 4 deletions src/generators/java/presets/CommonPreset.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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');
Expand All @@ -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() {
Expand All @@ -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" +`
);

Expand Down
6 changes: 0 additions & 6 deletions src/generators/java/presets/ConstraintsPreset.ts
Original file line number Diff line number Diff line change
@@ -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
*
Expand All @@ -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);
Expand Down
5 changes: 0 additions & 5 deletions src/generators/java/presets/DescriptioPreset.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { JavaRenderer } from '../JavaRenderer';
import { JavaPreset } from '../JavaPreset';

import { FormatHelpers } from '../../../helpers';
import { CommonModel } from '../../../models';

Expand All @@ -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');

Expand Down
12 changes: 8 additions & 4 deletions src/generators/java/presets/JacksonPreset.ts
Original file line number Diff line number Diff line change
@@ -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}
*/
Expand All @@ -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]);
},
}
};
61 changes: 44 additions & 17 deletions src/generators/java/renderers/ClassRenderer.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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} {
Expand All @@ -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<string> {
return this.runPreset('property', { propertyName, property });
runPropertyPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise<string> {
return this.runPreset('property', { propertyName, property, type});
}

async renderAccessors(): Promise<string> {
Expand All @@ -53,36 +63,53 @@ ${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<string> {
return this.runPreset('getter', { propertyName, property });
runGetterPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise<string> {
return this.runPreset('getter', { propertyName, property, type });
}

runSetterPreset(propertyName: string, property: CommonModel): Promise<string> {
return this.runPreset('setter', { propertyName, property });
runSetterPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise<string> {
return this.runPreset('setter', { propertyName, property, type });
}
}

export const JAVA_DEFAULT_CLASS_PRESET: ClassPreset<ClassRenderer> = {
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<String, ${propertyType}>`;
}
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<String, ${getterType}>`;
}
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<String, ${setterType}>`;
}
return `public void set${setterName}(${setterType} ${propertyName}) { this.${propertyName} = ${propertyName}; }`;
},
};
13 changes: 9 additions & 4 deletions src/generators/javascript/JavaScriptRenderer.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<string> {
return this.runPreset('property', { propertyName, property });
runPropertyPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise<string> {
return this.runPreset('property', { propertyName, property, type});
}
}
31 changes: 25 additions & 6 deletions src/generators/typescript/TypeScriptRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<string> {
const properties = this.model.properties || {};
const content: string[] = [];
Expand All @@ -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<String, ${signature}>;`;
default:
return '';
}
}

runPropertyPreset(propertyName: string, property: CommonModel): Promise<string> {
return this.runPreset('property', { propertyName, property });
runPropertyPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise<string> {
return this.runPreset('property', { propertyName, property, type });
}
}
Loading

0 comments on commit 60fcc07

Please sign in to comment.