diff --git a/docs/advanced.md b/docs/advanced.md index ffdf990d04..51bf63dc91 100644 --- a/docs/advanced.md +++ b/docs/advanced.md @@ -34,6 +34,7 @@ Supported by: - Java - TypeScript - C# +- Go - JavaScript > It is not supported in browsers. diff --git a/src/generators/go/GoFileGenerator.ts b/src/generators/go/GoFileGenerator.ts new file mode 100644 index 0000000000..e0ef44aae8 --- /dev/null +++ b/src/generators/go/GoFileGenerator.ts @@ -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 { + /** + * 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 | CommonInputModel, outputDirectory: string, options: GoRenderCompleteModelOptions): Promise { + 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; + } +} diff --git a/src/generators/go/GoGenerator.ts b/src/generators/go/GoGenerator.ts index 164978fa82..6c31d4bc1b 100644 --- a/src/generators/go/GoGenerator.ts +++ b/src/generators/go/GoGenerator.ts @@ -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 @@ -37,10 +37,14 @@ export interface GoOptions extends CommonGeneratorOptions { namingConvention?: GoNamingConvention; } +export interface GoRenderCompleteModelOptions { + packageName: string +} + /** * Generator for Go */ -export class GoGenerator extends AbstractGenerator { +export class GoGenerator extends AbstractGenerator { static defaultOptions: GoOptions = { ...defaultGeneratorOptions, defaultPreset: GO_DEFAULT_PRESET, @@ -69,8 +73,27 @@ export class GoGenerator extends AbstractGenerator { return Promise.resolve(RenderOutput.toRenderOutput({ result: '', renderedName: '', dependencies: [] })); } - renderCompleteModel(): Promise { - 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 { + 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 { diff --git a/src/generators/go/index.ts b/src/generators/go/index.ts index defe967b04..e408d8ecc4 100644 --- a/src/generators/go/index.ts +++ b/src/generators/go/index.ts @@ -1,3 +1,4 @@ export * from './GoGenerator'; +export * from './GoFileGenerator'; export { GO_DEFAULT_PRESET } from './GoPreset'; export type { GoPreset } from './GoPreset'; diff --git a/src/generators/java/renderers/ClassRenderer.ts b/src/generators/java/renderers/ClassRenderer.ts index 03aa856582..f006a55ef3 100644 --- a/src/generators/java/renderers/ClassRenderer.ts +++ b/src/generators/java/renderers/ClassRenderer.ts @@ -1,5 +1,4 @@ import { JavaRenderer } from '../JavaRenderer'; - import { CommonModel, ClassPreset, PropertyType } from '../../../models'; import { DefaultPropertyNames, FormatHelpers, getUniquePropertyName } from '../../../helpers'; diff --git a/test/blackbox/blackbox.spec.ts b/test/blackbox/blackbox.spec.ts index 2751c56de8..5dcc37023e 100644 --- a/test/blackbox/blackbox.spec.ts +++ b/test/blackbox/blackbox.spec.ts @@ -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'; /** @@ -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); }); }); diff --git a/test/generators/go/GoFileGenerator.spec.ts b/test/generators/go/GoFileGenerator.spec.ts new file mode 100644 index 0000000000..396e55b17d --- /dev/null +++ b/test/generators/go/GoFileGenerator.spec.ts @@ -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); + }); + }); +}); diff --git a/test/generators/go/GoGenerator.spec.ts b/test/generators/go/GoGenerator.spec.ts index 2f5de7471d..0560f257f5 100644 --- a/test/generators/go/GoGenerator.spec.ts +++ b/test/generators/go/GoGenerator.spec.ts @@ -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(); + }); + }); }); diff --git a/test/generators/go/__snapshots__/GoGenerator.spec.ts.snap b/test/generators/go/__snapshots__/GoGenerator.spec.ts.snap new file mode 100644 index 0000000000..4a89f5aca1 --- /dev/null +++ b/test/generators/go/__snapshots__/GoGenerator.spec.ts.snap @@ -0,0 +1,65 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`GoGenerator generateCompleteModels() should render dependencies 1`] = ` +" +package some_package +import ( + \\"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{} +}" +`;