Skip to content

Commit

Permalink
feat: add typescript headers generation (#162)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonaslagoni authored Oct 31, 2024
1 parent c0b0a7b commit cc9f0a2
Show file tree
Hide file tree
Showing 14 changed files with 333 additions and 3 deletions.
43 changes: 40 additions & 3 deletions src/codegen/generators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ import {
TypeScriptChannelsContext,
TypeScriptClientContext,
TypeScriptPayloadContext,
TypescriptParametersContext
TypescriptParametersContext,
TypescriptHeadersContext,
TypescriptHeadersGenerator,
defaultTypeScriptHeadersOptions,
generateTypescriptHeaders
} from './typescript';
import {defaultCustomGenerator, CustomGenerator} from './generic/custom';
import {TypeScriptPayloadGeneratorInternal} from './typescript/payloads';
Expand All @@ -34,6 +38,7 @@ import {loadAndRealizeConfigFile} from '../configuration-manager';
import {loadAsyncapi} from '../inputs/asyncapi';
import {runGenerators} from '..';
import {TypeScriptClientGeneratorInternal} from './typescript/client';
import { TypescriptHeadersGeneratorInternal } from './typescript/headers';

export {
TypeScriptChannelsGenerator,
Expand All @@ -53,7 +58,11 @@ export {
TypeScriptChannelsContext,
TypeScriptClientContext,
TypeScriptPayloadContext,
TypescriptParametersContext
TypescriptParametersContext,
TypescriptHeadersContext,
TypescriptHeadersGenerator,
defaultTypeScriptHeadersOptions,
generateTypescriptHeaders
};

export async function renderGenerator(
Expand Down Expand Up @@ -94,6 +103,27 @@ export async function renderGenerator(
}
}
}
case 'headers': {
switch (language) {
case 'typescript': {
return generateTypescriptHeaders({
asyncapiDocument,
generator: {
...(generator as TypescriptHeadersGeneratorInternal),
outputPath
},
inputType: configuration.inputType,
dependencyOutputs: renderedContext
});
}

default: {
throw new Error(
'Unable to determine language generator for headers preset'
);
}
}
}

case 'parameters': {
switch (language) {
Expand Down Expand Up @@ -155,7 +185,7 @@ export async function renderGenerator(

default: {
throw new Error(
'Unable to determine language generator for channels preset'
'Unable to determine language generator for client preset'
);
}
}
Expand Down Expand Up @@ -188,6 +218,13 @@ export function getDefaultConfiguration(
default:
return undefined;
}
case 'headers':
switch (language) {
case 'typescript':
return defaultTypeScriptHeadersOptions;
default:
return undefined;
}
case 'channels':
switch (language) {
case 'typescript':
Expand Down
89 changes: 89 additions & 0 deletions src/codegen/generators/typescript/headers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {
OutputModel,
TS_COMMON_PRESET,
TS_DESCRIPTION_PRESET,
TypeScriptFileGenerator
} from '@asyncapi/modelina';
import {AsyncAPIDocumentInterface} from '@asyncapi/parser';
import {GenericCodegenContext, HeadersRenderType} from '../../types';
import {z} from 'zod';
import {defaultCodegenTypescriptModelinaOptions, pascalCase} from './utils';

export const zodTypescriptHeadersGenerator = z.object({
id: z.string().optional().default('headers-typescript'),
dependencies: z.array(z.string()).optional().default([]),
preset: z.literal('headers').default('headers'),
outputPath: z.string().default('src/__gen__/headers'),
serializationType: z.literal('json').optional().default('json'),
language: z.literal('typescript').optional().default('typescript')
});

export type TypescriptHeadersGenerator = z.input<
typeof zodTypescriptHeadersGenerator
>;
export type TypescriptHeadersGeneratorInternal = z.infer<
typeof zodTypescriptHeadersGenerator
>;

export const defaultTypeScriptHeadersOptions: TypescriptHeadersGenerator =
zodTypescriptHeadersGenerator.parse({});

export interface TypescriptHeadersContext extends GenericCodegenContext {
inputType: 'asyncapi';
asyncapiDocument?: AsyncAPIDocumentInterface;
generator: TypescriptHeadersGeneratorInternal;
}

export type TypeScriptHeadersRenderType = HeadersRenderType;

export async function generateTypescriptHeaders(
context: TypescriptHeadersContext
): Promise<TypeScriptHeadersRenderType> {
const {asyncapiDocument, inputType, generator} = context;
if (inputType === 'asyncapi' && asyncapiDocument === undefined) {
throw new Error('Expected AsyncAPI input, was not given');
}
const modelinaGenerator = new TypeScriptFileGenerator({
...defaultCodegenTypescriptModelinaOptions,
enumType: 'union',
useJavascriptReservedKeywords: false,
presets: [
TS_DESCRIPTION_PRESET,
{
preset: TS_COMMON_PRESET,
options: {
marshalling: true
}
},
]
});
const returnType: Record<string, OutputModel | undefined> = {};
for (const channel of asyncapiDocument!.allChannels().all()) {
const messages = channel.messages().all();
for (const message of messages) {
if (message.hasHeaders()) {
const schemaObj: any = {
additionalProperties: false,
...message.headers()?.json(),
type: 'object',
$id: pascalCase(`${message.id()}_headers`),
$schema: 'http://json-schema.org/draft-07/schema'
};
const models = await modelinaGenerator.generateToFiles(
schemaObj,
generator.outputPath,
{exportType: 'named'},
true
);
returnType[channel.id()] = models[0];
} else {
returnType[channel.id()] = undefined;
}
}
}

return {
channelModels: returnType,
generator
};
}
6 changes: 6 additions & 0 deletions src/codegen/generators/typescript/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,9 @@ export {
defaultTypeScriptClientGenerator,
TypeScriptClientContext
} from './client';
export {
generateTypescriptHeaders,
TypescriptHeadersContext,
TypescriptHeadersGenerator,
defaultTypeScriptHeadersOptions
} from './headers';
8 changes: 8 additions & 0 deletions src/codegen/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ import {CustomGenerator, zodCustomGenerator} from './generators/generic/custom';
import {z} from 'zod';
import {TypeScriptClientGenerator} from './generators';
import {zodTypescriptClientGenerator} from './generators/typescript/client';
import { TypescriptHeadersGenerator, zodTypescriptHeadersGenerator } from './generators/typescript/headers';
export type PresetTypes =
| 'payloads'
| 'parameters'
| 'headers'
| 'channels'
| 'custom'
| 'client';
Expand All @@ -36,6 +38,7 @@ export const zodAsyncAPITypeScriptGenerators = z.discriminatedUnion('preset', [
zodTypescriptParametersGenerator,
zodTypescriptChannelsGenerator,
zodTypescriptClientGenerator,
zodTypescriptHeadersGenerator,
zodCustomGenerator
]);

Expand All @@ -44,6 +47,7 @@ export const zodAsyncAPIGenerators = z.union([
]);

export type Generators =
| TypescriptHeadersGenerator
| TypeScriptPayloadGenerator
| TypescriptParametersGenerator
| TypeScriptChannelsGenerator
Expand All @@ -60,6 +64,10 @@ export interface ParameterRenderType {
channelModels: Record<string, OutputModel | undefined>;
generator: TypescriptParametersGenerator;
}
export interface HeadersRenderType {
channelModels: Record<string, OutputModel | undefined>;
generator: TypescriptHeadersGenerator;
}
export interface ChannelPayload {
messageModel: OutputModel;
messageType: string;
Expand Down
40 changes: 40 additions & 0 deletions src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const inquirer = require('inquirer');
import {
defaultTypeScriptChannelsGenerator,
defaultTypeScriptClientGenerator,
defaultTypeScriptHeadersOptions,
defaultTypeScriptParametersOptions,
defaultTypeScriptPayloadGenerator
} from '../codegen/generators';
Expand Down Expand Up @@ -46,6 +47,7 @@ interface FlagTypes {
includeParameters: boolean;
includeChannels: boolean;
includeClient: boolean;
includeHeaders: boolean;
languages: (typeof LanguageOptions)[number];
noOutput: boolean;
}
Expand Down Expand Up @@ -96,6 +98,20 @@ export default class Init extends Command {
}
]
}),
'include-headers': Flags.boolean({
description: 'Include headers generation, available for TypeScript',
relationships: [
{
flags: [
{
name: 'languages',
when: async (flags: any) => flags['languages'] === 'typescript'
}
],
type: 'all'
}
]
}),
'include-client': Flags.boolean({
description: 'Include client generation, available for TypeScript',
relationships: [
Expand Down Expand Up @@ -173,6 +189,7 @@ export default class Init extends Command {
const configType = flags['config-type'];
const outputDirectory = flags['output-directory'];
const includePayloads = flags['include-payloads'];
const includeHeaders = flags['include-headers'];
const includeClient = flags['include-client'];
const includeParameters = flags['include-parameters'];
const includeChannels = flags['include-channels'];
Expand All @@ -182,6 +199,7 @@ export default class Init extends Command {
includeChannels,
includeParameters,
includePayloads,
includeHeaders,
includeClient,
outputDirectory,
configName,
Expand All @@ -202,6 +220,7 @@ export default class Init extends Command {
includeChannels,
includeParameters,
includePayloads,
includeHeaders,
includeClient,
configName,
outputDirectory,
Expand Down Expand Up @@ -303,6 +322,14 @@ export default class Init extends Command {
when: (flags: any) => flags['languages'] === 'typescript'
});
}
if (!includeHeaders) {
questions.push({
name: 'includeHeaders',
message: 'Do you want to include headers structures?',
type: 'confirm',
when: (flags: any) => flags['languages'] === 'typescript'
});
}
if (!includeParameters) {
questions.push({
name: 'includeParameters',
Expand Down Expand Up @@ -334,6 +361,9 @@ export default class Init extends Command {
if (includePayloads === undefined) {
includePayloads = answers.includePayloads;
}
if (includeHeaders === undefined) {
includeHeaders = answers.includeHeaders;
}
if (includeClient === undefined) {
includeClient = answers.includeClient;
}
Expand All @@ -356,6 +386,7 @@ export default class Init extends Command {
includeChannels,
includeParameters,
includePayloads,
includeHeaders,
includeClient,
inputFile,
inputType,
Expand Down Expand Up @@ -397,6 +428,15 @@ export default class Init extends Command {
configuration.generators.push(generator);
}
}
if (flags.includeHeaders) {
if (flags.languages === 'typescript') {
const generator: any = {...defaultTypeScriptHeadersOptions};
delete generator.dependencies;
delete generator.id;
delete generator.language;
configuration.generators.push(generator);
}
}
if (flags.includeClient) {
if (flags.languages === 'typescript') {
const generator: any = {...defaultTypeScriptClientGenerator};
Expand Down
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,17 @@ export {
defaultTypeScriptPayloadGenerator,
defaultTypeScriptClientGenerator,
defaultCustomGenerator,
defaultTypeScriptHeadersOptions,
TypeScriptClientGenerator,
TypeScriptChannelsGenerator,
TypeScriptPayloadGenerator,
TypescriptParametersGenerator,
TypescriptHeadersGenerator,
generateTypeScriptChannels,
generateTypescriptParameters,
generateTypescriptPayload,
generateTypeScriptClient,
generateTypescriptHeaders,
getDefaultConfiguration,
renderGenerator,
generateWithConfig,
Expand Down
13 changes: 13 additions & 0 deletions test/blackbox/configs/typescript/headers.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/** @type {import("@the-codegen-project/cli").TheCodegenConfiguration} TheCodegenConfiguration **/
export default {
inputType: 'asyncapi',
inputPath: 'asyncapi.json',
language: 'typescript',
generators: [
{
preset: 'headers',
outputPath: './',
serializationType: 'json',
}
]
};
Loading

0 comments on commit cc9f0a2

Please sign in to comment.