Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@ export function renderPublishExchange({
messageMarshalling = `${messageModule}.marshal(message)`;
}
messageType = messageModule ? `${messageModule}.${messageType}` : messageType;
const publishOperation = `let dataToSend: any = ${messageType === 'null' ? 'null' : messageMarshalling};
const channel = await amqp.createChannel();
const routingKey = ${addressToUse};
// Set up message properties (headers) if provided

const headersHandling = channelHeaders
? `// Set up message properties (headers) if provided
let publishOptions = { ...options };
if (headers) {
const headerData = headers.marshal();
Expand All @@ -38,7 +37,13 @@ if (headers) {
publishOptions.headers[key] = value;
}
}
}
}`
: `let publishOptions = { ...options };`;

const publishOperation = `let dataToSend: any = ${messageType === 'null' ? 'null' : messageMarshalling};
const channel = await amqp.createChannel();
const routingKey = ${addressToUse};
${headersHandling}
channel.publish(exchange, routingKey, Buffer.from(dataToSend), publishOptions);`;

const functionParameters = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@ export function renderPublishQueue({
messageMarshalling = `${messageModule}.marshal(message)`;
}
messageType = messageModule ? `${messageModule}.${messageType}` : messageType;
const publishOperation = `let dataToSend: any = ${messageType === 'null' ? 'null' : messageMarshalling};
const channel = await amqp.createChannel();
const queue = ${addressToUse};
// Set up message properties (headers) if provided

const headersHandling = channelHeaders
? `// Set up message properties (headers) if provided
let publishOptions = { ...options };
if (headers) {
const headerData = headers.marshal();
Expand All @@ -35,7 +34,13 @@ if (headers) {
publishOptions.headers[key] = value;
}
}
}
}`
: `let publishOptions = { ...options };`;

const publishOperation = `let dataToSend: any = ${messageType === 'null' ? 'null' : messageMarshalling};
const channel = await amqp.createChannel();
const queue = ${addressToUse};
${headersHandling}
channel.sendToQueue(queue, Buffer.from(dataToSend), publishOptions);`;

const functionParameters = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,23 @@ export function renderPublish({
}
];

const headersHandling = channelHeaders
? `// Set up headers if provided
let messageHeaders: Record<string, string> | undefined = undefined;
if (headers) {
const headerData = headers.marshal();
const parsedHeaders = typeof headerData === 'string' ? JSON.parse(headerData) : headerData;
messageHeaders = {};
for (const [key, value] of Object.entries(parsedHeaders)) {
if (value !== undefined) {
messageHeaders[key] = String(value);
}
}
}`
: '';

const headersInMessage = channelHeaders ? 'headers: messageHeaders' : '';

const code = `/**
* Kafka publish operation for \`${topic}\`
*
Expand All @@ -73,25 +90,13 @@ function ${functionName}({
${publishOperation}
const producer = kafka.producer();
await producer.connect();
// Set up headers if provided
let messageHeaders: Record<string, string> | undefined = undefined;
if (headers) {
const headerData = headers.marshal();
const parsedHeaders = typeof headerData === 'string' ? JSON.parse(headerData) : headerData;
messageHeaders = {};
for (const [key, value] of Object.entries(parsedHeaders)) {
if (value !== undefined) {
messageHeaders[key] = String(value);
}
}
}
${headersHandling}

await producer.send({
topic: ${addressToUse},
messages: [
{
value: dataToSend,
headers: messageHeaders
value: dataToSend${channelHeaders ? `,\n ${headersInMessage}` : ''}
},
],
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,35 +21,29 @@ export function renderPublish({
messageMarshalling = `${messageModule}.marshal(message)`;
}
messageType = messageModule ? `${messageModule}.${messageType}` : messageType;
const publishOperation =
messageType === 'null'
? `// Set up user properties (headers) if provided
let publishOptions: Mqtt.IClientPublishOptions = {};
if (headers) {
const headerData = headers.marshal();
const parsedHeaders = typeof headerData === 'string' ? JSON.parse(headerData) : headerData;
publishOptions.properties = { userProperties: {} };
for (const [key, value] of Object.entries(parsedHeaders)) {
if (value !== undefined) {
publishOptions.properties.userProperties[key] = String(value);
}
}
}
mqtt.publish(${addressToUse}, '', publishOptions);`
: `let dataToSend: any = ${messageMarshalling};
// Set up user properties (headers) if provided

