Skip to content

Commit 0b41dc6

Browse files
authored
feat: example function for TS class renderer (#327)
1 parent 065de97 commit 0b41dc6

File tree

6 files changed

+279
-2
lines changed

6 files changed

+279
-2
lines changed

src/generators/typescript/presets/CommonPreset.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import { TypeScriptRenderer } from '../TypeScriptRenderer';
22
import { TypeScriptPreset } from '../TypeScriptPreset';
33
import { getUniquePropertyName, DefaultPropertyNames } from '../../../helpers';
44
import { CommonModel } from '../../../models';
5+
import renderExampleFunction from './utils/ExampleFunction';
56

67
export interface TypeScriptCommonPresetOptions {
78
marshalling: boolean;
9+
example: boolean;
810
}
911

1012
function realizePropertyFactory(prop: string) {
@@ -157,7 +159,7 @@ ${renderer.indent(unmarshalAdditionalProperties, 4)}
157159
}
158160

159161
/**
160-
* Preset which adds `marshal`, `unmarshal` functions to class.
162+
* Preset which adds `marshal`, `unmarshal`, `example` functions to class.
161163
*
162164
* @implements {TypeScriptPreset}
163165
*/
@@ -171,6 +173,10 @@ export const TS_COMMON_PRESET: TypeScriptPreset = {
171173
blocks.push(renderMarshal({ renderer, model }));
172174
blocks.push(renderUnmarshal({ renderer, model }));
173175
}
176+
177+
if (options.example === true) {
178+
blocks.push(renderExampleFunction({ renderer, model }));
179+
}
174180

175181
return renderer.renderBlock([content, ...blocks], 2);
176182
},
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { TypeScriptRenderer } from '../../TypeScriptRenderer';
2+
import { CommonModel } from '../../../../models';
3+
4+
/**
5+
* Inferring first acceptable value from the model.
6+
*
7+
* @param model
8+
* @param renderer
9+
*/
10+
export function renderValueFromModel(model: CommonModel, renderer: TypeScriptRenderer): string | undefined {
11+
if (Array.isArray(model.enum) && model.enum.length > 0) {
12+
return JSON.stringify(model.enum[0]);
13+
}
14+
if (model.$ref !== undefined) {
15+
return `${renderer.nameType(model.$ref)}.example()`;
16+
}
17+
if (Array.isArray(model.type)) {
18+
if (model.type.length > 0) {
19+
return renderValueFromType(model.type[0], model, renderer);
20+
}
21+
return undefined;
22+
}
23+
return renderValueFromType(model.type, model, renderer);
24+
}
25+
26+
export function renderValueFromType(modelType: string | undefined, model: CommonModel, renderer: TypeScriptRenderer): string | undefined {
27+
if (modelType === undefined) {
28+
return undefined;
29+
}
30+
switch (modelType) {
31+
case 'string':
32+
return '"string"';
33+
case 'integer':
34+
case 'number':
35+
return '0';
36+
case 'boolean':
37+
return 'true';
38+
case 'array': {
39+
if (model.items === undefined) {
40+
return '[]';
41+
}
42+
//Check and see if it should be rendered as tuples
43+
if (Array.isArray(model.items)) {
44+
const arrayValues = model.items.map((item) => {
45+
return renderValueFromModel(item, renderer);
46+
});
47+
return `[${arrayValues.join(', ')}]`;
48+
}
49+
const arrayType = renderValueFromModel(model.items, renderer);
50+
return `[${arrayType}]`;
51+
}
52+
}
53+
return undefined;
54+
}
55+
/**
56+
* Render `example` function based on model properties.
57+
*/
58+
export default function renderExampleFunction({ renderer, model }: {
59+
renderer: TypeScriptRenderer,
60+
model: CommonModel,
61+
}): string {
62+
const properties = model.properties || {};
63+
const setProperties = [];
64+
for (const [propertyName, property] of Object.entries(properties)) {
65+
const formattedPropertyName = renderer.nameProperty(propertyName, property);
66+
const potentialRenderedValue = renderValueFromModel(property, renderer);
67+
if (potentialRenderedValue === undefined) {
68+
//Unable to determine example value, skip property.
69+
continue;
70+
}
71+
setProperties.push(` instance.${formattedPropertyName} = ${potentialRenderedValue};`);
72+
}
73+
const formattedModelName = renderer.nameType(model.$id);
74+
return `public static example(): ${formattedModelName} {
75+
const instance = new ${formattedModelName}({} as any);
76+
${(setProperties.join('\n'))}
77+
return instance;
78+
}`;
79+
}

