Skip to content

Commit

Permalink
feat: enable go generator (#283)
Browse files Browse the repository at this point in the history
  • Loading branch information
smoya authored Jul 2, 2021
1 parent 018ba36 commit 3a80296
Show file tree
Hide file tree
Showing 15 changed files with 176 additions and 51 deletions.
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ FROM openjdk:16.0.1-jdk-slim-buster
RUN apt-get update -yq \
&& apt-get install -yq curl \
&& curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - \
&& apt-get install -yq nodejs
&& apt-get install -yq nodejs \
&& curl -fsSL https://golang.org/dl/go1.16.5.linux-amd64.tar.gz | tar -C /usr/local -xz
ENV PATH="${PATH}:/usr/local/go/bin"

COPY package-lock.json .
RUN npm install
Expand Down
1 change: 1 addition & 0 deletions docs/generators.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- [JavaScript](../src/generators/javascript/JavaScriptGenerator.ts),
- [TypeScript](../src/generators/typescript/TypeScriptGenerator.ts),
- [Java](../src/generators/java/JavaGenerator.ts).
- [Go](../src/generators/go/GoGenerator.ts).

## Generator's options

Expand Down
8 changes: 8 additions & 0 deletions docs/presets.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,11 @@ Below is a list of supported languages with their model types and corresponding
#### **Type**

There are no additional methods.

### Go

#### **Struct**

| Method | Description | Additional arguments |
|---|---|---|
| `field` | A method to extend rendered given field. | `fieldName` as a name of a given field, `field` object as a [`CommonModel`](../src/models/CommonModel.ts) instance. |
2 changes: 1 addition & 1 deletion sonar-project.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# Disable specific duplicate code since it would introduce more complexity to reduce it.
sonar.cpd.exclusions=src/generators/javascript/renderers/ClassRenderer.ts,src/generators/java/renderers/ClassRenderer.ts,src/generators/typescript/renderers/ClassRenderer.ts,src/generators/javascript/JavaScriptRenderer.ts,src/generators/typescript/renderers/EnumRenderer.ts,src/generators/java/renderers/EnumRenderer.ts
sonar.cpd.exclusions=src/generators/javascript/renderers/ClassRenderer.ts,src/generators/java/renderers/ClassRenderer.ts,src/generators/typescript/renderers/ClassRenderer.ts,src/generators/javascript/JavaScriptRenderer.ts,src/generators/java/renderers/ClassRenderer.ts,src/generators/typescript/TypeScriptRenderer.ts,src/generators/typescript/renderers/EnumRenderer.ts,src/generators/java/renderers/EnumRenderer.ts
16 changes: 15 additions & 1 deletion src/generators/go/GoGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { CommonModel, CommonInputModel, RenderOutput } from '../../models';
import { TypeHelpers, ModelKind } from '../../helpers';
import { GoPreset, GO_DEFAULT_PRESET } from './GoPreset';
import { StructRenderer } from './renderers/StructRenderer';
import { EnumRenderer } from './renderers/EnumRenderer';

export type GoOptions = CommonGeneratorOptions<GoPreset>

Expand All @@ -27,12 +28,25 @@ export class GoGenerator extends AbstractGenerator<GoOptions> {

render(model: CommonModel, inputModel: CommonInputModel): Promise<RenderOutput> {
const kind = TypeHelpers.extractKind(model);
if (kind === ModelKind.OBJECT) {
switch (kind) {
case ModelKind.OBJECT: {
return this.renderStruct(model, inputModel);
}
case ModelKind.ENUM: {
return this.renderEnum(model, inputModel);
}
}

return Promise.resolve(RenderOutput.toRenderOutput({ result: '', dependencies: [] }));
}

async renderEnum(model: CommonModel, inputModel: CommonInputModel): Promise<RenderOutput> {
const presets = this.getPresets('enum');
const renderer = new EnumRenderer(this.options, presets, model, inputModel);
const result = await renderer.runSelfPreset();
return RenderOutput.toRenderOutput({ result, dependencies: renderer.dependencies });
}

async renderStruct(model: CommonModel, inputModel: CommonInputModel): Promise<RenderOutput> {
const presets = this.getPresets('struct');
const renderer = new StructRenderer(this.options, presets, model, inputModel);
Expand Down
8 changes: 4 additions & 4 deletions src/generators/go/GoPreset.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
/* eslint-disable @typescript-eslint/ban-types */
import { AbstractRenderer } from '../AbstractRenderer';
import { Preset, CommonModel, CommonPreset, PresetArgs } from '../../models';
import { Preset, CommonModel, CommonPreset, PresetArgs, EnumPreset } from '../../models';
import { StructRenderer, GO_DEFAULT_STRUCT_PRESET } from './renderers/StructRenderer';
import { EnumRenderer, GO_DEFAULT_ENUM_PRESET } from './renderers/EnumRenderer';

export interface FieldArgs {
fieldName: string;
field: CommonModel;
}

export interface StructPreset<R extends AbstractRenderer, O extends object = any> extends CommonPreset<R, O> {
ctor?: (args: PresetArgs<R, O>) => Promise<string> | string;
field?: (args: PresetArgs<R, O> & FieldArgs) => Promise<string> | string;
getter?: (args: PresetArgs<R, O> & FieldArgs) => Promise<string> | string;
setter?: (args: PresetArgs<R, O> & FieldArgs) => Promise<string> | string;
}

export type GoPreset = Preset<{
struct: StructPreset<StructRenderer>;
enum: EnumPreset<EnumRenderer>
}>;

export const GO_DEFAULT_PRESET: GoPreset = {
struct: GO_DEFAULT_STRUCT_PRESET,
enum: GO_DEFAULT_ENUM_PRESET,
};
7 changes: 6 additions & 1 deletion src/generators/go/GoRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export abstract class GoRenderer extends AbstractRenderer<GoOptions> {

renderType(model: CommonModel): string {
if (model.$ref !== undefined) {
return FormatHelpers.toPascalCase(model.$ref, { transform: pascalCaseTransformMerge });
return `*${FormatHelpers.toPascalCase(model.$ref, { transform: pascalCaseTransformMerge })}`;
}

if (Array.isArray(model.type)) {
Expand All @@ -47,6 +47,11 @@ export abstract class GoRenderer extends AbstractRenderer<GoOptions> {
return this.toGoType(model.type, model);
}

renderComments(lines: string | string[]): string {
lines = FormatHelpers.breakLines(lines);
return lines.map(line => `// ${line}`).join('\n');
}

/* eslint-disable sonarjs/no-duplicate-string */
toGoType(type: string | undefined, model: CommonModel): string {
if (type === undefined) {
Expand Down
38 changes: 38 additions & 0 deletions src/generators/go/renderers/EnumRenderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { GoRenderer } from '../GoRenderer';
import { EnumPreset, CommonModel } from '../../../models';
import { FormatHelpers } from '../../../helpers';

/**
* Renderer for Go's `enum` type
*
* @extends GoRenderer
*/
export class EnumRenderer extends GoRenderer {
public defaultSelf(): string {
const formattedName = this.model.$id && FormatHelpers.toPascalCase(this.model.$id);
const type = this.enumType(this.model);
const doc = formattedName && this.renderCommentForEnumType(formattedName, type);

return `${doc}
type ${formattedName} ${type}`;
}

enumType(model: CommonModel): string {
if (this.model.type === undefined || Array.isArray(this.model.type)) {
return 'interface{}';
}

return this.toGoType(this.model.type, model);
}

renderCommentForEnumType(name: string, type: string): string {
const globalType = type === 'interface{}' ? 'mixed types' : type;
return this.renderComments(`${name} represents an enum of ${globalType}.`);
}
}

export const GO_DEFAULT_ENUM_PRESET: EnumPreset<EnumRenderer> = {
self({ renderer }) {
return renderer.defaultSelf();
},
};
14 changes: 0 additions & 14 deletions src/generators/go/renderers/StructRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,13 @@ type ${formattedName} struct {
${this.indent(this.renderBlock(content, 2))}
}`;
}

renderComments(lines: string | string[]): string {
lines = FormatHelpers.breakLines(lines);
return lines.map(line => `// ${line}`).join('\n');
}
}

export const GO_DEFAULT_STRUCT_PRESET: StructPreset<StructRenderer> = {
self({ renderer }) {
return renderer.defaultSelf();
},
ctor() {
return 'thisShoulBeAConstructor';
},
field({ fieldName, field, renderer }) {
return `${FormatHelpers.toPascalCase(fieldName, { transform: pascalCaseTransformMerge }) } ${ renderer.renderType(field)}`;
},
getter() {
return 'getterFunc';
},
setter() {
return 'setterFunc';
},
};
1 change: 1 addition & 0 deletions src/generators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './AbstractRenderer';
export * from './java';
export * from './javascript';
export * from './typescript';
export * from './go';
38 changes: 14 additions & 24 deletions test/blackbox/AsyncAPI.spec.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,19 @@
import * as fs from 'fs';
import * as path from 'path';
import { TypeScriptGenerator, JavaGenerator, JavaScriptGenerator } from '../../src';
import { TypeScriptGenerator, JavaGenerator, JavaScriptGenerator, GoGenerator } from '../../src';
describe('AsyncAPI JSON Schema file', () => {
test('should be generated in TypeScript', async () => {
const inputSchemaString = fs.readFileSync(path.resolve(__dirname, './docs/AsyncAPI_2_0_0.json'), 'utf8');
const inputSchema = JSON.parse(inputSchemaString);
const generator = new TypeScriptGenerator();
const generatedContent = await generator.generate(inputSchema);
expect(generatedContent).not.toBeUndefined();
expect(generatedContent.length).toBeGreaterThan(1);
});
test('should be generated in Java', async () => {
const inputSchemaString = fs.readFileSync(path.resolve(__dirname, './docs/AsyncAPI_2_0_0.json'), 'utf8');
const inputSchema = JSON.parse(inputSchemaString);
const generator = new JavaGenerator();
const generatedContent = await generator.generate(inputSchema);
expect(generatedContent).not.toBeUndefined();
expect(generatedContent.length).toBeGreaterThan(1);
});
test('should be generated in JavaScript', async () => {
const inputSchemaString = fs.readFileSync(path.resolve(__dirname, './docs/AsyncAPI_2_0_0.json'), 'utf8');
const inputSchema = JSON.parse(inputSchemaString);
const generator = new JavaScriptGenerator();
const generatedContent = await generator.generate(inputSchema);
expect(generatedContent).not.toBeUndefined();
expect(generatedContent.length).toBeGreaterThan(1);
describe.each([
['TypeScript', new TypeScriptGenerator()],
['Java', new JavaGenerator()],
['Javascript', new JavaScriptGenerator()],
['Go', new GoGenerator()],
])('code generated in %s', (_, generator) => {
test('should not be empty', async () => {
const inputSchemaString = fs.readFileSync(path.resolve(__dirname, './docs/AsyncAPI_2_0_0.json'), 'utf8');
const inputSchema = JSON.parse(inputSchemaString);
const generatedContent = await generator.generate(inputSchema);
expect(generatedContent).not.toBeUndefined();
expect(generatedContent.length).toBeGreaterThan(1);
});
});
});
14 changes: 13 additions & 1 deletion test/blackbox/Dummy.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as path from 'path';
import { TypeScriptGenerator, JavaGenerator, JavaScriptGenerator } from '../../src';
import { TypeScriptGenerator, JavaGenerator, JavaScriptGenerator, GoGenerator } from '../../src';
import { execCommand, generateModels, renderModels } from './utils/Utils';
import { renderJavaModelsToSeparateFiles } from './utils/Utils';
const fileToGenerate = path.resolve(__dirname, './docs/dummy.json');
Expand Down Expand Up @@ -53,4 +53,16 @@ describe('Dummy JSON Schema file', () => {
await execCommand(transpileAndRunCommand);
});
});

describe('should be able to generate Go', () => {
test('struct', async () => {
const generator = new GoGenerator();
const generatedModels = await generateModels(fileToGenerate, generator);
expect(generatedModels).not.toHaveLength(0);
const renderOutputPath = path.resolve(__dirname, './output/go/struct/main.go');
await renderModels(generatedModels, renderOutputPath, ['package main\n', 'func main() {}']);
const compileCommand = `go build -o ${renderOutputPath.replace('.go', '')} ${renderOutputPath}`;
await execCommand(compileCommand);
});
});
});
6 changes: 4 additions & 2 deletions test/blackbox/utils/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,15 @@ ${outputModel.result}
* @param models to write to file
* @param outputPath path to output
*/
export async function renderModels(generatedModels: OutputModel[], outputPath: string): Promise<void> {
export async function renderModels(generatedModels: OutputModel[], outputPath: string, headers?: string[]): Promise<void> {
const outputDir = path.resolve(__dirname, path.dirname(outputPath));
await fs.rm(outputDir, { recursive: true, force: true });
await fs.mkdir(outputDir, { recursive: true });
const output = generatedModels.map((model) => {
return model.result;
});

const stringOutput = headers ? `${headers.join('\n')}\n\n${output.join('\n')}` : output.join('\n');
const outputFilePath = path.resolve(__dirname, outputPath);
await fs.writeFile(outputFilePath, output.join('\n'));
await fs.writeFile(outputFilePath, stringOutput);
}
68 changes: 67 additions & 1 deletion test/generators/go/GoGenerator.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GoGenerator } from '../../../src/generators/go/GoGenerator';
import { GoGenerator } from '../../../src/generators';

describe('GoGenerator', () => {
let generator: GoGenerator;
Expand Down Expand Up @@ -75,4 +75,70 @@ type CustomStruct struct {
expect(structModel.result).toEqual(expected);
expect(structModel.dependencies).toEqual([]);
});

describe.each([
{
name: 'with enums sharing same type',
doc: {
$id: 'States',
type: 'string',
enum: ['Texas', 'Alabama', 'California'],
},
expected: `// States represents an enum of string.
type States string`,
},
{
name: 'with enums of mixed types',
doc: {
$id: 'Things',
enum: ['Texas', 1, false],
},
expected: `// Things represents an enum of mixed types.
type Things interface{}`,
},
])('should render `enum` type $name', ({doc, expected}) => {
test('should not be empty', async () => {
const inputModel = await generator.process(doc);
const model = inputModel.models[doc.$id];

let enumModel = await generator.render(model, inputModel);
expect(enumModel.result).toEqual(expected);
expect(enumModel.dependencies).toEqual([]);

enumModel = await generator.renderEnum(model, inputModel);
expect(enumModel.result).toEqual(expected);
expect(enumModel.dependencies).toEqual([]);
});
});

test('should work custom preset for `enum` type', async () => {
const doc = {
$id: 'CustomEnum',
type: 'string',
enum: ['Texas', 'Alabama', 'California'],
};
const expected = `// CustomEnum represents an enum of string.
type CustomEnum string`;

generator = new GoGenerator({ presets: [
{
enum: {
self({ content }) {
return content;
},
}
}
] });

const inputModel = await generator.process(doc);
const model = inputModel.models['CustomEnum'];

let enumModel = await generator.render(model, inputModel);
expect(enumModel.result).toEqual(expected);
expect(enumModel.dependencies).toEqual([]);

enumModel = await generator.renderEnum(model, inputModel);
expect(enumModel.result).toEqual(expected);
expect(enumModel.dependencies).toEqual([]);
});
});
2 changes: 1 addition & 1 deletion test/generators/go/GoRenderer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe('GoRenderer', () => {
test('Should render refs with pascal case (no _ prefix before numbers)', () => {
const model = new CommonModel();
model.$ref = '<anonymous-schema-1>';
expect(renderer.renderType(model)).toEqual('AnonymousSchema1');
expect(renderer.renderType(model)).toEqual('*AnonymousSchema1');
});
test('Should render union types with one type as slice of that type', () => {
const model = CommonModel.toCommonModel({ type: ['number'] });
Expand Down

0 comments on commit 3a80296

Please sign in to comment.