diff --git a/src/generators/csharp/CSharpConstrainer.ts b/src/generators/csharp/CSharpConstrainer.ts index 6eb137bb7c..47738b33de 100644 --- a/src/generators/csharp/CSharpConstrainer.ts +++ b/src/generators/csharp/CSharpConstrainer.ts @@ -1,228 +1,58 @@ -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'; -import { Logger } from '../../utils'; - -export interface CSharpConstraints { - enumKey: EnumConstraintType, - modelName: ModelNameConstraintType, - propertyKey: PropertyKeyConstraintType, -} - -export const DefaultCSharpConstraints: CSharpConstraints = { - enumKey: defaultEnumKeyConstraints(), - modelName: defaultModelNameConstraints(), - propertyKey: defaultPropertyKeyConstraints() -}; - -type TypeMappingFunction = (model: T) => string; - -type TypeMapping = { - Object?: TypeMappingFunction, - Reference?: TypeMappingFunction, - Any?: TypeMappingFunction, - Float?: TypeMappingFunction, - Integer?: TypeMappingFunction, - String?: TypeMappingFunction, - Boolean?: TypeMappingFunction, - Tuple?: TypeMappingFunction, - Array?: TypeMappingFunction, - Enum?: TypeMappingFunction, - Union?: TypeMappingFunction, - Dictionary?: TypeMappingFunction -} - -function constrainReferenceModel(constrainedName: string, metaModel: ReferenceModel, typeMapping: TypeMapping, constrainRules: CSharpConstraints): 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 = 'dynamic'; - } - 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 = 'float'; - } - 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: CSharpConstraints): 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 tupleTypes = constrainedTupleModels.map((constrainedType) => { +import { TypeMapping } from '../../helpers'; +import { defaultEnumKeyConstraints, defaultEnumValueConstraints } from './constrainer/EnumConstrainer'; +import { defaultModelNameConstraints } from './constrainer/ModelNameConstrainer'; +import { defaultPropertyKeyConstraints } from './constrainer/PropertyKeyConstrainer'; +import { CSharpRenderer } from './CSharpRenderer'; + +export const CSharpDefaultTypeMapping: TypeMapping = { + Object ({constrainedModel}): string { + return constrainedModel.name; + }, + Reference ({constrainedModel}): string { + return constrainedModel.name; + }, + Any (): string { + return 'dynamic'; + }, + Float (): string { + return 'float'; + }, + Integer (): string { + return 'int'; + }, + String (): string { + return 'string'; + }, + Boolean (): string { + return 'bool'; + }, + Tuple ({constrainedModel}): string { + const tupleTypes = constrainedModel.tuple.map((constrainedType) => { return constrainedType.value.type; }); - const tupleType = `(${tupleTypes.join(', ')})`; - constrainedModel.type = tupleType; - } - return constrainedModel; -} -function constrainArrayModel(constrainedName: string, metaModel: ArrayModel, typeMapping: TypeMapping, constrainRules: CSharpConstraints): 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: CSharpConstraints): 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 = 'dynamic'; - } - return constrainedModel; -} -function constrainDictionaryModel(constrainedName: string, metaModel: DictionaryModel, typeMapping: TypeMapping, constrainRules: CSharpConstraints): ConstrainedDictionaryModel { - let keyModel; - //There is some restrictions on what can be used as keys for dictionaries. - if (metaModel.key instanceof UnionModel) { - Logger.error('Key for dictionary is not allowed to be union type, falling back to any model.'); - const anyModel = new AnyModel(metaModel.key.name, metaModel.key.originalInput); - keyModel = constrainMetaModel(anyModel, typeMapping, constrainRules); - } else { - 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 = `Dictionary<${keyModel.type}, ${valueModel.type}>`; - constrainedModel.type = type; - } - return constrainedModel; -} - -function constrainObjectModel(constrainedName: string, objectModel: ObjectModel, typeMapping: TypeMapping, constrainRules: CSharpConstraints): 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: CSharpConstraints): 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}); - let normalizedEnumValue; - switch (typeof enumValue.value) { - case 'boolean': - case 'bigint': - case 'number': { - normalizedEnumValue = enumValue.value; - break; - } - case 'object': { - normalizedEnumValue = `"${JSON.stringify(enumValue).replace(/"/g, '\\"')}"`; - break; - } - default: { - normalizedEnumValue = `"${enumValue}"`; + return `(${tupleTypes.join(', ')})`; + }, + Array ({constrainedModel, renderer}): string { + if (renderer.options.collectionType && renderer.options.collectionType === 'List') { + return `IEnumerable<${constrainedModel.valueModel.type}>`; } - } - const constrainedEnumValueModel = new ConstrainedEnumValueModel(constrainedEnumKey, normalizedEnumValue); - constrainedModel.values.push(constrainedEnumValueModel); - } - if (typeMapping.Enum !== undefined) { - constrainedModel.type = typeMapping.Enum(constrainedModel); - } else { - constrainedModel.type = constrainedName; + return `${constrainedModel.valueModel.type}[]`; + }, + Enum ({constrainedModel}): string { + return constrainedModel.name; + }, + Union (): string { + //Because CSharp have no notion of unions (and no custom implementation), we have to render it as any value. + return 'dynamic'; + }, + Dictionary ({constrainedModel}): string { + return `Dictionary<${constrainedModel.key.type}, ${constrainedModel.value.type}>`; } - return constrainedModel; -} +}; -export function constrainMetaModel(metaModel: MetaModel, typeMapping: TypeMapping, constrainRules: CSharpConstraints): 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'); -} +export const CSharpDefaultConstraints = { + enumKey: defaultEnumKeyConstraints(), + enumValue: defaultEnumValueConstraints(), + modelName: defaultModelNameConstraints(), + propertyKey: defaultPropertyKeyConstraints() +}; diff --git a/src/generators/csharp/CSharpGenerator.ts b/src/generators/csharp/CSharpGenerator.ts index bb930f4962..ed97b7b2a9 100644 --- a/src/generators/csharp/CSharpGenerator.ts +++ b/src/generators/csharp/CSharpGenerator.ts @@ -4,16 +4,20 @@ import { defaultGeneratorOptions } from '../AbstractGenerator'; import { CommonModel, CommonInputModel, RenderOutput } from '../../models'; -import { TypeHelpers, ModelKind, CommonNamingConvention, CommonNamingConventionImplementation, FormatHelpers } from '../../helpers'; +import { TypeHelpers, ModelKind, CommonNamingConvention, CommonNamingConventionImplementation, FormatHelpers, TypeMapping, Constraints } from '../../helpers'; import { CSharpPreset, CSHARP_DEFAULT_PRESET } from './CSharpPreset'; import { EnumRenderer } from './renderers/EnumRenderer'; import { ClassRenderer } from './renderers/ClassRenderer'; import { isReservedCSharpKeyword } from './Constants'; import { Logger } from '../../index'; +import { CSharpRenderer } from './CSharpRenderer'; +import { CSharpDefaultConstraints, CSharpDefaultTypeMapping } from './CSharpConstrainer'; export interface CSharpOptions extends CommonGeneratorOptions { collectionType?: 'List' | 'Array'; namingConvention?: CommonNamingConvention; + typeMapping: TypeMapping; + constraints: Constraints; } export interface CSharpRenderCompleteModelOptions { @@ -28,13 +32,17 @@ export class CSharpGenerator extends AbstractGenerator = CSharpGenerator.defaultOptions, ) { - super('CSharp', CSharpGenerator.defaultOptions, options); + const mergedOptions = {...CSharpGenerator.defaultOptions, ...options}; + + super('CSharp', CSharpGenerator.defaultOptions, mergedOptions); } /** diff --git a/src/generators/csharp/constrainer/EnumConstrainer.ts b/src/generators/csharp/constrainer/EnumConstrainer.ts index 53f08f4dc7..ee305766c6 100644 --- a/src/generators/csharp/constrainer/EnumConstrainer.ts +++ b/src/generators/csharp/constrainer/EnumConstrainer.ts @@ -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, NO_RESERVED_KEYWORDS} from '../../../helpers/Constraints'; -import { FormatHelpers } from '../../../helpers'; +import { EnumKeyConstraint, EnumValueConstraint, FormatHelpers } from '../../../helpers'; import { isReservedCSharpKeyword } from '../Constants'; export type ModelEnumKeyConstraints = { @@ -27,40 +27,39 @@ export const DefaultEnumKeyConstraints: ModelEnumKeyConstraints = { } }; -export type EnumKeyContext = { - enumKey: string, - constrainedEnumModel: ConstrainedEnumModel, - enumModel: EnumModel +export function defaultEnumKeyConstraints(customConstraints?: Partial): EnumKeyConstraint { + const constraints = {...DefaultEnumKeyConstraints, ...customConstraints}; + + 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!); + return constrainedEnumKey; + }; } -export type EnumConstraintType = (context: EnumKeyContext, constraints?: ModelEnumKeyConstraints) => string; -export function defaultEnumKeyConstraints(customConstraints?: ModelEnumKeyConstraints): EnumConstraintType { - const constraints = DefaultEnumKeyConstraints; - if (customConstraints !== undefined) { - if (customConstraints.NAMING_FORMATTER !== undefined) { - constraints.NAMING_FORMATTER = customConstraints.NAMING_FORMATTER; +export function defaultEnumValueConstraints(): EnumValueConstraint { + return ({enumValue}) => { + let normalizedEnumValue; + switch (typeof enumValue.value) { + case 'boolean': + case 'bigint': + case 'number': { + normalizedEnumValue = enumValue.value; + break; } - if (customConstraints.NO_SPECIAL_CHAR !== undefined) { - constraints.NO_SPECIAL_CHAR = customConstraints.NO_SPECIAL_CHAR; + case 'object': { + normalizedEnumValue = `"${JSON.stringify(enumValue).replace(/"/g, '\\"')}"`; + break; } - if (customConstraints.NO_NUMBER_START_CHAR !== undefined) { - constraints.NO_NUMBER_START_CHAR = customConstraints.NO_NUMBER_START_CHAR; + default: { + normalizedEnumValue = `"${enumValue}"`; } - 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!); - return constrainedEnumKey; + return normalizedEnumValue; }; } diff --git a/src/generators/csharp/constrainer/ModelNameConstrainer.ts b/src/generators/csharp/constrainer/ModelNameConstrainer.ts index 4434330a59..f2a73fa48b 100644 --- a/src/generators/csharp/constrainer/ModelNameConstrainer.ts +++ b/src/generators/csharp/constrainer/ModelNameConstrainer.ts @@ -1,14 +1,14 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { NO_NUMBER_START_CHAR, NO_EMPTY_VALUE, NO_RESERVED_KEYWORDS} from '../../../helpers/Constraints'; -import { FormatHelpers } from '../../../helpers'; +import { FormatHelpers, ModelNameConstraint } from '../../../helpers'; import { isReservedCSharpKeyword } from '../Constants'; export type ModelNameConstraints = { - NO_SPECIAL_CHAR?: (value: string) => string; - NO_NUMBER_START_CHAR?: (value: string) => string; - NO_EMPTY_VALUE?: (value: string) => string; - NAMING_FORMATTER?: (value: string) => string; - NO_RESERVED_KEYWORDS?: (value: string) => string; + NO_SPECIAL_CHAR: (value: string) => string; + NO_NUMBER_START_CHAR: (value: string) => string; + NO_EMPTY_VALUE: (value: string) => string; + NAMING_FORMATTER: (value: string) => string; + NO_RESERVED_KEYWORDS: (value: string) => string; }; export const DefaultModelNameConstraints: ModelNameConstraints = { @@ -26,38 +26,16 @@ export const DefaultModelNameConstraints: ModelNameConstraints = { return NO_RESERVED_KEYWORDS(value, isReservedCSharpKeyword); } }; -export type ModelNameContext = { - modelName: string -} -export type ModelNameConstraintType = (context: ModelNameContext) => string; - -export function defaultModelNameConstraints(customConstraints?: ModelNameConstraints): ModelNameConstraintType { - const constraints = DefaultModelNameConstraints; - if (customConstraints !== undefined) { - 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_EMPTY_VALUE !== undefined) { - constraints.NO_EMPTY_VALUE = customConstraints.NO_EMPTY_VALUE; - } - if (customConstraints.NAMING_FORMATTER !== undefined) { - constraints.NAMING_FORMATTER = customConstraints.NAMING_FORMATTER; - } - if (customConstraints.NO_RESERVED_KEYWORDS !== undefined) { - constraints.NAMING_FORMATTER = customConstraints.NO_RESERVED_KEYWORDS; - } - } +export function defaultModelNameConstraints(customConstraints?: Partial): ModelNameConstraint { + const constraints = {...DefaultModelNameConstraints, ...customConstraints}; return ({modelName}) => { let constrainedValue = modelName; - constrainedValue = constraints.NO_SPECIAL_CHAR!(constrainedValue); - constrainedValue = constraints.NO_NUMBER_START_CHAR!(constrainedValue); - constrainedValue = constraints.NO_EMPTY_VALUE!(constrainedValue); - constrainedValue = constraints.NAMING_FORMATTER!(constrainedValue); - constrainedValue = constraints.NO_RESERVED_KEYWORDS!(constrainedValue); + constrainedValue = constraints.NO_SPECIAL_CHAR(constrainedValue); + constrainedValue = constraints.NO_NUMBER_START_CHAR(constrainedValue); + constrainedValue = constraints.NO_EMPTY_VALUE(constrainedValue); + constrainedValue = constraints.NO_RESERVED_KEYWORDS(constrainedValue); + constrainedValue = constraints.NAMING_FORMATTER(constrainedValue); return constrainedValue; }; } diff --git a/src/generators/csharp/constrainer/PropertyKeyConstrainer.ts b/src/generators/csharp/constrainer/PropertyKeyConstrainer.ts index 1af3387e88..a2a324ce57 100644 --- a/src/generators/csharp/constrainer/PropertyKeyConstrainer.ts +++ b/src/generators/csharp/constrainer/PropertyKeyConstrainer.ts @@ -1,16 +1,15 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ import { ConstrainedObjectModel, ObjectModel } from '../../../models'; import { NO_NUMBER_START_CHAR, NO_DUPLICATE_PROPERTIES, NO_EMPTY_VALUE, NO_RESERVED_KEYWORDS} from '../../../helpers/Constraints'; -import { FormatHelpers } from '../../../helpers'; +import { FormatHelpers, PropertyKeyConstraint } from '../../../helpers'; import { isReservedCSharpKeyword } from '../Constants'; export type PropertyKeyConstraintOptions = { - NO_SPECIAL_CHAR?: (value: string) => string; - NO_NUMBER_START_CHAR?: (value: string) => string; - NO_DUPLICATE_PROPERTIES?: (constrainedObjectModel: ConstrainedObjectModel, objectModel: ObjectModel, propertyName: string, namingFormatter: (value: string) => string) => string; - NO_EMPTY_VALUE?: (value: string) => string; - NAMING_FORMATTER?: (value: string) => string; - NO_RESERVED_KEYWORDS?: (value: string) => string; + NO_SPECIAL_CHAR: (value: string) => string; + NO_NUMBER_START_CHAR: (value: string) => string; + NO_DUPLICATE_PROPERTIES: (constrainedObjectModel: ConstrainedObjectModel, objectModel: ObjectModel, propertyName: string, namingFormatter: (value: string) => string) => string; + NO_EMPTY_VALUE: (value: string) => string; + NAMING_FORMATTER: (value: string) => string; + NO_RESERVED_KEYWORDS: (value: string) => string; }; export const DefaultPropertyKeyConstraints: PropertyKeyConstraintOptions = { @@ -28,45 +27,18 @@ export const DefaultPropertyKeyConstraints: PropertyKeyConstraintOptions = { } }; -export type PropertyKeyContext = { - propertyKey: string, - constrainedObjectModel: ConstrainedObjectModel, - objectModel: ObjectModel -} - -export type PropertyKeyConstraintType = (context: PropertyKeyContext) => string; +export function defaultPropertyKeyConstraints(customConstraints?: Partial): PropertyKeyConstraint { + const constraints = {...DefaultPropertyKeyConstraints, ...customConstraints}; -export function defaultPropertyKeyConstraints(customConstraints?: PropertyKeyConstraintOptions): PropertyKeyConstraintType { - const constraints = DefaultPropertyKeyConstraints; - 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_EMPTY_VALUE !== undefined) { - constraints.NO_EMPTY_VALUE = customConstraints.NO_EMPTY_VALUE; - } - if (customConstraints.NO_RESERVED_KEYWORDS !== undefined) { - constraints.NO_RESERVED_KEYWORDS = customConstraints.NO_RESERVED_KEYWORDS; - } - if (customConstraints.NO_DUPLICATE_PROPERTIES !== undefined) { - constraints.NO_DUPLICATE_PROPERTIES = customConstraints.NO_DUPLICATE_PROPERTIES; - } - } - return ({propertyKey, constrainedObjectModel, objectModel}: PropertyKeyContext) => { + return ({propertyKey, constrainedObjectModel, objectModel}) => { let constrainedPropertyKey = propertyKey; - constrainedPropertyKey = constraints.NO_SPECIAL_CHAR!(constrainedPropertyKey); - constrainedPropertyKey = constraints.NO_NUMBER_START_CHAR!(constrainedPropertyKey); - constrainedPropertyKey = constraints.NO_EMPTY_VALUE!(constrainedPropertyKey); - constrainedPropertyKey = constraints.NO_RESERVED_KEYWORDS!(constrainedPropertyKey); - constrainedPropertyKey = constraints.NAMING_FORMATTER!(constrainedPropertyKey); - constrainedPropertyKey = constraints.NO_DUPLICATE_PROPERTIES!(constrainedObjectModel, objectModel, constrainedPropertyKey, constraints.NAMING_FORMATTER!); + constrainedPropertyKey = constraints.NO_SPECIAL_CHAR(constrainedPropertyKey); + constrainedPropertyKey = constraints.NO_NUMBER_START_CHAR(constrainedPropertyKey); + constrainedPropertyKey = constraints.NO_EMPTY_VALUE(constrainedPropertyKey); + constrainedPropertyKey = constraints.NO_RESERVED_KEYWORDS(constrainedPropertyKey); + constrainedPropertyKey = constraints.NAMING_FORMATTER(constrainedPropertyKey); + constrainedPropertyKey = constraints.NO_DUPLICATE_PROPERTIES(constrainedObjectModel, objectModel, constrainedPropertyKey, constraints.NAMING_FORMATTER); return constrainedPropertyKey; }; } diff --git a/src/generators/java/JavaGenerator.ts b/src/generators/java/JavaGenerator.ts index 873c3b4e26..403857857b 100644 --- a/src/generators/java/JavaGenerator.ts +++ b/src/generators/java/JavaGenerator.ts @@ -18,7 +18,7 @@ export interface JavaOptions extends CommonGeneratorOptions { collectionType: 'List' | 'Array'; namingConvention: CommonNamingConvention; typeMapping: TypeMapping; - constraints: Constraints + constraints: Constraints; } export interface JavaRenderCompleteModelOptions { packageName: string diff --git a/test/TestUtils/TestRenderers.ts b/test/TestUtils/TestRenderers.ts index 311087cf94..590e6f17da 100644 --- a/test/TestUtils/TestRenderers.ts +++ b/test/TestUtils/TestRenderers.ts @@ -1,4 +1,5 @@ import { AbstractRenderer, CommonInputModel, CommonModel, RenderOutput } from '../../src'; +import { CSharpRenderer } from '../../src/generators/csharp/CSharpRenderer'; import { JavaRenderer } from '../../src/generators/java/JavaRenderer'; import {testOptions, TestGenerator} from './TestGenerator'; @@ -11,6 +12,5 @@ export class TestRenderer extends AbstractRenderer { } } -export class MockJavaRenderer extends JavaRenderer { - -} +export class MockJavaRenderer extends JavaRenderer {} +export class MockCSharpRenderer extends CSharpRenderer {} diff --git a/test/generators/csharp/CSharpConstrainer.spec.ts b/test/generators/csharp/CSharpConstrainer.spec.ts new file mode 100644 index 0000000000..11f4596211 --- /dev/null +++ b/test/generators/csharp/CSharpConstrainer.spec.ts @@ -0,0 +1,121 @@ +import { CSharpDefaultTypeMapping } from '../../../src/generators/csharp/CSharpConstrainer'; +import { MockCSharpRenderer } from '../../TestUtils/TestRenderers'; +import { CSharpRenderer } from '../../../src/generators/csharp/CSharpRenderer'; +import { CommonInputModel, CommonModel, ConstrainedAnyModel, ConstrainedArrayModel, ConstrainedBooleanModel, ConstrainedDictionaryModel, ConstrainedEnumModel, ConstrainedFloatModel, ConstrainedIntegerModel, ConstrainedObjectModel, ConstrainedReferenceModel, ConstrainedStringModel, ConstrainedTupleModel, ConstrainedTupleValueModel, ConstrainedUnionModel, CSharpGenerator } from '../../../src'; +describe('CSharpConstrainer', () => { + let renderer: CSharpRenderer; + beforeEach(() => { + renderer = new MockCSharpRenderer(CSharpGenerator.defaultOptions, new CSharpGenerator(), [], new CommonModel(), new CommonInputModel()); + }); + describe('ObjectModel', () => { + test('should render the constrained name as type', () => { + const model = new ConstrainedObjectModel('test', undefined, '', {}); + const type = CSharpDefaultTypeMapping.Object({constrainedModel: model, renderer}); + expect(type).toEqual(model.name); + }); + }); + describe('Reference', () => { + test('should render the constrained name as type', () => { + const refModel = new ConstrainedAnyModel('test', undefined, ''); + const model = new ConstrainedReferenceModel('test', undefined, '', refModel); + const type = CSharpDefaultTypeMapping.Reference({constrainedModel: model, renderer}); + expect(type).toEqual(model.name); + }); + }); + describe('Any', () => { + test('should render type', () => { + const model = new ConstrainedAnyModel('test', undefined, ''); + const type = CSharpDefaultTypeMapping.Any({constrainedModel: model, renderer}); + expect(type).toEqual('dynamic'); + }); + }); + describe('Float', () => { + test('should render type', () => { + const model = new ConstrainedFloatModel('test', undefined, ''); + const type = CSharpDefaultTypeMapping.Float({constrainedModel: model, renderer}); + expect(type).toEqual('float'); + }); + }); + describe('Integer', () => { + test('should render type', () => { + const model = new ConstrainedIntegerModel('test', undefined, ''); + const type = CSharpDefaultTypeMapping.Integer({constrainedModel: model, renderer}); + expect(type).toEqual('int'); + }); + }); + describe('String', () => { + test('should render type', () => { + const model = new ConstrainedStringModel('test', undefined, ''); + const type = CSharpDefaultTypeMapping.String({constrainedModel: model, renderer}); + expect(type).toEqual('string'); + }); + }); + describe('Boolean', () => { + test('should render type', () => { + const model = new ConstrainedBooleanModel('test', undefined, ''); + const type = CSharpDefaultTypeMapping.Boolean({constrainedModel: model, renderer}); + expect(type).toEqual('bool'); + }); + }); + + describe('Tuple', () => { + test('should render type', () => { + const tupleModel = new ConstrainedBooleanModel('test', undefined, 'string'); + const tupleValueModel = new ConstrainedTupleValueModel(0, tupleModel); + const model = new ConstrainedTupleModel('test', undefined, '', [tupleValueModel]); + const type = CSharpDefaultTypeMapping.Tuple({constrainedModel: model, renderer}); + expect(type).toEqual('(string)'); + }); + test('should render multiple types', () => { + const tupleModel = new ConstrainedBooleanModel('test', undefined, 'string'); + const tupleValueModel0 = new ConstrainedTupleValueModel(0, tupleModel); + const tupleValueModel1 = new ConstrainedTupleValueModel(1, tupleModel); + const model = new ConstrainedTupleModel('test', undefined, '', [tupleValueModel0, tupleValueModel1]); + const type = CSharpDefaultTypeMapping.Tuple({constrainedModel: model, renderer}); + expect(type).toEqual('(string, string)'); + }); + }); + + describe('Array', () => { + test('should render type', () => { + const arrayModel = new ConstrainedStringModel('test', undefined, 'String'); + const model = new ConstrainedArrayModel('test', undefined, '', arrayModel); + renderer.options.collectionType = 'Array'; + const type = CSharpDefaultTypeMapping.Array({constrainedModel: model, renderer}); + expect(type).toEqual('String[]'); + }); + test('should render array as a list', () => { + const arrayModel = new ConstrainedStringModel('test', undefined, 'String'); + const model = new ConstrainedArrayModel('test', undefined, '', arrayModel); + renderer.options.collectionType = 'List'; + const type = CSharpDefaultTypeMapping.Array({constrainedModel: model, renderer}); + expect(type).toEqual('IEnumerable'); + }); + }); + + describe('Enum', () => { + test('should render the constrained name as type', () => { + const model = new ConstrainedEnumModel('test', undefined, '', []); + const type = CSharpDefaultTypeMapping.Enum({constrainedModel: model, renderer}); + expect(type).toEqual(model.name); + }); + }); + + describe('Union', () => { + test('should render type', () => { + const model = new ConstrainedUnionModel('test', undefined, '', []); + const type = CSharpDefaultTypeMapping.Union({constrainedModel: model, renderer}); + expect(type).toEqual('dynamic'); + }); + }); + + describe('Dictionary', () => { + test('should render type', () => { + const keyModel = new ConstrainedStringModel('test', undefined, 'String'); + const valueModel = new ConstrainedStringModel('test', undefined, 'String'); + const model = new ConstrainedDictionaryModel('test', undefined, '', keyModel, valueModel); + const type = CSharpDefaultTypeMapping.Dictionary({constrainedModel: model, renderer}); + expect(type).toEqual('Dictionary'); + }); + }); +}); diff --git a/test/generators/csharp/constrainer/EnumConstrainer.spec.ts b/test/generators/csharp/constrainer/EnumConstrainer.spec.ts new file mode 100644 index 0000000000..3b36eab973 --- /dev/null +++ b/test/generators/csharp/constrainer/EnumConstrainer.spec.ts @@ -0,0 +1,94 @@ +import { CSharpDefaultConstraints } from '../../../../src/generators/csharp/CSharpConstrainer'; +import {EnumModel} from '../../../../src/models/MetaModel'; +import { ConstrainedEnumModel, ConstrainedEnumValueModel } from '../../../../src'; +import { defaultEnumKeyConstraints, ModelEnumKeyConstraints, DefaultEnumKeyConstraints } from '../../../../src/generators/csharp/constrainer/EnumConstrainer'; +describe('EnumConstrainer', () => { + const enumModel = new EnumModel('test', undefined, []); + const constrainedEnumModel = new ConstrainedEnumModel('test', undefined, '', []); + + describe('enum keys', () => { + test('should never render special chars', () => { + const constrainedKey = CSharpDefaultConstraints.enumKey({enumModel, constrainedEnumModel, enumKey: '%'}); + expect(constrainedKey).toEqual('PERCENT'); + }); + test('should not render number as start char', () => { + const constrainedKey = CSharpDefaultConstraints.enumKey({enumModel, constrainedEnumModel, enumKey: '1'}); + expect(constrainedKey).toEqual('NUMBER_1'); + }); + test('should not contain duplicate keys', () => { + const existingConstrainedEnumValueModel = new ConstrainedEnumValueModel('TEST', 'test'); + const constrainedEnumModel = new ConstrainedEnumModel('test', undefined, '', [existingConstrainedEnumValueModel]); + const constrainedKey = CSharpDefaultConstraints.enumKey({enumModel, constrainedEnumModel, enumKey: 'TEST'}); + expect(constrainedKey).toEqual('RESERVED_TEST'); + }); + test('should never contain empty keys', () => { + const constrainedKey = CSharpDefaultConstraints.enumKey({enumModel, constrainedEnumModel, enumKey: ''}); + expect(constrainedKey).toEqual('EMPTY'); + }); + test('should use constant naming format', () => { + const constrainedKey = CSharpDefaultConstraints.enumKey({enumModel, constrainedEnumModel, enumKey: 'some weird_value!"#2'}); + expect(constrainedKey).toEqual('SOME_SPACE_WEIRD_VALUE_EXCLAMATION_QUOTATION_HASH_2'); + }); + test('should never render reserved keywords', () => { + const constrainedKey = CSharpDefaultConstraints.enumKey({enumModel, constrainedEnumModel, enumKey: 'return'}); + expect(constrainedKey).toEqual('RESERVED_RETURN'); + }); + }); + describe('enum values', () => { + test('should render string values', () => { + const constrainedValue = CSharpDefaultConstraints.enumValue({enumModel, constrainedEnumModel, enumValue: 'string value'}); + expect(constrainedValue).toEqual('"string value"'); + }); + test('should render boolean values', () => { + const constrainedValue = CSharpDefaultConstraints.enumValue({enumModel, constrainedEnumModel, enumValue: true}); + expect(constrainedValue).toEqual('"true"'); + }); + test('should render numbers', () => { + const constrainedValue = CSharpDefaultConstraints.enumValue({enumModel, constrainedEnumModel, enumValue: 123}); + expect(constrainedValue).toEqual('"123"'); + }); + test.skip('should render object', () => { + const constrainedValue = CSharpDefaultConstraints.enumValue({enumModel, constrainedEnumModel, enumValue: {test: 'test'}}); + expect(constrainedValue).toEqual('"{\\"test\\":\\"test\\"}"'); + }); + test.skip('should render unknown value', () => { + const constrainedValue = CSharpDefaultConstraints.enumValue({enumModel, constrainedEnumModel, enumValue: undefined}); + expect(constrainedValue).toEqual('"undefined"'); + }); + }); + describe('custom constraints', () => { + test('should be able to overwrite all hooks for enum key', () => { + const mockedConstraintCallbacks: ModelEnumKeyConstraints = { + NAMING_FORMATTER: jest.fn().mockReturnValue(''), + NO_SPECIAL_CHAR: jest.fn().mockReturnValue(''), + NO_NUMBER_START_CHAR: jest.fn().mockReturnValue(''), + NO_DUPLICATE_KEYS: jest.fn().mockReturnValue(''), + NO_EMPTY_VALUE: jest.fn().mockReturnValue(''), + NO_RESERVED_KEYWORDS: jest.fn().mockReturnValue('') + }; + const constrainFunction = defaultEnumKeyConstraints(mockedConstraintCallbacks); + constrainFunction({enumModel, constrainedEnumModel, enumKey: ''}); + //Expect all callbacks to be called + for (const jestMockCallback of Object.values(mockedConstraintCallbacks)) { + expect(jestMockCallback).toHaveBeenCalled(); + } + }); + test('should be able to overwrite one hooks for enum key', () => { + //All but NAMING_FORMATTER, as we customize that + const spies = [ + jest.spyOn(DefaultEnumKeyConstraints, 'NO_SPECIAL_CHAR'), + jest.spyOn(DefaultEnumKeyConstraints, 'NO_NUMBER_START_CHAR'), + jest.spyOn(DefaultEnumKeyConstraints, 'NO_DUPLICATE_KEYS'), + jest.spyOn(DefaultEnumKeyConstraints, 'NO_EMPTY_VALUE'), + jest.spyOn(DefaultEnumKeyConstraints, 'NO_RESERVED_KEYWORDS') + ]; + const jestCallback = jest.fn().mockReturnValue(''); + const constrainFunction = defaultEnumKeyConstraints({NAMING_FORMATTER: jestCallback}); + const constrainedValue = constrainFunction({enumModel, constrainedEnumModel, enumKey: ''}); + expect(constrainedValue).toEqual(''); + for (const jestMockCallback of spies) { + expect(jestMockCallback).toHaveBeenCalled(); + } + }); + }); +}); diff --git a/test/generators/csharp/constrainer/ModelNameConstrainer.spec.ts b/test/generators/csharp/constrainer/ModelNameConstrainer.spec.ts new file mode 100644 index 0000000000..5b13d09fd2 --- /dev/null +++ b/test/generators/csharp/constrainer/ModelNameConstrainer.spec.ts @@ -0,0 +1,57 @@ +import { CSharpDefaultConstraints } from '../../../../src/generators/csharp/CSharpConstrainer'; +import { DefaultModelNameConstraints, defaultModelNameConstraints, ModelNameConstraints } from '../../../../src/generators/csharp/constrainer/ModelNameConstrainer'; +describe('ModelNameConstrainer', () => { + test('should never render special chars', () => { + const constrainedKey = CSharpDefaultConstraints.modelName({modelName: '%'}); + expect(constrainedKey).toEqual('Percent'); + }); + test('should never render number as start char', () => { + const constrainedKey = CSharpDefaultConstraints.modelName({modelName: '1'}); + expect(constrainedKey).toEqual('Number_1'); + }); + test('should never contain empty name', () => { + const constrainedKey = CSharpDefaultConstraints.modelName({modelName: ''}); + expect(constrainedKey).toEqual('Empty'); + }); + test('should use constant naming format', () => { + const constrainedKey = CSharpDefaultConstraints.modelName({modelName: 'some weird_value!"#2'}); + expect(constrainedKey).toEqual('SomeWeirdValueExclamationQuotationHash_2'); + }); + test('should never render reserved keywords', () => { + const constrainedKey = CSharpDefaultConstraints.modelName({modelName: 'return'}); + expect(constrainedKey).toEqual('ReservedReturn'); + }); + describe('custom constraints', () => { + test('should be able to overwrite all hooks', () => { + const mockedConstraintCallbacks: ModelNameConstraints = { + NAMING_FORMATTER: jest.fn().mockReturnValue(''), + NO_SPECIAL_CHAR: jest.fn().mockReturnValue(''), + NO_NUMBER_START_CHAR: jest.fn().mockReturnValue(''), + NO_EMPTY_VALUE: jest.fn().mockReturnValue(''), + NO_RESERVED_KEYWORDS: jest.fn().mockReturnValue('') + }; + const constrainFunction = defaultModelNameConstraints(mockedConstraintCallbacks); + constrainFunction({modelName: ''}); + //Expect all callbacks to be called + for (const jestMockCallback of Object.values(mockedConstraintCallbacks)) { + expect(jestMockCallback).toHaveBeenCalled(); + } + }); + test('should be able to overwrite one hooks', () => { + //All but NAMING_FORMATTER, as we customize that + const spies = [ + jest.spyOn(DefaultModelNameConstraints, 'NO_SPECIAL_CHAR'), + jest.spyOn(DefaultModelNameConstraints, 'NO_NUMBER_START_CHAR'), + jest.spyOn(DefaultModelNameConstraints, 'NO_EMPTY_VALUE'), + jest.spyOn(DefaultModelNameConstraints, 'NO_RESERVED_KEYWORDS') + ]; + const jestCallback = jest.fn().mockReturnValue(''); + const constrainFunction = defaultModelNameConstraints({NAMING_FORMATTER: jestCallback}); + const constrainedValue = constrainFunction({modelName: ''}); + expect(constrainedValue).toEqual(''); + for (const jestMockCallback of spies) { + expect(jestMockCallback).toHaveBeenCalled(); + } + }); + }); +}); diff --git a/test/generators/csharp/constrainer/PropertyKeyConstrainer.spec.ts b/test/generators/csharp/constrainer/PropertyKeyConstrainer.spec.ts new file mode 100644 index 0000000000..90350e6e05 --- /dev/null +++ b/test/generators/csharp/constrainer/PropertyKeyConstrainer.spec.ts @@ -0,0 +1,70 @@ +import { CSharpDefaultConstraints } from '../../../../src/generators/csharp/CSharpConstrainer'; +import { ConstrainedObjectModel, ObjectModel } from '../../../../src'; +import { DefaultPropertyKeyConstraints, defaultPropertyKeyConstraints, PropertyKeyConstraintOptions } from '../../../../src/generators/csharp/constrainer/PropertyKeyConstrainer'; +describe('PropertyKeyConstrainer', () => { + const objectModel = new ObjectModel('test', undefined, {}); + const constrainedObjectModel = new ConstrainedObjectModel('test', undefined, '', {}); + + test('should never render special chars', () => { + const constrainedKey = CSharpDefaultConstraints.propertyKey({constrainedObjectModel, objectModel, propertyKey: '%'}); + expect(constrainedKey).toEqual('Percent'); + }); + test('should not render number as start char', () => { + const constrainedKey = CSharpDefaultConstraints.propertyKey({constrainedObjectModel, objectModel, propertyKey: '1'}); + expect(constrainedKey).toEqual('Number_1'); + }); + test('should never contain empty name', () => { + const constrainedKey = CSharpDefaultConstraints.propertyKey({constrainedObjectModel, objectModel, propertyKey: ''}); + expect(constrainedKey).toEqual('Empty'); + }); + test('should use constant naming format', () => { + const constrainedKey = CSharpDefaultConstraints.propertyKey({constrainedObjectModel, objectModel, propertyKey: 'some weird_value!"#2'}); + expect(constrainedKey).toEqual('SomeWeirdValueExclamationQuotationHash_2'); + }); + test('should not contain duplicate properties', () => { + const objectModel = new ObjectModel('test', undefined, {}); + const propertyModel = new ConstrainedObjectModel('SomeProperty', undefined, '', {}); + const constrainedObjectModel = new ConstrainedObjectModel('test', undefined, '', {SomeProperty: propertyModel}); + const constrainedKey = CSharpDefaultConstraints.propertyKey({constrainedObjectModel, objectModel, propertyKey: 'SomeProperty'}); + expect(constrainedKey).toEqual('ReservedSomeProperty'); + }); + test('should never render reserved keywords', () => { + const constrainedKey = CSharpDefaultConstraints.propertyKey({constrainedObjectModel, objectModel, propertyKey: 'return'}); + expect(constrainedKey).toEqual('ReservedReturn'); + }); + describe('custom constraints', () => { + test('should be able to overwrite all hooks', () => { + const mockedConstraintCallbacks: PropertyKeyConstraintOptions = { + NAMING_FORMATTER: jest.fn().mockReturnValue(''), + NO_SPECIAL_CHAR: jest.fn().mockReturnValue(''), + NO_NUMBER_START_CHAR: jest.fn().mockReturnValue(''), + NO_EMPTY_VALUE: jest.fn().mockReturnValue(''), + NO_RESERVED_KEYWORDS: jest.fn().mockReturnValue(''), + NO_DUPLICATE_PROPERTIES: jest.fn().mockReturnValue('') + }; + const constrainFunction = defaultPropertyKeyConstraints(mockedConstraintCallbacks); + constrainFunction({constrainedObjectModel, objectModel, propertyKey: ''}); + //Expect all callbacks to be called + for (const jestMockCallback of Object.values(mockedConstraintCallbacks)) { + expect(jestMockCallback).toHaveBeenCalled(); + } + }); + test('should be able to overwrite one hooks', () => { + //All but NAMING_FORMATTER, as we customize that + const spies = [ + jest.spyOn(DefaultPropertyKeyConstraints, 'NO_SPECIAL_CHAR'), + jest.spyOn(DefaultPropertyKeyConstraints, 'NO_NUMBER_START_CHAR'), + jest.spyOn(DefaultPropertyKeyConstraints, 'NO_EMPTY_VALUE'), + jest.spyOn(DefaultPropertyKeyConstraints, 'NO_RESERVED_KEYWORDS'), + jest.spyOn(DefaultPropertyKeyConstraints, 'NO_DUPLICATE_PROPERTIES') + ]; + const jestCallback = jest.fn().mockReturnValue(''); + const constrainFunction = defaultPropertyKeyConstraints({NAMING_FORMATTER: jestCallback}); + const constrainedValue = constrainFunction({constrainedObjectModel, objectModel, propertyKey: ''}); + expect(constrainedValue).toEqual(''); + for (const jestMockCallback of spies) { + expect(jestMockCallback).toHaveBeenCalled(); + } + }); + }); +});