Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: complete model rendering and file output for Go generator #540

Merged
merged 11 commits into from
Jan 11, 2022
1 change: 1 addition & 0 deletions docs/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Supported by:
- Java
- TypeScript
- C#
- Go
- JavaScript

> It is not supported in browsers.
Expand Down
24 changes: 24 additions & 0 deletions src/generators/go/GoFileGenerator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { GoGenerator, GoRenderCompleteModelOptions } from './GoGenerator';
import { CommonInputModel, OutputModel } from '../../models';
import * as path from 'path';
import { AbstractFileGenerator } from '../AbstractFileGenerator';
import { FileHelpers } from '../../helpers';

export class GoFileGenerator extends GoGenerator implements AbstractFileGenerator<GoRenderCompleteModelOptions> {
/**
* Generates all the models to an output directory each model with their own separate files.
*
* @param input
* @param outputDirectory where you want the models generated to
* @param options
*/
public async generateToFiles(input: Record<string, unknown> | CommonInputModel, outputDirectory: string, options: GoRenderCompleteModelOptions): Promise<OutputModel[]> {
let generatedModels = await this.generateCompleteModels(input, options);
generatedModels = generatedModels.filter((outputModel) => { return outputModel.modelName !== undefined; });
for (const outputModel of generatedModels) {
const filePath = path.resolve(outputDirectory, `${outputModel.modelName}.go`);
await FileHelpers.writerToFileSystem(outputModel.result, filePath);
}
return generatedModels;
}
}
31 changes: 27 additions & 4 deletions src/generators/go/GoGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { GoPreset, GO_DEFAULT_PRESET } from './GoPreset';
import { StructRenderer } from './renderers/StructRenderer';
import { EnumRenderer } from './renderers/EnumRenderer';
import { pascalCaseTransformMerge } from 'change-case';
import { Logger } from '../../';
import { Logger } from '../../utils/LoggingInterface';

