diff --git a/src/generators/go/GoConstrainer.ts b/src/generators/go/GoConstrainer.ts index 72b5d8ee64..e4b471bb7d 100644 --- a/src/generators/go/GoConstrainer.ts +++ b/src/generators/go/GoConstrainer.ts @@ -2,9 +2,9 @@ import { TypeMapping } from '../../helpers'; import { defaultEnumKeyConstraints, defaultEnumValueConstraints } from './constrainer/EnumConstrainer'; import { defaultModelNameConstraints } from './constrainer/ModelNameConstrainer'; import { defaultPropertyKeyConstraints } from './constrainer/PropertyKeyConstrainer'; -import { GoRenderer } from './GoRenderer'; +import { GoOptions } from './GoGenerator'; -export const GoDefaultTypeMapping: TypeMapping = { +export const GoDefaultTypeMapping: TypeMapping = { Object ({constrainedModel}): string { return constrainedModel.name; }, diff --git a/src/generators/go/GoFileGenerator.ts b/src/generators/go/GoFileGenerator.ts index e0ef44aae8..ba519e7870 100644 --- a/src/generators/go/GoFileGenerator.ts +++ b/src/generators/go/GoFileGenerator.ts @@ -1,5 +1,5 @@ import { GoGenerator, GoRenderCompleteModelOptions } from './GoGenerator'; -import { CommonInputModel, OutputModel } from '../../models'; +import { InputMetaModel, OutputModel } from '../../models'; import * as path from 'path'; import { AbstractFileGenerator } from '../AbstractFileGenerator'; import { FileHelpers } from '../../helpers'; @@ -12,7 +12,7 @@ export class GoFileGenerator extends GoGenerator implements AbstractFileGenerato * @param outputDirectory where you want the models generated to * @param options */ - public async generateToFiles(input: Record | CommonInputModel, outputDirectory: string, options: GoRenderCompleteModelOptions): Promise { + public async generateToFiles(input: Record | InputMetaModel, 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) { diff --git a/src/generators/go/GoGenerator.ts b/src/generators/go/GoGenerator.ts index 0dba394172..b8209b724c 100644 --- a/src/generators/go/GoGenerator.ts +++ b/src/generators/go/GoGenerator.ts @@ -3,54 +3,16 @@ import { CommonGeneratorOptions, defaultGeneratorOptions } from '../AbstractGenerator'; -import { CommonModel, CommonInputModel, RenderOutput } from '../../models'; -import { TypeHelpers, ModelKind, FormatHelpers, Constraints, TypeMapping } from '../../helpers'; +import { InputMetaModel, RenderOutput, ConstrainedObjectModel, ConstrainedEnumModel, ConstrainedMetaModel, MetaModel } from '../../models'; +import { constrainMetaModel, Constraints, split, TypeMapping } from '../../helpers'; 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 '../../utils/LoggingInterface'; -import { isReservedGoKeyword } from './Constants'; import { GoDefaultConstraints, GoDefaultTypeMapping } from './GoConstrainer'; -import { GoRenderer } from './GoRenderer'; -/** - * The Go naming convention type - */ -export type GoNamingConvention = { - type?: (name: string | undefined, ctx: { model: CommonModel, inputModel: CommonInputModel, reservedKeywordCallback?: (name: string) => boolean }) => string; - field?: (name: string | undefined, ctx: { model: CommonModel, inputModel: CommonInputModel, field?: CommonModel, reservedKeywordCallback?: (name: string) => boolean }) => string; -}; - -/** - * A GoNamingConvention implementation for Go - */ -export const GoNamingConventionImplementation: GoNamingConvention = { - type: (name: string | undefined, ctx) => { - if (!name) { return ''; } - let formattedName = FormatHelpers.toPascalCase(name, { transform: pascalCaseTransformMerge }); - if (ctx.reservedKeywordCallback !== undefined && ctx.reservedKeywordCallback(formattedName)) { - formattedName = FormatHelpers.toPascalCase(`reserved_${formattedName}`); - } - return formattedName; - }, - // eslint-disable-next-line sonarjs/no-identical-functions - field: (name: string | undefined, ctx) => { - if (!name) { return ''; } - let formattedName = FormatHelpers.toPascalCase(name, { transform: pascalCaseTransformMerge }); - if (ctx.reservedKeywordCallback !== undefined && ctx.reservedKeywordCallback(formattedName)) { - formattedName = FormatHelpers.toPascalCase(`reserved_${formattedName}`); - if (Object.keys(ctx.model.properties || {}).includes(formattedName)) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return GoNamingConventionImplementation.field!(`reserved_${formattedName}`, ctx); - } - } - return formattedName; - } -}; export interface GoOptions extends CommonGeneratorOptions { - namingConvention?: GoNamingConvention; - typeMapping: TypeMapping; + typeMapping: TypeMapping; constraints: Constraints } @@ -65,33 +27,45 @@ export class GoGenerator extends AbstractGenerator = GoGenerator.defaultOptions, ) { - const mergedOptions = {...GoGenerator.defaultOptions, ...options}; + const realizedOptions = {...GoGenerator.defaultOptions, ...options}; - super('Go', GoGenerator.defaultOptions, mergedOptions); + super('Go', realizedOptions); } - reservedGoKeyword(name: string): boolean { - return isReservedGoKeyword(name); + + splitMetaModel(model: MetaModel): MetaModel[] { + //These are the models that we have separate renderers for + const metaModelsToSplit = { + splitEnum: true, + splitObject: true + }; + return split(model, metaModelsToSplit); } - render(model: CommonModel, inputModel: CommonInputModel): Promise { - const kind = TypeHelpers.extractKind(model); - switch (kind) { - case ModelKind.UNION: - // We don't support union in Go generator, however, if union is an object, we render it as a struct. - if (!model.type?.includes('object')) { break; } - return this.renderStruct(model, inputModel); - case ModelKind.OBJECT: + + constrainToMetaModel(model: MetaModel): ConstrainedMetaModel { + return constrainMetaModel( + this.options.typeMapping, + this.options.constraints, + { + metaModel: model, + options: this.options, + constrainedName: '' //This is just a placeholder, it will be constrained within the function + } + ); + } + + render(model: ConstrainedMetaModel, inputModel: InputMetaModel): Promise { + if (model instanceof ConstrainedObjectModel) { return this.renderStruct(model, inputModel); - case ModelKind.ENUM: + } else if (model instanceof ConstrainedEnumModel) { return this.renderEnum(model, inputModel); - } - Logger.warn(`Go generator, cannot generate this type of model, ${model.$id}`); + } + Logger.warn(`Go generator, cannot generate this type of model, ${model.name}`); return Promise.resolve(RenderOutput.toRenderOutput({ result: '', renderedName: '', dependencies: [] })); } @@ -102,7 +76,7 @@ export class GoGenerator extends AbstractGenerator { + async renderCompleteModel(model: ConstrainedMetaModel, inputModel: InputMetaModel, options: GoRenderCompleteModelOptions): Promise { const outputModel = await this.render(model, inputModel); let importCode = ''; if (outputModel.dependencies.length > 0) { @@ -118,19 +92,17 @@ ${outputModel.result}`; return RenderOutput.toRenderOutput({ result: outputContent, renderedName: outputModel.renderedName, dependencies: outputModel.dependencies }); } - async renderEnum(model: CommonModel, inputModel: CommonInputModel): Promise { + async renderEnum(model: ConstrainedEnumModel, inputModel: InputMetaModel): Promise { const presets = this.getPresets('enum'); const renderer = new EnumRenderer(this.options, this, presets, model, inputModel); const result = await renderer.runSelfPreset(); - const renderedName = renderer.nameType(model.$id, model); - return RenderOutput.toRenderOutput({ result, renderedName, dependencies: renderer.dependencies }); + return RenderOutput.toRenderOutput({ result, renderedName: model.name, dependencies: renderer.dependencies }); } - async renderStruct(model: CommonModel, inputModel: CommonInputModel): Promise { + async renderStruct(model: ConstrainedObjectModel, inputModel: InputMetaModel): Promise { const presets = this.getPresets('struct'); const renderer = new StructRenderer(this.options, this, presets, model, inputModel); const result = await renderer.runSelfPreset(); - const renderedName = renderer.nameType(model.$id, model); - return RenderOutput.toRenderOutput({ result, renderedName, dependencies: renderer.dependencies }); + return RenderOutput.toRenderOutput({ result, renderedName: model.name, dependencies: renderer.dependencies }); } } diff --git a/src/generators/go/GoPreset.ts b/src/generators/go/GoPreset.ts index 0b0fc6cca4..859494b041 100644 --- a/src/generators/go/GoPreset.ts +++ b/src/generators/go/GoPreset.ts @@ -1,27 +1,22 @@ -/* eslint-disable @typescript-eslint/ban-types */ import { AbstractRenderer } from '../AbstractRenderer'; -import { Preset, CommonModel, CommonPreset, PresetArgs, EnumPreset } from '../../models'; +import { Preset, CommonPreset, PresetArgs, EnumPreset, ConstrainedObjectModel, ConstrainedObjectPropertyModel } from '../../models'; import { StructRenderer, GO_DEFAULT_STRUCT_PRESET } from './renderers/StructRenderer'; import { EnumRenderer, GO_DEFAULT_ENUM_PRESET } from './renderers/EnumRenderer'; +import { GoOptions } from './GoGenerator'; -export enum FieldType { - field, - additionalProperty, - patternProperties -} export interface FieldArgs { - fieldName: string; - field: CommonModel; - type: FieldType; + field: ConstrainedObjectPropertyModel; } -export interface StructPreset extends CommonPreset { - field?: (args: PresetArgs & FieldArgs) => Promise | string; +export interface StructPreset extends CommonPreset { + field?: (args: PresetArgs & FieldArgs) => Promise | string; } +export type StructPresetType = StructPreset; +export type EnumPresetType = EnumPreset; -export type GoPreset = Preset<{ - struct: StructPreset; - enum: EnumPreset +export type GoPreset = Preset<{ + struct: StructPresetType; + enum: EnumPresetType }>; export const GO_DEFAULT_PRESET: GoPreset = { diff --git a/src/generators/go/GoRenderer.ts b/src/generators/go/GoRenderer.ts index 1c01de1daf..738b5205f8 100644 --- a/src/generators/go/GoRenderer.ts +++ b/src/generators/go/GoRenderer.ts @@ -1,121 +1,26 @@ import { AbstractRenderer } from '../AbstractRenderer'; import { GoGenerator, GoOptions } from './GoGenerator'; -import { CommonModel, CommonInputModel, Preset } from '../../models'; +import { InputMetaModel, Preset, ConstrainedMetaModel } from '../../models'; import { FormatHelpers } from '../../helpers/FormatHelpers'; -import { DefaultPropertyNames, getUniquePropertyName } from '../../helpers'; -import { FieldType } from './GoPreset'; -import { isReservedGoKeyword } from './Constants'; /** * Common renderer for Go types * * @extends AbstractRenderer */ -export abstract class GoRenderer extends AbstractRenderer { +export abstract class GoRenderer extends AbstractRenderer { constructor( options: GoOptions, generator: GoGenerator, presets: Array<[Preset, unknown]>, - model: CommonModel, - inputModel: CommonInputModel, + model: RendererModelType, + inputModel: InputMetaModel, ) { super(options, generator, presets, model, inputModel); } - async renderFields(): Promise { - const fields = this.model.properties || {}; - const content: string[] = []; - - for (const [fieldName, field] of Object.entries(fields)) { - const renderField = await this.runFieldPreset(fieldName, field); - content.push(renderField); - } - - if (this.model.additionalProperties !== undefined) { - const propertyName = getUniquePropertyName(this.model, DefaultPropertyNames.additionalProperties); - const additionalProperty = await this.runFieldPreset(propertyName, this.model.additionalProperties, FieldType.additionalProperty); - content.push(additionalProperty); - } - - if (this.model.patternProperties !== undefined) { - for (const [pattern, patternModel] of Object.entries(this.model.patternProperties)) { - const propertyName = getUniquePropertyName(this.model, `${pattern}${DefaultPropertyNames.patternProperties}`); - const renderedPatternProperty = await this.runFieldPreset(propertyName, patternModel, FieldType.patternProperties); - content.push(renderedPatternProperty); - } - } - return this.renderBlock(content); - } - - /** - * Renders the name of a type based on provided generator option naming convention type function. - * - * This is used to render names of models and then later used if that class is referenced from other models. - * - * @param name - * @param model - */ - nameType(name: string | undefined, model?: CommonModel): string { - return this.options?.namingConvention?.type - ? this.options.namingConvention.type(name, { model: model || this.model, inputModel: this.inputModel, reservedKeywordCallback: isReservedGoKeyword }) - : name || ''; - } - - /** - * Renders the name of a field based on provided generator option naming convention field function. - * - * @param fieldName - * @param field - */ - nameField(fieldName: string | undefined, field?: CommonModel): string { - return this.options?.namingConvention?.field - ? this.options.namingConvention.field(fieldName, { model: this.model, inputModel: this.inputModel, field, reservedKeywordCallback: isReservedGoKeyword }) - : fieldName || ''; - } - - runFieldPreset(fieldName: string, field: CommonModel, type: FieldType = FieldType.field): Promise { - return this.runPreset('field', { fieldName, field, type }); - } - - renderType(model: CommonModel): string { - if (model.$ref !== undefined) { - const formattedRef = this.nameType(model.$ref); - return `*${formattedRef}`; - } - - if (Array.isArray(model.type)) { - return model.type.length > 1 ? '[]interface{}' : `[]${this.toGoType(model.type[0], model)}`; - } - - 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 { - switch (type) { - case 'string': - return 'string'; - case 'integer': - return 'int'; - case 'number': - return 'float64'; - case 'boolean': - return 'bool'; - case 'object': - return 'interface{}'; - case 'array': { - if (Array.isArray(model.items)) { - return model.items.length > 1 ? '[]interface{}' : `[]${this.renderType(model.items[0])}`; - } - const arrayType = model.items ? this.renderType(model.items) : 'interface{}'; - return `[]${arrayType}`; - } - default: return 'interface{}'; - } - } } diff --git a/src/generators/go/renderers/EnumRenderer.ts b/src/generators/go/renderers/EnumRenderer.ts index c7fc3af93f..dcc3bddd8d 100644 --- a/src/generators/go/renderers/EnumRenderer.ts +++ b/src/generators/go/renderers/EnumRenderer.ts @@ -1,41 +1,33 @@ import { GoRenderer } from '../GoRenderer'; -import { EnumPreset, CommonModel } from '../../../models'; +import { ConstrainedEnumModel } from '../../../models'; import { FormatHelpers } from '../../../helpers'; +import { EnumPresetType } from '../GoPreset'; +import { GoOptions } from '../GoGenerator'; /** * Renderer for Go's `enum` type * * @extends GoRenderer */ -export class EnumRenderer extends GoRenderer { +export class EnumRenderer extends GoRenderer { public defaultSelf(): string { - const formattedName = this.nameType(this.model.$id); - const type = this.enumType(this.model); - const doc = formattedName && this.renderCommentForEnumType(formattedName, type); + const doc = this.renderCommentForEnumType(this.model.name, this.model.type); // eslint-disable-next-line sonarjs/no-duplicate-string - if (type === 'interface{}') { + if (this.model.type === 'interface{}') { return `${doc} -type ${formattedName} ${type}`; +type ${this.model.name} ${this.model.type}`; } - const enumValues = this.renderConstValuesForEnumType(formattedName, type, this.model.enum); + const enumValues = this.renderConstValuesForEnumType(this.model.name, this.model.type, this.model.values.map((enumValue) => enumValue.value)); return `${doc} -type ${formattedName} ${type} +type ${this.model.name} ${this.model.type} const ( ${this.indent(this.renderBlock(enumValues))} )`; } - 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}.`); @@ -61,7 +53,7 @@ ${this.indent(this.renderBlock(enumValues))} } } -export const GO_DEFAULT_ENUM_PRESET: EnumPreset = { +export const GO_DEFAULT_ENUM_PRESET: EnumPresetType = { self({ renderer }) { return renderer.defaultSelf(); }, diff --git a/src/generators/go/renderers/StructRenderer.ts b/src/generators/go/renderers/StructRenderer.ts index e8fb19799e..6688a349b5 100644 --- a/src/generators/go/renderers/StructRenderer.ts +++ b/src/generators/go/renderers/StructRenderer.ts @@ -1,38 +1,49 @@ import { GoRenderer } from '../GoRenderer'; -import { FieldType, StructPreset } from '../GoPreset'; +import { StructPresetType } from '../GoPreset'; +import { ConstrainedObjectModel, ConstrainedObjectPropertyModel } from 'models'; +import { GoOptions } from '../GoGenerator'; /** * Renderer for Go's `struct` type * * @extends GoRenderer */ -export class StructRenderer extends GoRenderer { +export class StructRenderer extends GoRenderer { public async defaultSelf(): Promise { const content = [ await this.renderFields(), await this.runAdditionalContentPreset() ]; - const formattedName = this.nameType(this.model.$id); - const doc = this.renderComments(`${formattedName} represents a ${formattedName} model.`); + const doc = this.renderComments(`${this.model.name} represents a ${this.model.name} model.`); return `${doc} -type ${formattedName} struct { +type ${this.model.name} struct { ${this.indent(this.renderBlock(content, 2))} }`; } + + async renderFields(): Promise { + const fields = this.model.properties || {}; + const content: string[] = []; + + for (const field of Object.values(fields)) { + const renderField = await this.runFieldPreset(field); + content.push(renderField); + } + return this.renderBlock(content); + } + + runFieldPreset(field: ConstrainedObjectPropertyModel): Promise { + return this.runPreset('field', { field }); + } } -export const GO_DEFAULT_STRUCT_PRESET: StructPreset = { +export const GO_DEFAULT_STRUCT_PRESET: StructPresetType = { self({ renderer }) { return renderer.defaultSelf(); }, - field({ fieldName, field, renderer, type }) { - fieldName = renderer.nameField(fieldName, field); - let fieldType = renderer.renderType(field); - if (type === FieldType.additionalProperty || type === FieldType.patternProperties) { - fieldType = `map[string]${fieldType}`; - } - return `${ fieldName } ${ fieldType }`; + field({ field }) { + return `${ field } ${ field.property.type }`; }, };