Skip to content

Commit

Permalink
feat: add csharp payload generator (#63)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonaslagoni authored Jul 19, 2024
1 parent b83d6d1 commit 5069e44
Show file tree
Hide file tree
Showing 11 changed files with 130 additions and 29 deletions.
1 change: 1 addition & 0 deletions docs/generators/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ All available generators, across languages and inputs:
|---|---|---|---|---|
| TypeScript | X | X | X | X |
| Java | X | | | X |
| C# | X | | | X |
8 changes: 6 additions & 2 deletions docs/generators/payloads.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

Input support; `asyncapi`

Language support; `typescript`, `java`
Language support; `typescript`, `java`, `csharp`

## Inputs

The `parameters` preset with `asyncapi` input generates all the message payloads for each channel in the AsyncAPI document.
The `payloads` preset with `asyncapi` input generates all the message payloads for each channel in the AsyncAPI document.

The return type is a map of channels and the model that represent the payload.

Expand All @@ -19,6 +19,7 @@ Each language has a set of constraints which means that some typed model types a
|---|---|---|---|---|---|---|---|
| **Java** | X | X | X | X | X | X | X |
| **TypeScript** | X | X | X | X | X | X | X |
| **C#** | X | X | X | X | X | X | X |

### Java

Expand All @@ -28,3 +29,6 @@ Dependencies: Jackson

Dependencies: None

### C#

Requires System.Text.Json, System.Text.Json.Serialization, System.Text.RegularExpressions and Microsoft.CSharp version 4.7 to work.
1 change: 1 addition & 0 deletions docs/protocols/nats.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ It is one of the first protocols for The Codegen Project to support, here is wha
|---|---|---|---|---|---|
| TypeScript | X | X | X | X | X |
| Java | _ | _ | _ | _ | _ | _ |
| C# | _ | _ | _ | _ | _ | _ |

All of this is available through [AsyncAPI](../inputs/asyncapi.md).
11 changes: 11 additions & 0 deletions src/codegen/generators/csharp/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {
CsharpPayloadGenerator,
generateCsharpPayload,
defaultCsharpPayloadGenerator
} from './payloads';

export {
CsharpPayloadGenerator,
generateCsharpPayload,
defaultCsharpPayloadGenerator
};
53 changes: 53 additions & 0 deletions src/codegen/generators/csharp/payloads.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {CSHARP_JSON_SERIALIZER_PRESET, CSharpFileGenerator} from '@asyncapi/modelina';
import {GenericCodegenContext, PayloadRenderType} from '../../types';
import {AsyncAPIDocumentInterface} from '@asyncapi/parser';
import {generateAsyncAPIPayloads} from '../helpers/payloads';
import {z} from 'zod';

export const zodCsharpPayloadGenerator = z.object({
id: z.string().optional().default('payloads-csharp'),
dependencies: z.array(z.string()).optional().default([]),
preset: z.literal('payloads').default('payloads'),
outputPath: z.string().default('src/__gen__/payloads'),
serializationType: z.literal('json').optional().default('json'),
language: z.literal('csharp').optional().default('csharp'),
namespace: z.string().optional().default('the.codegen.project')
});
export type CsharpPayloadGenerator = z.infer<
typeof zodCsharpPayloadGenerator
>;

export const defaultCsharpPayloadGenerator: CsharpPayloadGenerator =
zodCsharpPayloadGenerator.parse({});

export interface CsharpPayloadContext extends GenericCodegenContext {
inputType: 'asyncapi';
asyncapiDocument?: AsyncAPIDocumentInterface;
generator: CsharpPayloadGenerator;
}

export async function generateCsharpPayload(
context: CsharpPayloadContext
): Promise<PayloadRenderType<CsharpPayloadGenerator>> {
const {asyncapiDocument, inputType, generator} = context;
if (inputType === 'asyncapi' && asyncapiDocument === undefined) {
throw new Error('Expected AsyncAPI input, was not given');
}

const modelinaGenerator = new CSharpFileGenerator({
presets: [
CSHARP_JSON_SERIALIZER_PRESET
]
});
return generateAsyncAPIPayloads(
asyncapiDocument!,
(input) =>
modelinaGenerator.generateToFiles(
input,
generator.outputPath,
{namespace: generator.namespace},
true
),
generator
);
}
7 changes: 3 additions & 4 deletions src/codegen/generators/helpers/payloads.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import {AsyncAPIInputProcessor, OutputModel} from '@asyncapi/modelina';
import {AsyncAPIDocumentInterface} from '@asyncapi/parser';
import {PayloadRenderType} from '../../types';
import {TypeScriptPayloadGenerator} from '../typescript/payloads';

export async function generateAsyncAPIPayloads(
export async function generateAsyncAPIPayloads<GeneratorType>(
asyncapiDocument: AsyncAPIDocumentInterface,
generator: (input: any) => Promise<OutputModel[]>,
generatorConfig: TypeScriptPayloadGenerator
): Promise<PayloadRenderType> {
generatorConfig: GeneratorType
): Promise<PayloadRenderType<GeneratorType>> {
const returnType: Record<string, OutputModel> = {};
for (const channel of asyncapiDocument.allChannels().all()) {
let schemaObj: any = {
Expand Down
15 changes: 14 additions & 1 deletion src/codegen/generators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
defaultTypeScriptChannelsGenerator
} from './typescript';
import {defaultCustomGenerator} from './generic/custom';
import { CsharpPayloadGenerator, generateCsharpPayload } from './csharp';

export {
TypeScriptChannelsGenerator,
Expand Down Expand Up @@ -73,7 +74,7 @@ export async function renderGenerator(

case 'java': {
return generateJavaPayload({
documentPath,
asyncapiDocument,
generator: {
...generator,
outputPath
Expand All @@ -83,6 +84,18 @@ export async function renderGenerator(
});
}

case 'csharp': {
return generateCsharpPayload({
asyncapiDocument,
generator: {
...generator,
outputPath
} as CsharpPayloadGenerator,
inputType: configuration.inputType,
dependencyOutputs: renderedContext
});
}

default: {
throw new Error(
'Unable to determine language generator for payloads preset'
Expand Down
35 changes: 21 additions & 14 deletions src/codegen/generators/java/payloads.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {JAVA_JACKSON_PRESET, JavaFileGenerator} from '@asyncapi/modelina';
import {Logger} from '../../../LoggingInterface';
import {GenericCodegenContext} from '../../types';
import {GenericCodegenContext, PayloadRenderType} from '../../types';
import {z} from 'zod';
import { generateAsyncAPIPayloads } from '../helpers/payloads';
import { AsyncAPIDocumentInterface } from '@asyncapi/parser';

export const zodJavaPayloadGenerator = z.object({
id: z.string().optional().default('payloads-java'),
Expand All @@ -19,27 +20,33 @@ export type JavaPayloadGenerator = z.infer<typeof zodJavaPayloadGenerator>;

export interface JavaPayloadContext extends GenericCodegenContext {
inputType: 'asyncapi';
documentPath: string;
asyncapiDocument?: AsyncAPIDocumentInterface;
generator: JavaPayloadGenerator;
}

export const defaultJavaPayloadGenerator: JavaPayloadGenerator =
zodJavaPayloadGenerator.parse({});

export async function generateJavaPayload(context: JavaPayloadContext) {
const {documentPath, generator} = context;
export async function generateJavaPayload(context: JavaPayloadContext): Promise<PayloadRenderType<JavaPayloadGenerator>> {
const {asyncapiDocument, inputType, generator} = context;
if (inputType === 'asyncapi' && asyncapiDocument === undefined) {
throw new Error('Expected AsyncAPI input, was not given');
}

const modelinaGenerator = new JavaFileGenerator({
presets: [
{
preset: JAVA_JACKSON_PRESET
}
JAVA_JACKSON_PRESET
]
});
const models = await modelinaGenerator.generateToFiles(
`file://${documentPath}`,
generator.outputPath,
{packageName: generator.packageName},
true
return generateAsyncAPIPayloads(
asyncapiDocument!,
(input) =>
modelinaGenerator.generateToFiles(
input,
generator.outputPath,
{packageName: generator.packageName},
true
),
generator
);
Logger.info(`Generated ${models.length} models to ${generator.outputPath}`);
}
2 changes: 1 addition & 1 deletion src/codegen/generators/typescript/payloads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export interface TypeScriptPayloadContext extends GenericCodegenContext {

export async function generateTypescriptPayload(
context: TypeScriptPayloadContext
): Promise<PayloadRenderType> {
): Promise<PayloadRenderType<TypeScriptPayloadGenerator>> {
const {asyncapiDocument, inputType, generator} = context;
if (inputType === 'asyncapi' && asyncapiDocument === undefined) {
throw new Error('Expected AsyncAPI input, was not given');
Expand Down
18 changes: 13 additions & 5 deletions src/codegen/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ import {
import {AsyncAPIDocumentInterface} from '@asyncapi/parser';
import {CustomGenerator, zodCustomGenerator} from './generators/generic/custom';
import {z} from 'zod';
import { CsharpPayloadGenerator, zodCsharpPayloadGenerator } from './generators/csharp/payloads';
export type PresetTypes = 'payloads' | 'parameters' | 'channels' | 'custom';
export interface LoadArgument {
configPath: string;
configType: 'esm' | 'json' | 'yaml';
}
export type SupportedLanguages = 'typescript' | 'java';
export type SupportedLanguages = 'typescript' | 'java' | 'csharp';
export interface GenericCodegenContext {
dependencyOutputs?: Record<string, any>;
}
Expand All @@ -40,13 +41,20 @@ export const zodAsyncAPIJavaGenerators = z.discriminatedUnion('preset', [
zodCustomGenerator
]);

export const zodAsyncAPICsharpGenerators = z.discriminatedUnion('preset', [
zodCsharpPayloadGenerator,
zodCustomGenerator
]);

export const zodAsyncAPIGenerators = z.union([
...zodAsyncAPITypeScriptGenerators.options,
...zodAsyncAPIJavaGenerators.options
...zodAsyncAPIJavaGenerators.options,
...zodAsyncAPICsharpGenerators.options
]);

export type Generators =
| JavaPayloadGenerator
| CsharpPayloadGenerator
| TypeScriptPayloadGenerator
| TypescriptParametersGenerator
| TypeScriptChannelsGenerator
Expand All @@ -62,9 +70,9 @@ export interface ParameterRenderType {
channelModels: Record<string, OutputModel | undefined>;
generator: TypescriptParametersGenerator;
}
export interface PayloadRenderType {
export interface PayloadRenderType<GeneratorType> {
channelModels: Record<string, OutputModel>;
generator: TypeScriptPayloadGenerator;
generator: GeneratorType;
}
export interface SingleFunctionRenderType {
functionName: string;
Expand All @@ -75,7 +83,7 @@ export interface SingleFunctionRenderType {
export const zodAsyncAPICodegenConfiguration = z.object({
inputType: z.literal('asyncapi'),
inputPath: z.string(),
language: z.enum(['typescript', 'java']).optional(),
language: z.enum(['typescript', 'java', 'csharp']).optional(),
generators: z.array(zodAsyncAPIGenerators)
});

Expand Down
8 changes: 6 additions & 2 deletions test/codegen/generators/typescript/channels.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import path from "node:path";
import { generateTypeScriptChannels } from "../../../../src/codegen/generators";
import { generateTypeScriptChannels, TypeScriptPayloadGenerator } from "../../../../src/codegen/generators";
import { loadAsyncapi } from "../../../helpers";
jest.mock('node:fs/promises', () => ({
writeFile: jest.fn().mockResolvedValue(undefined),
Expand All @@ -23,7 +23,7 @@ describe('channels', () => {
},
generator: {outputPath: './test'} as any
};
const payloadsDependency: PayloadRenderType = {
const payloadsDependency: PayloadRenderType<TypeScriptPayloadGenerator> = {
channelModels: {
"user/signedup": payloadModel
},
Expand All @@ -35,6 +35,10 @@ describe('channels', () => {
preset: 'channels',
protocols: ['nats'],
language: 'typescript',
parameterGeneratorId: 'parameters-typescript',
payloadGeneratorId: 'payloads-typescript',
dependencies: ['parameters-typescript', 'payloads-typescript'],
id: 'test'
},
inputType: 'asyncapi',
asyncapiDocument: parsedAsyncAPIDocument,
Expand Down

0 comments on commit 5069e44

Please sign in to comment.