/**
* The Go naming convention type
Expand Down Expand Up @@ -37,10 +37,14 @@ export interface GoOptions extends CommonGeneratorOptions<GoPreset> {
namingConvention?: GoNamingConvention;
}

export interface GoRenderCompleteModelOptions {
packageName: string
}

/**
* Generator for Go
*/
export class GoGenerator extends AbstractGenerator<GoOptions> {
export class GoGenerator extends AbstractGenerator<GoOptions, GoRenderCompleteModelOptions> {
static defaultOptions: GoOptions = {
...defaultGeneratorOptions,
defaultPreset: GO_DEFAULT_PRESET,
Expand Down Expand Up @@ -69,8 +73,27 @@ export class GoGenerator extends AbstractGenerator<GoOptions> {
return Promise.resolve(RenderOutput.toRenderOutput({ result: '', renderedName: '', dependencies: [] }));
}

renderCompleteModel(): Promise<RenderOutput> {
throw new Error('Method not implemented.');
/**
* Render a complete model result where the model code, library and model dependencies are all bundled appropriately.
*
* @param model
* @param inputModel
* @param options
*/
async renderCompleteModel(model: CommonModel, inputModel: CommonInputModel, options: GoRenderCompleteModelOptions): Promise<RenderOutput> {
const outputModel = await this.render(model, inputModel);
let importCode = '';
if (outputModel.dependencies.length > 0) {
const dependencies = outputModel.dependencies.map((dependency) => {return `"${ dependency }"`;}).join('\n');
importCode = `import (
${dependencies}
)`;
}
const outputContent = `
package ${options.packageName}
${importCode}
${outputModel.result}`;
return RenderOutput.toRenderOutput({ result: outputContent, renderedName: outputModel.renderedName, dependencies: outputModel.dependencies });
}

async renderEnum(model: CommonModel, inputModel: CommonInputModel): Promise<RenderOutput> {
Expand Down
1 change: 1 addition & 0 deletions src/generators/go/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './GoGenerator';
export * from './GoFileGenerator';
export { GO_DEFAULT_PRESET } from './GoPreset';
export type { GoPreset } from './GoPreset';
1 change: 0 additions & 1 deletion src/generators/java/renderers/ClassRenderer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { JavaRenderer } from '../JavaRenderer';

import { CommonModel, ClassPreset, PropertyType } from '../../../models';
import { DefaultPropertyNames, FormatHelpers, getUniquePropertyName } from '../../../helpers';

Expand Down
15 changes: 9 additions & 6 deletions test/blackbox/blackbox.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import * as path from 'path';
import * as fs from 'fs';
import { GoGenerator, CSharpFileGenerator, JavaFileGenerator, JAVA_COMMON_PRESET, TypeScriptFileGenerator, JavaScriptFileGenerator } from '../../src';
import { GoFileGenerator, CSharpFileGenerator, JavaFileGenerator, JAVA_COMMON_PRESET, TypeScriptFileGenerator, JavaScriptFileGenerator } from '../../src';
import { execCommand, generateModels, renderModels, renderModelsToSeparateFiles } from './utils/Utils';

/**
Expand Down Expand Up @@ -189,12 +189,15 @@ describe.each(filesToTest)('Should be able to generate with inputs', ({file, out

describe('should be able to generate Go', () => {
test('struct', async () => {
const generator = new GoGenerator();
const generatedModels = await generateModels(fileToGenerateFor, generator);
const generator = new GoFileGenerator();
const inputFileContent = await fs.promises.readFile(fileToGenerateFor);
const input = JSON.parse(String(inputFileContent));
const renderOutputPath = path.resolve(outputDirectoryPath, './go/struct/');

const generatedModels = await generator.generateToFiles(input, renderOutputPath, {packageName: 'test_package_name'});
expect(generatedModels).not.toHaveLength(0);
const renderOutputPath = path.resolve(outputDirectoryPath, './go/struct/main.go');
await renderModels(generatedModels, renderOutputPath, ['package main\n', 'func main() {}']);
const compileCommand = `go build -o ${renderOutputPath.replace('.go', '')} ${renderOutputPath}`;

const compileCommand = `gofmt ${renderOutputPath}`;
await execCommand(compileCommand);
});
});
Expand Down
51 changes: 51 additions & 0 deletions test/generators/go/GoFileGenerator.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { CommonInputModel, CommonModel, FileHelpers, GoFileGenerator, OutputModel } from '../../../src';
import * as path from 'path';

describe('GoFileGenerator', () => {
afterEach(() => {
jest.restoreAllMocks();
});

describe('generateToFile()', () => {
const doc = {
$id: 'CustomClass',
type: 'object',
additionalProperties: true,
properties: {
someProp: { type: 'string' },
someEnum: {
$id: 'CustomEnum',
type: 'string',
enum: ['Texas', 'Alabama', 'California'],
}
}
};
test('should throw accurate error if file cannot be written', async () => {
const generator = new GoFileGenerator();
const expectedError = new Error('write error');
jest.spyOn(FileHelpers, 'writerToFileSystem').mockRejectedValue(expectedError);
jest.spyOn(generator, 'generateCompleteModels').mockResolvedValue([new OutputModel('content', new CommonModel(), '', new CommonInputModel(), [])]);

await expect(generator.generateToFiles(doc, '/test/', {packageName: 'some_package'})).rejects.toEqual(expectedError);
expect(generator.generateCompleteModels).toHaveBeenCalledTimes(1);
expect(FileHelpers.writerToFileSystem).toHaveBeenCalledTimes(1);
});
test('should try and generate models to files', async () => {
const generator = new GoFileGenerator();
const outputDir = './test';
const expectedOutputDirPath = path.resolve(outputDir);
const expectedOutputFilePath = path.resolve(`${outputDir}/Test.go`);
const expectedWriteToFileParameters = [
'content',
expectedOutputFilePath,
];
jest.spyOn(FileHelpers, 'writerToFileSystem').mockResolvedValue(undefined);
jest.spyOn(generator, 'generateCompleteModels').mockResolvedValue([new OutputModel('content', new CommonModel(), 'Test', new CommonInputModel(), [])]);

await generator.generateToFiles(doc, expectedOutputDirPath, {packageName: 'some_package'});
expect(generator.generateCompleteModels).toHaveBeenCalledTimes(1);
expect(FileHelpers.writerToFileSystem).toHaveBeenCalledTimes(1);
expect((FileHelpers.writerToFileSystem as jest.Mock).mock.calls[0]).toEqual(expectedWriteToFileParameters);
});
});
});
67 changes: 67 additions & 0 deletions test/generators/go/GoGenerator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,71 @@ type CustomEnum string`;
expect(enumModel.result).toEqual(expected);
expect(enumModel.dependencies).toEqual([]);
});
describe('generateCompleteModels()', () => {
test('should render models', async () => {
const doc = {
$id: 'Address',
type: 'object',
properties: {
street_name: { type: 'string' },
city: { type: 'string', description: 'City description' },
state: { type: 'string' },
house_number: { type: 'number' },
marriage: { type: 'boolean', description: 'Status if marriage live in given house' },
members: { oneOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }], },
array_type: { type: 'array', items: [{ type: 'string' }, { type: 'number' }] },
other_model: { type: 'object', $id: 'OtherModel', properties: {street_name: { type: 'string' }} },
},
patternProperties: {
'^S(.?*)test&': {
type: 'string'
}
},
required: ['street_name', 'city', 'state', 'house_number', 'array_type'],
};
const config = {packageName: 'some_package'};
const models = await generator.generateCompleteModels(doc, config);
expect(models).toHaveLength(2);
expect(models[0].result).toMatchSnapshot();
expect(models[1].result).toMatchSnapshot();
});

test('should render dependencies', async () => {
const doc = {
$id: 'Address',
type: 'object',
properties: {
street_name: { type: 'string' },
city: { type: 'string', description: 'City description' },
state: { type: 'string' },
house_number: { type: 'number' },
marriage: { type: 'boolean', description: 'Status if marriage live in given house' },
members: { oneOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }], },
array_type: { type: 'array', items: [{ type: 'string' }, { type: 'number' }] },
other_model: { type: 'object', $id: 'OtherModel', properties: {street_name: { type: 'string' }} },
},
patternProperties: {
'^S(.?*)test&': {
type: 'string'
}
},
required: ['street_name', 'city', 'state', 'house_number', 'array_type'],
};
generator = new GoGenerator({ presets: [
{
struct: {
self({ renderer, content }) {
renderer.addDependency('time');
return content;
},
}
}
] });
const config = {packageName: 'some_package'};
const models = await generator.generateCompleteModels(doc, config);
expect(models).toHaveLength(2);
expect(models[0].result).toMatchSnapshot();
expect(models[1].result).toMatchSnapshot();
});
});
});
65 changes: 65 additions & 0 deletions test/generators/go/__snapshots__/GoGenerator.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`GoGenerator generateCompleteModels() should render dependencies 1`] = `
"
package some_package
import (
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved
\\"time\\"
)
// Address represents a Address model.
type Address struct {
StreetName string
City string
State string
HouseNumber float64
Marriage bool
Members []interface{}
ArrayType []interface{}
OtherModel *OtherModel
AdditionalProperties map[string][]interface{}
STestPatternProperties map[string]string
}"
`;

exports[`GoGenerator generateCompleteModels() should render dependencies 2`] = `
"
package some_package
import (
\\"time\\"
)
// OtherModel represents a OtherModel model.
type OtherModel struct {
StreetName string
AdditionalProperties map[string][]interface{}
}"
`;

exports[`GoGenerator generateCompleteModels() should render models 1`] = `
"
package some_package

// Address represents a Address model.
type Address struct {
StreetName string
City string
State string
HouseNumber float64
Marriage bool
Members []interface{}
ArrayType []interface{}
OtherModel *OtherModel
AdditionalProperties map[string][]interface{}
STestPatternProperties map[string]string
}"
`;

exports[`GoGenerator generateCompleteModels() should render models 2`] = `
"
package some_package

// OtherModel represents a OtherModel model.
type OtherModel struct {
StreetName string
AdditionalProperties map[string][]interface{}
}"
`;