const headersHandling = channelHeaders
? `// Set up user properties (headers) if provided
let publishOptions: Mqtt.IClientPublishOptions = {};
if (headers) {
const headerData = headers.marshal();
const parsedHeaders = typeof headerData === 'string' ? JSON.parse(headerData) : headerData;
const userProperties = {};
const userProperties: Record<string, string> = {};
for (const [key, value] of Object.entries(parsedHeaders)) {
if (value !== undefined) {
userProperties[key] = String(value);
}
}
publishOptions.properties = { userProperties };
}
}`
: `let publishOptions: Mqtt.IClientPublishOptions = {};`;

const publishOperation =
messageType === 'null'
? `${headersHandling}
mqtt.publish(${addressToUse}, '', publishOptions);`
: `let dataToSend: any = ${messageMarshalling};
${headersHandling}
mqtt.publish(${addressToUse}, dataToSend, publishOptions);`;

const functionParameters = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ function ${functionName}({

// Validate message if validation is enabled
if (!skipMessageValidation) {
const messageToValidate = parsedMessage.marshal();
const messageToValidate = ${messageModule ? `${messageModule}.marshal(parsedMessage)` : `parsedMessage.marshal()`};
const {valid, errors} = ${messageModule ? `${messageModule}.validate({data: messageToValidate, ajvValidatorFunction: validator})` : `${messageType}.validate({data: messageToValidate, ajvValidatorFunction: validator})`};
if (!valid) {
onDataCallback({
Expand Down
9 changes: 8 additions & 1 deletion src/codegen/generators/typescript/payloads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import {OpenAPIV2, OpenAPIV3, OpenAPIV3_1} from 'openapi-types';
import {TS_COMMON_PRESET} from '@asyncapi/modelina';
import {
createValidationPreset,
createUnionPreset
createUnionPreset,
createPrimitivesPreset
} from '../../modelina/presets';

export const zodTypeScriptPayloadGenerator = z.object({
Expand Down Expand Up @@ -144,6 +145,12 @@ export async function generateTypescriptPayloadsCoreFromSchemas({
includeValidation: generator.includeValidation
},
context
),
createPrimitivesPreset(
{
includeValidation: generator.includeValidation
},
context
)
],
enumType: generator.enum,
Expand Down
6 changes: 6 additions & 0 deletions src/codegen/modelina/presets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,9 @@ export {

// Core union marshalling/unmarshalling presets
export {createUnionPreset, type UnionPresetOptions} from './union';

// Primitive and array type marshalling/unmarshalling presets
export {
createPrimitivesPreset,
type PrimitivesPresetOptions
} from './primitives';
170 changes: 170 additions & 0 deletions src/codegen/modelina/presets/primitives.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import {
ConstrainedMetaModel,
ConstrainedStringModel,
ConstrainedIntegerModel,
ConstrainedFloatModel,
ConstrainedBooleanModel,
ConstrainedArrayModel
} from '@asyncapi/modelina';
import {TypeScriptRenderer} from '@asyncapi/modelina/lib/types/generators/typescript/TypeScriptRenderer';
import {
BaseGeneratorContext,
generateTypescriptValidationCode
} from './validation';

/**
* Configuration options for the primitives preset
*/
export interface PrimitivesPresetOptions {
/** Whether to include validation methods in generated primitive types */
includeValidation: boolean;
}

/**
* Check if the model is a primitive type (string, integer, float, boolean)
*/
function isPrimitiveModel(model: ConstrainedMetaModel): boolean {
return (
model instanceof ConstrainedStringModel ||
model instanceof ConstrainedIntegerModel ||
model instanceof ConstrainedFloatModel ||
model instanceof ConstrainedBooleanModel
);
}

/**
* Render marshal function for primitive types
*/
function renderPrimitiveMarshal(model: ConstrainedMetaModel): string {
return `export function marshal(payload: ${model.name}): string {
return JSON.stringify(payload);
}`;
}

/**
* Render unmarshal function for primitive types
*/
function renderPrimitiveUnmarshal(model: ConstrainedMetaModel): string {
// For string types, the input can be a JSON string (quoted) or the raw value
// We use 'any' for the json parameter since JSON.parse returns 'any'
return `export function unmarshal(json: string): ${model.name} {
return JSON.parse(json) as ${model.name};
}`;
}

/**
* Render marshal function for array types
*/
function renderArrayMarshal(model: ConstrainedArrayModel): string {
const valueModel = model.valueModel;

// Check if array items have a marshal method (object types)
const hasItemMarshal =
valueModel.type !== 'string' &&
valueModel.type !== 'number' &&
valueModel.type !== 'boolean';

if (hasItemMarshal) {
return `export function marshal(payload: ${model.name}): string {
return JSON.stringify(payload.map((item) => {
if (item && typeof item === 'object' && 'marshal' in item && typeof item.marshal === 'function') {
return JSON.parse(item.marshal());
}
return item;
}));
}`;
}

return `export function marshal(payload: ${model.name}): string {
return JSON.stringify(payload);
}`;
}

/**
* Render unmarshal function for array types
*/
function renderArrayUnmarshal(model: ConstrainedArrayModel): string {
const valueModel = model.valueModel;

// Check if array items have an unmarshal method (object types)
const hasItemUnmarshal =
valueModel.type !== 'string' &&
valueModel.type !== 'number' &&
valueModel.type !== 'boolean';

if (hasItemUnmarshal) {
const itemTypeName = valueModel.name;
return `export function unmarshal(json: string | any[]): ${model.name} {
const arr = typeof json === 'string' ? JSON.parse(json) : json;
return arr.map((item: any) => {
if (item && typeof item === 'object') {
return ${itemTypeName}.unmarshal(item);
}
return item;
}) as ${model.name};
}`;
}

return `export function unmarshal(json: string | any[]): ${model.name} {
if (typeof json === 'string') {
return JSON.parse(json) as ${model.name};
}
return json as ${model.name};
}`;
}

/**
* Creates a preset that adds marshalling/unmarshalling and validation methods
* to primitive types (string, number, boolean) and array types
*
* @param options Configuration for primitive generation
* @param context Generator context containing input type information
* @returns Modelina preset object with primitive marshalling functionality
*
* @example
* ```typescript
* const preset = createPrimitivesPreset({
* includeValidation: true
* }, context);
* ```
*/
export function createPrimitivesPreset(
options: PrimitivesPresetOptions,
context: BaseGeneratorContext
) {
return {
type: {
self({
model,
content,
renderer
}: {
model: ConstrainedMetaModel;
content: string;
renderer: TypeScriptRenderer;
}) {
// Handle primitive types (string, integer, float, boolean)
if (isPrimitiveModel(model)) {
return `${content}

${renderPrimitiveUnmarshal(model)}
${renderPrimitiveMarshal(model)}
${options.includeValidation ? generateTypescriptValidationCode({model, renderer, asClassMethods: false, context: context as any}) : ''}
`;
}

// Handle array types
if (model instanceof ConstrainedArrayModel) {
return `${content}

${renderArrayUnmarshal(model)}
${renderArrayMarshal(model)}
${options.includeValidation ? generateTypescriptValidationCode({model, renderer, asClassMethods: false, context: context as any}) : ''}
`;
}

return content;
}
}
};
}
Loading