Skip to content

Commit

Permalink
refactor: convert Go to new constraint setup (#732)
Browse files Browse the repository at this point in the history
* Convert Go to the new setup

* Add extra test
  • Loading branch information
jonaslagoni authored Apr 28, 2022
1 parent f7a581f commit 078bb98
Show file tree
Hide file tree
Showing 10 changed files with 447 additions and 311 deletions.
243 changes: 48 additions & 195 deletions src/generators/go/GoConstrainer.ts
Original file line number Diff line number Diff line change
@@ -1,200 +1,53 @@
import { ConstrainedAnyModel, ConstrainedBooleanModel, ConstrainedFloatModel, ConstrainedIntegerModel, ConstrainedMetaModel, ConstrainedObjectModel, ConstrainedReferenceModel, ConstrainedStringModel, ConstrainedTupleModel, ConstrainedTupleValueModel, ConstrainedArrayModel, ConstrainedUnionModel, ConstrainedEnumModel, ConstrainedDictionaryModel, ConstrainedEnumValueModel } from '../../models/ConstrainedMetaModel';
import { AnyModel, BooleanModel, FloatModel, IntegerModel, MetaModel, ObjectModel, ReferenceModel, StringModel, TupleModel, ArrayModel, UnionModel, EnumModel, DictionaryModel } from '../../models/MetaModel';
import { defaultPropertyKeyConstraints, PropertyKeyConstraintType } from './constrainer/PropertyKeyConstrainer';
import { defaultModelNameConstraints, ModelNameConstraintType } from './constrainer/ModelNameConstrainer';
import { defaultEnumKeyConstraints, EnumConstraintType } from './constrainer/EnumConstrainer';

export interface GoConstraints {
enumKey: EnumConstraintType,
modelName: ModelNameConstraintType,
propertyKey: PropertyKeyConstraintType,
}
import { TypeMapping } from '../../helpers';
import { defaultEnumKeyConstraints, defaultEnumValueConstraints } from './constrainer/EnumConstrainer';
import { defaultModelNameConstraints } from './constrainer/ModelNameConstrainer';
import { defaultPropertyKeyConstraints } from './constrainer/PropertyKeyConstrainer';
import { GoRenderer } from './GoRenderer';

export const GoDefaultTypeMapping: TypeMapping<GoRenderer> = {
Object ({constrainedModel}): string {
return constrainedModel.name;
},
Reference ({constrainedModel}): string {
return constrainedModel.name;
},
Any (): string {
return 'interface{}';
},
Float (): string {
return 'float64';
},
Integer (): string {
return 'int';
},
String (): string {
return 'string';
},
Boolean (): string {
return 'bool';
},
Tuple (): string {
//Because Java have no notion of tuples (and no custom implementation), we have to render it as a list of any value.
return '[]interface{}';
},
Array ({constrainedModel}): string {
return `[]${constrainedModel.valueModel.type}`;
},
Enum ({constrainedModel}): string {
return constrainedModel.name;
},
Union (): string {
//Because Java have no notion of unions (and no custom implementation), we have to render it as any value.
return 'interface{}';
},
Dictionary ({constrainedModel}): string {
return `map[${constrainedModel.key.type}]${constrainedModel.value.type}`;
}
};

export const DefaultGoConstraints: GoConstraints = {
export const GoDefaultConstraints = {
enumKey: defaultEnumKeyConstraints(),
enumValue: defaultEnumValueConstraints(),
modelName: defaultModelNameConstraints(),
propertyKey: defaultPropertyKeyConstraints()
};

type TypeMappingFunction<T extends ConstrainedMetaModel> = (model: T) => string;

type TypeMapping = {
Object?: TypeMappingFunction<ConstrainedObjectModel>,
Reference?: TypeMappingFunction<ConstrainedReferenceModel>,
Any?: TypeMappingFunction<ConstrainedAnyModel>,
Float?: TypeMappingFunction<ConstrainedFloatModel>,
Integer?: TypeMappingFunction<ConstrainedIntegerModel>,
String?: TypeMappingFunction<ConstrainedStringModel>,
Boolean?: TypeMappingFunction<ConstrainedBooleanModel>,
Tuple?: TypeMappingFunction<ConstrainedTupleModel>,
Array?: TypeMappingFunction<ConstrainedArrayModel>,
Enum?: TypeMappingFunction<ConstrainedEnumModel>,
Union?: TypeMappingFunction<ConstrainedUnionModel>,
Dictionary?: TypeMappingFunction<ConstrainedDictionaryModel>
}

function constrainReferenceModel(constrainedName: string, metaModel: ReferenceModel, typeMapping: TypeMapping, constrainRules: GoConstraints): ConstrainedReferenceModel {
const constrainedRefModel = constrainMetaModel(metaModel.ref, typeMapping, constrainRules);
const constrainedModel = new ConstrainedReferenceModel(constrainedName, metaModel.originalInput, '', constrainedRefModel);
if (typeMapping.Reference !== undefined) {
constrainedModel.type = typeMapping.Reference(constrainedModel);
} else {
constrainedModel.type = constrainedModel.name;
}
return constrainedModel;
}
function constrainAnyModel(constrainedName: string, metaModel: AnyModel, typeMapping: TypeMapping): ConstrainedAnyModel {
const constrainedModel = new ConstrainedAnyModel(constrainedName, metaModel.originalInput, '');
if (typeMapping.Any !== undefined) {
constrainedModel.type = typeMapping.Any(constrainedModel);
} else {
constrainedModel.type = 'interface{}';
}
return constrainedModel;
}
function constrainFloatModel(constrainedName: string, metaModel: FloatModel, typeMapping: TypeMapping): ConstrainedFloatModel {
const constrainedModel = new ConstrainedFloatModel(constrainedName, metaModel.originalInput, '');
if (typeMapping.Float !== undefined) {
constrainedModel.type = typeMapping.Float(constrainedModel);
} else {
constrainedModel.type = 'float64';
}
return constrainedModel;
}
function constrainIntegerModel(constrainedName: string, metaModel: IntegerModel, typeMapping: TypeMapping): ConstrainedIntegerModel {
const constrainedModel = new ConstrainedIntegerModel(constrainedName, metaModel.originalInput, '');
if (typeMapping.Integer !== undefined) {
constrainedModel.type = typeMapping.Integer(constrainedModel);
} else {
constrainedModel.type = 'int';
}
return constrainedModel;
}
function constrainStringModel(constrainedName: string, metaModel: IntegerModel, typeMapping: TypeMapping): ConstrainedIntegerModel {
const constrainedModel = new ConstrainedStringModel(constrainedName, metaModel.originalInput, '');
if (typeMapping.String !== undefined) {
constrainedModel.type = typeMapping.String(constrainedModel);
} else {
constrainedModel.type = 'string';
}
return constrainedModel;
}
function constrainBooleanModel(constrainedName: string, metaModel: BooleanModel, typeMapping: TypeMapping): ConstrainedBooleanModel {
const constrainedModel = new ConstrainedBooleanModel(constrainedName, metaModel.originalInput, '');
if (typeMapping.Boolean !== undefined) {
constrainedModel.type = typeMapping.Boolean(constrainedModel);
} else {
constrainedModel.type = 'bool';
}
return constrainedModel;
}
function constrainTupleModel(constrainedName: string, metaModel: TupleModel, typeMapping: TypeMapping, constrainRules: GoConstraints): ConstrainedTupleModel {
const constrainedTupleModels = metaModel.tuple.map((tupleValue) => {
const tupleType = constrainMetaModel(tupleValue.value, typeMapping, constrainRules);
return new ConstrainedTupleValueModel(tupleValue.index, tupleType);
});
const constrainedModel = new ConstrainedTupleModel(constrainedName, metaModel.originalInput, '', constrainedTupleModels);
if (typeMapping.Tuple !== undefined) {
constrainedModel.type = typeMapping.Tuple(constrainedModel);
} else {
const tupleType = '[]interface{}';
constrainedModel.type = tupleType;
}
return constrainedModel;
}
function constrainArrayModel(constrainedName: string, metaModel: ArrayModel, typeMapping: TypeMapping, constrainRules: GoConstraints): ConstrainedArrayModel {
const constrainedValueModel = constrainMetaModel(metaModel.valueModel, typeMapping, constrainRules);
const constrainedModel = new ConstrainedArrayModel(constrainedName, metaModel.originalInput, '', constrainedValueModel);
if (typeMapping.Array !== undefined) {
constrainedModel.type = typeMapping.Array(constrainedModel);
} else {
constrainedModel.type = `[]${constrainedValueModel.type}`;
}
return constrainedModel;
}
function constrainUnionModel(constrainedName: string, metaModel: UnionModel, typeMapping: TypeMapping, constrainRules: GoConstraints): ConstrainedUnionModel {
const constrainedUnionModels = metaModel.union.map((unionValue) => {
return constrainMetaModel(unionValue, typeMapping, constrainRules);
});
const constrainedModel = new ConstrainedUnionModel(constrainedName, metaModel.originalInput, '', constrainedUnionModels);
if (typeMapping.Union !== undefined) {
constrainedModel.type = typeMapping.Union(constrainedModel);
} else {
constrainedModel.type = 'interface{}';
}
return constrainedModel;
}
function constrainDictionaryModel(constrainedName: string, metaModel: DictionaryModel, typeMapping: TypeMapping, constrainRules: GoConstraints): ConstrainedDictionaryModel {
const keyModel = constrainMetaModel(metaModel.key, typeMapping, constrainRules);
const valueModel = constrainMetaModel(metaModel.value, typeMapping, constrainRules);
const constrainedModel = new ConstrainedDictionaryModel(constrainedName, metaModel.originalInput, '', keyModel, valueModel, metaModel.serializationType);
if (typeMapping.Dictionary !== undefined) {
constrainedModel.type = typeMapping.Dictionary(constrainedModel);
} else {
const type = `map[${keyModel.type}]${valueModel.type}`;
constrainedModel.type = type;
}
return constrainedModel;
}

function constrainObjectModel(constrainedName: string, objectModel: ObjectModel, typeMapping: TypeMapping, constrainRules: GoConstraints): ConstrainedObjectModel {
const constrainedObjectModel = new ConstrainedObjectModel(constrainedName, objectModel.originalInput, '', {});
for (const [propertyKey, propertyMetaModel] of Object.entries(objectModel.properties)) {
const constrainedPropertyName = constrainRules.propertyKey({propertyKey, constrainedObjectModel, objectModel});
const constrainedProperty = constrainMetaModel(propertyMetaModel, typeMapping, constrainRules);
constrainedObjectModel.properties[String(constrainedPropertyName)] = constrainedProperty;
}
if (typeMapping.Object !== undefined) {
constrainedObjectModel.type = typeMapping.Object(constrainedObjectModel);
} else {
constrainedObjectModel.type = constrainedName;
}
return constrainedObjectModel;
}

export function ConstrainEnumModel(constrainedName: string, enumModel: EnumModel, typeMapping: TypeMapping, constrainRules: GoConstraints): ConstrainedEnumModel {
const constrainedModel = new ConstrainedEnumModel(constrainedName, enumModel.originalInput, '', []);

for (const enumValue of enumModel.values) {
const constrainedEnumKey = constrainRules.enumKey({enumKey: String(enumValue.key), enumModel, constrainedEnumModel: constrainedModel});
const constrainedEnumValueModel = new ConstrainedEnumValueModel(constrainedEnumKey, enumValue.value);
constrainedModel.values.push(constrainedEnumValueModel);
}
if (typeMapping.Enum !== undefined) {
constrainedModel.type = typeMapping.Enum(constrainedModel);
} else {
constrainedModel.type = constrainedName;
}
return constrainedModel;
}

export function constrainMetaModel(metaModel: MetaModel, typeMapping: TypeMapping, constrainRules: GoConstraints): ConstrainedMetaModel {
const constrainedName = constrainRules.modelName({modelName: metaModel.name});

if (metaModel instanceof ObjectModel) {
return constrainObjectModel(constrainedName, metaModel, typeMapping, constrainRules);
} else if (metaModel instanceof ReferenceModel) {
return constrainReferenceModel(constrainedName, metaModel, typeMapping, constrainRules);
} else if (metaModel instanceof AnyModel) {
return constrainAnyModel(constrainedName, metaModel, typeMapping);
} else if (metaModel instanceof FloatModel) {
return constrainFloatModel(constrainedName, metaModel, typeMapping);
} else if (metaModel instanceof IntegerModel) {
return constrainIntegerModel(constrainedName, metaModel, typeMapping);
} else if (metaModel instanceof StringModel) {
return constrainStringModel(constrainedName, metaModel, typeMapping);
} else if (metaModel instanceof BooleanModel) {
return constrainBooleanModel(constrainedName, metaModel, typeMapping);
} else if (metaModel instanceof TupleModel) {
return constrainTupleModel(constrainedName, metaModel, typeMapping, constrainRules);
} else if (metaModel instanceof ArrayModel) {
return constrainArrayModel(constrainedName, metaModel, typeMapping, constrainRules);
} else if (metaModel instanceof UnionModel) {
return constrainUnionModel(constrainedName, metaModel, typeMapping, constrainRules);
} else if (metaModel instanceof EnumModel) {
return ConstrainEnumModel(constrainedName, metaModel, typeMapping, constrainRules);
} else if (metaModel instanceof DictionaryModel) {
return constrainDictionaryModel(constrainedName, metaModel, typeMapping, constrainRules);
}
throw new Error('Could not constrain model');
}
16 changes: 12 additions & 4 deletions src/generators/go/GoGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import {
defaultGeneratorOptions
} from '../AbstractGenerator';
import { CommonModel, CommonInputModel, RenderOutput } from '../../models';
import { TypeHelpers, ModelKind, FormatHelpers } from '../../helpers';
import { TypeHelpers, ModelKind, FormatHelpers, Constraints, 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
*/
Expand Down Expand Up @@ -48,6 +50,8 @@ export const GoNamingConventionImplementation: GoNamingConvention = {

export interface GoOptions extends CommonGeneratorOptions<GoPreset> {
namingConvention?: GoNamingConvention;
typeMapping: TypeMapping<GoRenderer>;
constraints: Constraints
}

export interface GoRenderCompleteModelOptions {
Expand All @@ -61,12 +65,16 @@ export class GoGenerator extends AbstractGenerator<GoOptions, GoRenderCompleteMo
static defaultOptions: GoOptions = {
...defaultGeneratorOptions,
defaultPreset: GO_DEFAULT_PRESET,
namingConvention: GoNamingConventionImplementation
namingConvention: GoNamingConventionImplementation,
typeMapping: GoDefaultTypeMapping,
constraints: GoDefaultConstraints
};
constructor(
options: GoOptions = GoGenerator.defaultOptions,
options: Partial<GoOptions> = GoGenerator.defaultOptions,
) {
super('Go', GoGenerator.defaultOptions, options);
const mergedOptions = {...GoGenerator.defaultOptions, ...options};

super('Go', GoGenerator.defaultOptions, mergedOptions);
}
reservedGoKeyword(name: string): boolean {
return isReservedGoKeyword(name);
Expand Down
53 changes: 18 additions & 35 deletions src/generators/go/constrainer/EnumConstrainer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { ConstrainedEnumModel, EnumModel } from '../../../models';
import { NO_NUMBER_START_CHAR, NO_DUPLICATE_ENUM_KEYS, NO_EMPTY_VALUE} from '../../../helpers/Constraints';
import { FormatHelpers } from '../../../helpers';
import { NO_NUMBER_START_CHAR, NO_DUPLICATE_ENUM_KEYS, NO_EMPTY_VALUE, NO_RESERVED_KEYWORDS } from '../../../helpers/Constraints';
import { FormatHelpers, EnumKeyConstraint, EnumValueConstraint } from '../../../helpers';
import { isReservedGoKeyword } from '../Constants';

export type ModelEnumKeyConstraints = {
NO_SPECIAL_CHAR: (value: string) => string;
Expand All @@ -22,44 +22,27 @@ export const DefaultEnumKeyConstraints: ModelEnumKeyConstraints = {
NO_EMPTY_VALUE,
NAMING_FORMATTER: FormatHelpers.toConstantCase,
NO_RESERVED_KEYWORDS: (value: string) => {
return value;
return NO_RESERVED_KEYWORDS(value, isReservedGoKeyword);
}
};

export type EnumKeyContext = {
enumKey: string,
constrainedEnumModel: ConstrainedEnumModel,
enumModel: EnumModel
}
export type EnumConstraintType = (context: EnumKeyContext, constraints?: ModelEnumKeyConstraints) => string;
export function defaultEnumKeyConstraints(customConstraints?: Partial<ModelEnumKeyConstraints>): EnumKeyConstraint {
const constraints = {...DefaultEnumKeyConstraints, ...customConstraints};

export function defaultEnumKeyConstraints(customConstraints?: ModelEnumKeyConstraints): EnumConstraintType {
const constraints = DefaultEnumKeyConstraints;
if (customConstraints !== undefined) {
if (customConstraints.NAMING_FORMATTER !== undefined) {
constraints.NAMING_FORMATTER = customConstraints.NAMING_FORMATTER;
}
if (customConstraints.NO_SPECIAL_CHAR !== undefined) {
constraints.NO_SPECIAL_CHAR = customConstraints.NO_SPECIAL_CHAR;
}
if (customConstraints.NO_NUMBER_START_CHAR !== undefined) {
constraints.NO_NUMBER_START_CHAR = customConstraints.NO_NUMBER_START_CHAR;
}
if (customConstraints.NO_RESERVED_KEYWORDS !== undefined) {
constraints.NO_RESERVED_KEYWORDS = customConstraints.NO_RESERVED_KEYWORDS;
}
if (customConstraints.NO_DUPLICATE_KEYS !== undefined) {
constraints.NO_DUPLICATE_KEYS = customConstraints.NO_DUPLICATE_KEYS;
}
}
return ({enumKey, enumModel, constrainedEnumModel}) => {
let constrainedEnumKey = enumKey;
constrainedEnumKey = constraints.NO_SPECIAL_CHAR!(constrainedEnumKey);
constrainedEnumKey = constraints.NO_NUMBER_START_CHAR!(constrainedEnumKey);
constrainedEnumKey = constraints.NO_EMPTY_VALUE!(constrainedEnumKey);
constrainedEnumKey = constraints.NO_RESERVED_KEYWORDS!(constrainedEnumKey);
constrainedEnumKey = constraints.NAMING_FORMATTER!(constrainedEnumKey);
constrainedEnumKey = constraints.NO_DUPLICATE_KEYS!(constrainedEnumModel, enumModel, constrainedEnumKey, constraints.NAMING_FORMATTER!);
constrainedEnumKey = constraints.NO_SPECIAL_CHAR(constrainedEnumKey);
constrainedEnumKey = constraints.NO_NUMBER_START_CHAR(constrainedEnumKey);
constrainedEnumKey = constraints.NO_EMPTY_VALUE(constrainedEnumKey);
constrainedEnumKey = constraints.NO_RESERVED_KEYWORDS(constrainedEnumKey);
constrainedEnumKey = constraints.NAMING_FORMATTER(constrainedEnumKey);
constrainedEnumKey = constraints.NO_DUPLICATE_KEYS(constrainedEnumModel, enumModel, constrainedEnumKey, constraints.NAMING_FORMATTER);
return constrainedEnumKey;
};
}

export function defaultEnumValueConstraints(): EnumValueConstraint {
return ({enumValue}) => {
return `"${enumValue}"`;
};
}
Loading

0 comments on commit 078bb98

Please sign in to comment.