diff --git a/docs/presets.md b/docs/presets.md index 8775e6ff2d..634e8a0831 100644 --- a/docs/presets.md +++ b/docs/presets.md @@ -446,6 +446,8 @@ This preset is a generator for the meta model `ConstrainedEnumModel` and [can be | Method | Description | Additional arguments | |---|---|---| | `item` | A method to extend enum's item. | `item` object as a [`ConstrainedEnumValueModel`](./internal-model.md#the-constrained-meta-model) instance, which contains the value and key of enum's item. | +| `extension` | A method to extend the enums extension class. | | +| `extensionMethods` | A method to extend the enums extension class methods instead of the whole class. | | ### Rust #### **Struct** diff --git a/src/generators/csharp/CSharpPreset.ts b/src/generators/csharp/CSharpPreset.ts index 4aae3a4a70..ac66c1c36c 100644 --- a/src/generators/csharp/CSharpPreset.ts +++ b/src/generators/csharp/CSharpPreset.ts @@ -5,7 +5,8 @@ import { PresetArgs, PropertyArgs, ConstrainedObjectModel, - InterfacePreset + InterfacePreset, + ConstrainedEnumModel } from '../../models'; import { CSharpOptions } from './CSharpGenerator'; import { @@ -40,12 +41,18 @@ export interface CsharpRecordPreset export type ClassPresetType = CsharpClassPreset; export type RecordPresetType = CsharpRecordPreset; -export type EnumPresetType = EnumPreset; - +export interface EnumPresetType extends EnumPreset { + extension?: ( + args: PresetArgs + ) => Promise | string; + extensionMethods?: ( + args: PresetArgs + ) => Promise | string; +}; export type CSharpPreset = Preset<{ class: CsharpClassPreset; record: CsharpRecordPreset; - enum: EnumPreset; + enum: EnumPresetType; }>; export const CSHARP_DEFAULT_PRESET: CSharpPreset = { diff --git a/src/generators/csharp/constrainer/EnumConstrainer.ts b/src/generators/csharp/constrainer/EnumConstrainer.ts index 2b808a9527..b5aa0ccefe 100644 --- a/src/generators/csharp/constrainer/EnumConstrainer.ts +++ b/src/generators/csharp/constrainer/EnumConstrainer.ts @@ -75,6 +75,11 @@ export function defaultEnumKeyConstraints( export function defaultEnumValueConstraints(): CSharpEnumValueConstraint { return ({ enumValue }) => { let normalizedEnumValue; + if(enumValue === null) return enumValue; + if(Array.isArray(enumValue)) return `"${JSON.stringify(enumValue).replace( + /"/g, + '\\"' + )}"`; switch (typeof enumValue) { case 'boolean': case 'bigint': diff --git a/src/generators/csharp/presets/JsonSerializerPreset.ts b/src/generators/csharp/presets/JsonSerializerPreset.ts index d160775e0c..2f5d678c6d 100644 --- a/src/generators/csharp/presets/JsonSerializerPreset.ts +++ b/src/generators/csharp/presets/JsonSerializerPreset.ts @@ -14,15 +14,14 @@ function renderSerializeProperty( modelInstanceVariable: string, model: ConstrainedObjectPropertyModel ) { - let value = modelInstanceVariable; //Special case where a referenced enum model need to be accessed if ( model.property instanceof ConstrainedReferenceModel && model.property.ref instanceof ConstrainedEnumModel ) { - value = `${modelInstanceVariable}${model.required ? '' : '?'}.GetValue()`; + return `writer.WriteRawValue(${modelInstanceVariable}${model.required ? '' : '?'}.GetRawJsonValue(), true);`; } - return `JsonSerializer.Serialize(writer, ${value}, options);`; + return `JsonSerializer.Serialize(writer, ${modelInstanceVariable}, options);`; } function renderSerializeProperties(model: ConstrainedObjectModel) { @@ -31,7 +30,7 @@ function renderSerializeProperties(model: ConstrainedObjectModel) { for (const [propertyName, propertyModel] of Object.entries( model.properties )) { - const modelInstanceVariable = `value.${pascalCase(propertyName)}`; + const modelInstanceVariable = `value?.${pascalCase(propertyName)}`; if ( propertyModel.property instanceof ConstrainedDictionaryModel && propertyModel.property.serializationType === 'unwrap' @@ -74,13 +73,13 @@ function renderPropertiesList( ); }) .map((value) => { - return `prop.Name != "${pascalCase(value.propertyName)}"`; + return `prop.Name != "${pascalCase(value?.propertyName)}"`; }); - let propertiesList = 'var properties = value.GetType().GetProperties();'; + let propertiesList = 'var properties = value?.GetType().GetProperties();'; if (unwrappedDictionaryProperties.length > 0) { renderer.dependencyManager.addDependency('using System.Linq;'); - propertiesList = `var properties = value.GetType().GetProperties().Where(prop => ${unwrappedDictionaryProperties.join( + propertiesList = `var properties = value?.GetType().GetProperties().Where(prop => ${unwrappedDictionaryProperties.join( ' && ' )});`; } @@ -103,11 +102,6 @@ function renderSerialize({ model.name } value, JsonSerializerOptions options) { - if (value == null) - { - JsonSerializer.Serialize(writer, null, options); - return; - } ${propertiesList} writer.WriteStartObject(); @@ -124,9 +118,15 @@ function renderDeserializeProperty(model: ConstrainedObjectPropertyModel) { model.property instanceof ConstrainedReferenceModel && model.property.ref instanceof ConstrainedEnumModel ) { - return `${model.property.name}Extensions.To${model.property.name}(JsonSerializer.Deserialize(ref reader, options))`; + return `${model.property.name}Extensions.FromJsonTo${model.property.name}(JsonSerializer.Deserialize(ref reader))`; + } else if ( + model.property instanceof ConstrainedReferenceModel && + model.property.ref instanceof ConstrainedObjectModel + ) { + return `JsonSerializer.Deserialize<${model.property.name}>(ref reader)`; } - return `JsonSerializer.Deserialize<${model.property.type}>(ref reader, options)`; + + return `JsonSerializer.Deserialize<${model.property.type}>(ref reader)`; } function renderDeserializeProperties(model: ConstrainedObjectModel) { @@ -141,7 +141,7 @@ function renderDeserializeProperties(model: ConstrainedObjectModel) { ) { return `if(instance.${pascalProp} == null) { instance.${pascalProp} = new Dictionary<${ propModel.property.key.type - }, ${propModel.property.value.type}>(); } + }, ${propModel.property.value?.type}>(); } var deserializedValue = ${renderDeserializeProperty(propModel)}; instance.${pascalProp}.Add(propertyName, deserializedValue); continue;`; @@ -151,8 +151,7 @@ function renderDeserializeProperties(model: ConstrainedObjectModel) { } return `if (propertyName == "${propModel.unconstrainedPropertyName}") { - var value = ${renderDeserializeProperty(propModel)}; - instance.${pascalProp} = value; + instance.${pascalProp} = ${renderDeserializeProperty(propModel)}; continue; }`; }) @@ -196,6 +195,9 @@ function renderDeserialize({ } string propertyName = reader.GetString(); + + // Advance to the value token + reader.Read(); ${renderer.indent(deserializeProperties, 4)} } @@ -219,11 +221,9 @@ public string Serialize(JsonSerializerOptions options = null) { return JsonSerializer.Serialize(this, options); } -public static ${model.type} Deserialize(string json) +public static ${model.type} Deserialize(string json, JsonSerializerOptions options = null) { - var deserializeOptions = new JsonSerializerOptions(); - deserializeOptions.Converters.Add(new ${model.name}Converter()); - return JsonSerializer.Deserialize<${model.type}>(json, deserializeOptions); + return JsonSerializer.Deserialize<${model.type}>(json, options); }`; return `${content}\n${renderer.indent(supportFunctions)}`; }, @@ -244,16 +244,100 @@ ${content} internal class ${model.name}Converter : JsonConverter<${model.name}> { - public override bool CanConvert(System.Type objectType) - { - // this converter can be applied to any type - return true; - } ${renderer.indent(deserialize)} ${renderer.indent(serialize)} +}`; + } + }, + enum: { + self({content, renderer}) { + renderer.dependencyManager.addDependency('using System.Text.Json;') + return content; + }, + extensionMethods({content, model, renderer}) { + const enums = model.values || []; + const items: string[] = []; + const items2: string[] = []; + + for (const enumValue of enums) { + let jsonValue = enumValue.value; + const originalEnumValue = enumValue.originalInput; + let stringValue = jsonValue; + if(typeof jsonValue !== 'string') stringValue = `"${jsonValue}"`; + items.push( + `case ${model.name}.${enumValue.key}: return ${stringValue};` + ); + + if(typeof originalEnumValue === 'string'){ + items2.push( + `if (value?.ValueKind == JsonValueKind.String && value?.GetString() == ${enumValue.value}) +{ + return ${model.name}.${enumValue.key}; +}` + ); + } else if(originalEnumValue === null){ + items2.push( + `if (value == null || value?.ValueKind == JsonValueKind.Null && value?.GetRawText() == "null") +{ + return ${model.name}.${enumValue.key}; +}` + ); + } else if(typeof originalEnumValue === 'boolean'){ + items2.push( + `if (value?.ValueKind == JsonValueKind.True || value?.ValueKind == JsonValueKind.False && value?.GetBoolean() == ${enumValue.value}) +{ + return ${model.name}.${enumValue.key}; +}` + ); + } else if(Array.isArray(originalEnumValue)){ + items2.push( + `if (value?.ValueKind == JsonValueKind.Array && value?.GetRawText() == ${enumValue.value}) +{ + return ${model.name}.${enumValue.key}; +}` + ); + } else if(typeof originalEnumValue === 'object'){ + items2.push( + `if (value?.ValueKind == JsonValueKind.Object && value?.GetRawText() == ${enumValue.value}) +{ + return ${model.name}.${enumValue.key}; +}` + ); + } else if(typeof originalEnumValue === 'number'){ + items2.push( + `if (value?.ValueKind == JsonValueKind.Number && value?.GetInt32() == ${enumValue.value}) +{ + return ${model.name}.${enumValue.key}; +}` + ); + } else if(typeof originalEnumValue === 'bigint'){ + items2.push( + `if (value?.ValueKind == JsonValueKind.Number && value?.GetInt64() == ${enumValue.value}) +{ + return ${model.name}.${enumValue.key}; +}` + ); + } + } + const newstuff = items.join('\n'); + const newstuff2 = items2.join('\n'); + return `${content} + +public static string? GetRawJsonValue(this ${model.name} enumValue) +{ + switch (enumValue) + { +${renderer.indent(newstuff, 3)} + } + return null; } -`; - } + +public static ${model.type}? FromJsonTo${model.name}(JsonElement? value) +{ +${renderer.indent(newstuff2, 2)} + return null; +}`; + }, } }; diff --git a/src/generators/csharp/renderers/EnumRenderer.ts b/src/generators/csharp/renderers/EnumRenderer.ts index a7e53d1040..093153463d 100644 --- a/src/generators/csharp/renderers/EnumRenderer.ts +++ b/src/generators/csharp/renderers/EnumRenderer.ts @@ -14,42 +14,15 @@ import { CSharpOptions } from '../CSharpGenerator'; export class EnumRenderer extends CSharpRenderer { async defaultSelf(): Promise { const enumItems = await this.renderItems(); - const getValueCaseItemValues = this.getValueCaseItemValues(); - const toEnumCaseItemValues = this.toEnumCaseItemValues(); - const enumValueSwitch = `switch (enumValue) -{ -${this.indent(getValueCaseItemValues)} -} -return null;`; - const valueSwitch = `switch (value) -{ -${this.indent(toEnumCaseItemValues)} -} -return null;`; - const classContent = `public static ${this.model.type}? GetValue(this ${ - this.model.name - } enumValue) -{ -${this.indent(enumValueSwitch)} -} - -public static ${this.model.name}? To${this.model.name}(dynamic? value) -{ -${this.indent(valueSwitch)} -}`; + const extension = await this.runExtensionPreset(); return `public enum ${this.model.name} { ${this.indent(enumItems)} } - -public static class ${this.model.name}Extensions -{ -${this.indent(classContent)} -} +${this.indent(extension)} `; } - async renderItems(): Promise { const enums = this.model.values || []; const items: string[] = []; @@ -93,6 +66,12 @@ ${this.indent(classContent)} runItemPreset(item: ConstrainedEnumValueModel): Promise { return this.runPreset('item', { item }); } + runExtensionPreset(): Promise { + return this.runPreset('extension'); + } + runExtensionMethodsPreset(): Promise { + return this.runPreset('extensionMethods'); + } } export const CSHARP_DEFAULT_ENUM_PRESET: EnumPresetType = { @@ -101,5 +80,38 @@ export const CSHARP_DEFAULT_ENUM_PRESET: EnumPresetType = { }, item({ item }) { return item.key; + }, + async extension({ renderer, model }) { + const extensionMethods = await renderer.runExtensionMethodsPreset(); + return `public static class ${model.name}Extensions +{ +${renderer.indent(extensionMethods)} +}` + }, + extensionMethods({ model, renderer }) { + const getValueCaseItemValues = renderer.getValueCaseItemValues(); + const toEnumCaseItemValues = renderer.toEnumCaseItemValues(); + const enumValueSwitch = `switch (enumValue) +{ +${renderer.indent(getValueCaseItemValues)} +} +return null;`; + const valueSwitch = `switch (value) +{ +${renderer.indent(toEnumCaseItemValues)} +} +return null;`; + const classContent = `public static ${model.type}? GetValue(this ${ + model.name + } enumValue) +{ +${renderer.indent(enumValueSwitch)} +} + +public static ${model.name}? To${model.name}(dynamic? value) +{ +${renderer.indent(valueSwitch)} +}`; + return classContent; } }; diff --git a/src/generators/typescript/index.ts b/src/generators/typescript/index.ts index 2861978502..d50285bd4e 100644 --- a/src/generators/typescript/index.ts +++ b/src/generators/typescript/index.ts @@ -3,6 +3,7 @@ export * from './TypeScriptFileGenerator'; export { TS_DEFAULT_PRESET } from './TypeScriptPreset'; export type { TypeScriptPreset } from './TypeScriptPreset'; export * from './presets'; +export {RESERVED_TYPESCRIPT_KEYWORDS} from './Constants'; export { defaultEnumKeyConstraints as typeScriptDefaultEnumKeyConstraints, diff --git a/src/generators/typescript/renderers/TypeRenderer.ts b/src/generators/typescript/renderers/TypeRenderer.ts index d80a67f441..4d75ff840a 100644 --- a/src/generators/typescript/renderers/TypeRenderer.ts +++ b/src/generators/typescript/renderers/TypeRenderer.ts @@ -9,8 +9,14 @@ import { TypeScriptOptions } from '../TypeScriptGenerator'; * @extends TypeScriptRenderer */ export class TypeRenderer extends TypeScriptRenderer { - defaultSelf(): string { - return `type ${this.model.name} = ${this.model.type};`; + async defaultSelf(): Promise { + const content = [ + await this.runAdditionalContentPreset() + ]; + + return `type ${this.model.name} = ${this.model.type}; + +${this.renderBlock(content, 2)}`; } } diff --git a/src/processors/AsyncAPIInputProcessor.ts b/src/processors/AsyncAPIInputProcessor.ts index bac4136004..ed1580dc27 100644 --- a/src/processors/AsyncAPIInputProcessor.ts +++ b/src/processors/AsyncAPIInputProcessor.ts @@ -121,7 +121,7 @@ export class AsyncAPIInputProcessor extends AbstractInputProcessor { const channels = doc.channels(); if (channels.length) { - for (const channel of doc.channels()) { + for (const channel of channels) { for (const operation of channel.operations()) { const handleMessages = (messages: MessagesInterface) => { // treat multiple messages as oneOf diff --git a/test/generators/csharp/CSharpGenerator.spec.ts b/test/generators/csharp/CSharpGenerator.spec.ts index 86f81aa603..3678eaecfb 100644 --- a/test/generators/csharp/CSharpGenerator.spec.ts +++ b/test/generators/csharp/CSharpGenerator.spec.ts @@ -173,7 +173,7 @@ describe('CSharpGenerator', () => { test('should render `enum` type', async () => { const doc = { $id: 'Things', - enum: ['Texas', '1', 1, false, { test: 'test' }] + enum: ['Texas', '1', 1, false, { test: 'test' }, ["test", 1], null] }; const models = await generator.generate(doc); expect(models).toHaveLength(1); diff --git a/test/generators/csharp/presets/JsonSerializerPreset.spec.ts b/test/generators/csharp/presets/JsonSerializerPreset.spec.ts index e801b9f087..9317af4591 100644 --- a/test/generators/csharp/presets/JsonSerializerPreset.spec.ts +++ b/test/generators/csharp/presets/JsonSerializerPreset.spec.ts @@ -13,7 +13,7 @@ const doc = { numberProp: { type: 'number' }, enumProp: { $id: 'EnumTest', - enum: ['Some enum String', true, { test: 'test' }, 2] + enum: ['Some enum String', true, { test: 'test' }, 2, ["test", 1], null] }, objectProp: { type: 'object',