test/blackbox/Dummy.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as path from 'path';
2-
import { TypeScriptGenerator, JavaGenerator, JavaScriptGenerator, GoGenerator, CSharpGenerator } from '../../src';
2+
import { TypeScriptGenerator, JavaGenerator, JavaScriptGenerator, GoGenerator, CSharpGenerator, TS_COMMON_PRESET } from '../../src';
33
import { execCommand, generateModels, renderModels, renderModelsToSeparateFiles } from './utils/Utils';
44
const fileToGenerate = path.resolve(__dirname, './docs/dummy.json');
55
describe('Dummy JSON Schema file', () => {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { TypeScriptGenerator, TS_COMMON_PRESET } from '../../../../src/generators';
2+
const doc = {
3+
$id: 'Test',
4+
type: 'object',
5+
additionalProperties: true,
6+
required: ['string prop'],
7+
properties: {
8+
'string prop': { type: 'string' },
9+
numberProp: { type: 'number' },
10+
objectProp: { type: 'object', $id: 'NestedTest', properties: {stringProp: { type: 'string' }}}
11+
},
12+
patternProperties: {
13+
'^S(.?)test': {
14+
type: 'string'
15+
}
16+
},
17+
};
18+
describe('Example function generation', () => {
19+
test('should render example function for model', async () => {
20+
const generator = new TypeScriptGenerator({
21+
presets: [
22+
{
23+
preset: TS_COMMON_PRESET,
24+
options: {
25+
example: true
26+
}
27+
}
28+
]
29+
});
30+
const inputModel = await generator.process(doc);
31+
const testModel = inputModel.models['Test'];
32+
const nestedTestModel = inputModel.models['NestedTest'];
33+
34+
const testClass = await generator.renderClass(testModel, inputModel);
35+
const nestedTestClass = await generator.renderClass(nestedTestModel, inputModel);
36+
37+
expect(testClass.result).toMatchSnapshot();
38+
expect(nestedTestClass.result).toMatchSnapshot();
39+
});
40+
});
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Example function generation should render example function for model 1`] = `
4+
"export class Test {
5+
private _stringProp: string;
6+
private _numberProp?: number;
7+
private _objectProp?: NestedTest;
8+
private _additionalProperties?: Map<String, object | string | number | Array<unknown> | boolean | null | number>;
9+
private _sTestPatternProperties?: Map<String, string>;
10+
11+
constructor(input: {
12+
stringProp: string,
13+
numberProp?: number,
14+
objectProp?: NestedTest,
15+
}) {
16+
this._stringProp = input.stringProp;
17+
this._numberProp = input.numberProp;
18+
this._objectProp = input.objectProp;
19+
}
20+
21+
get stringProp(): string { return this._stringProp; }
22+
set stringProp(stringProp: string) { this._stringProp = stringProp; }
23+
24+
get numberProp(): number | undefined { return this._numberProp; }
25+
set numberProp(numberProp: number | undefined) { this._numberProp = numberProp; }
26+
27+
get objectProp(): NestedTest | undefined { return this._objectProp; }
28+
set objectProp(objectProp: NestedTest | undefined) { this._objectProp = objectProp; }
29+
30+
get additionalProperties(): Map<String, object | string | number | Array<unknown> | boolean | null | number> | undefined { return this._additionalProperties; }
31+
set additionalProperties(additionalProperties: Map<String, object | string | number | Array<unknown> | boolean | null | number> | undefined) { this._additionalProperties = additionalProperties; }
32+
33+
get sTestPatternProperties(): Map<String, string> | undefined { return this._sTestPatternProperties; }
34+
set sTestPatternProperties(sTestPatternProperties: Map<String, string> | undefined) { this._sTestPatternProperties = sTestPatternProperties; }
35+
36+
public static example(): Test {
37+
const instance = new Test({} as any);
38+
instance.stringProp = \\"string\\";
39+
instance.numberProp = 0;
40+
instance.objectProp = NestedTest.example();
41+
return instance;
42+
}
43+
}"
44+
`;
45+
46+
exports[`Example function generation should render example function for model 2`] = `
47+
"export class NestedTest {
48+
private _stringProp?: string;
49+
private _additionalProperties?: Map<String, object | string | number | Array<unknown> | boolean | null | number>;
50+
51+
constructor(input: {
52+
stringProp?: string,
53+
}) {
54+
this._stringProp = input.stringProp;
55+
}
56+
57+
get stringProp(): string | undefined { return this._stringProp; }
58+
set stringProp(stringProp: string | undefined) { this._stringProp = stringProp; }
59+
60+
get additionalProperties(): Map<String, object | string | number | Array<unknown> | boolean | null | number> | undefined { return this._additionalProperties; }
61+
set additionalProperties(additionalProperties: Map<String, object | string | number | Array<unknown> | boolean | null | number> | undefined) { this._additionalProperties = additionalProperties; }
62+
63+
public static example(): NestedTest {
64+
const instance = new NestedTest({} as any);
65+
instance.stringProp = \\"string\\";
66+
return instance;
67+
}
68+
}"
69+
`;
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { TypeScriptGenerator } from '../../../../../src/generators';
2+
import { renderValueFromModel } from '../../../../../src/generators/typescript/presets/utils/ExampleFunction';
3+
import { TypeScriptRenderer } from '../../../../../src/generators/typescript/TypeScriptRenderer';
4+
import { CommonInputModel, CommonModel } from '../../../../../src/models';
5+
class MockTypeScriptRenderer extends TypeScriptRenderer {
6+
7+
}
8+
const renderer = new MockTypeScriptRenderer(TypeScriptGenerator.defaultOptions, new TypeScriptGenerator(), [], new CommonModel(), new CommonInputModel());
9+
describe('Marshalling preset', () => {
10+
describe('.renderValueFromModel()', () => {
11+
describe('enums', () => {
12+
test('Should render strings correctly', () => {
13+
const input = CommonModel.toCommonModel({enum: ['somevalue']});
14+
const output = renderValueFromModel(input, renderer);
15+
expect(output).toEqual('"somevalue"');
16+
});
17+
test('Should render numbers correctly', () => {
18+
const input = CommonModel.toCommonModel({enum: [1]});
19+
const output = renderValueFromModel(input, renderer);
20+
expect(output).toEqual('1');
21+
});
22+
test('Should ignore multiple values', () => {
23+
const input = CommonModel.toCommonModel({enum: [1, 'somevalue']});
24+
const output = renderValueFromModel(input, renderer);
25+
expect(output).toEqual('1');
26+
});
27+
test('Should use first value if there is more then one', () => {
28+
const input = CommonModel.toCommonModel({enum: []});
29+
const output = renderValueFromModel(input, renderer);
30+
expect(output).toBeUndefined();
31+
});
32+
});
33+
test('should render refs correctly', () => {
34+
const input = CommonModel.toCommonModel({$ref: 'SomeOtherModel'});
35+
const output = renderValueFromModel(input, renderer);
36+
expect(output).toEqual('SomeOtherModel.example()');
37+
});
38+
describe('types', () => {
39+
test('Should render strings correctly', () => {
40+
const input = CommonModel.toCommonModel({type: 'string'});
41+
const output = renderValueFromModel(input, renderer);
42+
expect(output).toEqual('"string"');
43+
});
44+
test('Should render numbers correctly', () => {
45+
const input = CommonModel.toCommonModel({type: 'number'});
46+
const output = renderValueFromModel(input, renderer);
47+
expect(output).toEqual('0');
48+
});
49+
test('Should render booleans correctly', () => {
50+
const input = CommonModel.toCommonModel({type: 'boolean'});
51+
const output = renderValueFromModel(input, renderer);
52+
expect(output).toEqual('true');
53+
});
54+
test('Should use first value if there is more then one', () => {
55+
const input = CommonModel.toCommonModel({type: ['boolean', 'string']});
56+
const output = renderValueFromModel(input, renderer);
57+
expect(output).toEqual('true');
58+
});
59+
describe('array', () => {
60+
test('should not render anything if no items are defined', () => {
61+
const input = CommonModel.toCommonModel({type: 'array'});
62+
const output = renderValueFromModel(input, renderer);
63+
expect(output).toEqual('[]');
64+
});
65+
test('should render multiple array values', () => {
66+
const input = CommonModel.toCommonModel({type: 'array', items: [{type: 'string'}, {type: 'number'}]});
67+
const output = renderValueFromModel(input, renderer);
68+
expect(output).toEqual('["string", 0]');
69+
});
70+
test('should render single array value', () => {
71+
const input = CommonModel.toCommonModel({type: 'array', items: {type: 'string'}});
72+
const output = renderValueFromModel(input, renderer);
73+
expect(output).toEqual('["string"]');
74+
});
75+
});
76+
test('Should ignore if none are present', () => {
77+
const input = CommonModel.toCommonModel({type: []});
78+
const output = renderValueFromModel(input, renderer);
79+
expect(output).toBeUndefined();
80+
});
81+
});
82+
});
83+
});

0 commit comments

Comments
 (0)