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
1 change: 1 addition & 0 deletions docs/configurations.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ If no explicit configuration file is sat, it will be looked for in the following
- codegen.ts
- codegen.mjs
- codegen.cjs

15 changes: 1 addition & 14 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@ sidebar_label: Contributing
- [Getting started](#getting-started)
- [Contribution recogniton](#contribution-recogniton)
- [Summary of the contribution flow](#summary-of-the-contribution-flow)
- [Code of Conduct](#code-of-conduct)
- [Our Development Process](#our-development-process)
- [Pull Requests](#pull-requests)
- [Conventional commits](#conventional-commits)
- [License](#license)

<!-- tocstop -->

Expand Down Expand Up @@ -110,7 +108,7 @@ The following is a summary of the ideal contribution flow. Please, note that Pul
```

## Code of Conduct
AsyncAPI has adopted a Code of Conduct that we expect project participants to adhere to. Please [read the full text](../CODE_OF_CONDUCT.md) so that you can understand what sort of behaviour is expected.
We have adopted a Code of Conduct that we expect project participants to adhere to. Please [read the full text](../CODE_OF_CONDUCT.md) so that you can understand what sort of behaviour is expected.

## Our Development Process
We use Github to host code, to track issues and feature requests, as well as accept pull requests.
Expand All @@ -135,14 +133,3 @@ Pull requests should have a title that follows the specification, otherwise, mer
What about MAJOR release? just add `!` to the prefix, like `fix!: ` or `refactor!: `

Prefix that follows specification is not enough though. Remember that the title must be clear and descriptive with usage of [imperative mood](https://chris.beams.io/posts/git-commit/#imperative).

## License
When you submit changes, your submissions are understood to be under the same [Apache 2.0 License](../LICENSE) that covers the project.








9 changes: 9 additions & 0 deletions docs/generators/channels.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ It supports the following languages; [`typescript`](#typescript)

It supports the following protocols; [`nats`](../protocols/nats.md)

## Options
These are the available options for the `channels` generator;

| **Option** | Default | Type | Description |
|---|---|---|
| asyncapiReverseOperations | `false` | Boolean | Used in conjunction with AsyncAPI input, and reverses the operation actions i.e. send becomes receive and receive becomes send. Often used in testing scenarios to act as the reverse API. |
| asyncapiGenerateForOperations | `true` | Boolean | Used in conjunction with AsyncAPI input, which if `true` generate the functions upholding how operations are defined. If `false` the functions are generated regardless of what operations define. I.e. `send` and `receive` does not matter. |
| functionTypeMapping | {} | Record\<string, [ChannelFunctionTypes](https://the-codegen-project.org/docs/api/enumerations/ChannelFunctionTypes.md)[]\> | Used in conjunction with AsyncAPI input, can define channel ID along side the type of functions that should be rendered. |

## TypeScript

Depending on which protocol, these are the dependencies:
Expand Down
157 changes: 103 additions & 54 deletions src/codegen/generators/helpers/payloads.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
/* eslint-disable security/detect-object-injection */
import {
AsyncAPIInputProcessor,
ConstrainedObjectModel,
OutputModel
} from '@asyncapi/modelina';
import {AsyncAPIDocumentInterface} from '@asyncapi/parser';
import {AsyncAPIDocumentInterface, ChannelInterface, MessageInterface, OperationInterface, OperationReplyInterface} from '@asyncapi/parser';
import {ChannelPayload, PayloadRenderType} from '../../types';
import {pascalCase} from '../typescript/utils';
import {findNameFromChannel} from '../../utils';
type ChannelReturnType = Record<
import {findExtensionObject, findNameFromChannel} from '../../utils';
type PayloadGenerationType = Record<
string,
{messageModel: OutputModel; messageType: string}
>;
Expand All @@ -17,68 +18,100 @@ export async function generateAsyncAPIPayloads<GeneratorType>(
generator: (input: any) => Promise<OutputModel[]>,
generatorConfig: GeneratorType
): Promise<PayloadRenderType<GeneratorType>> {
const channelReturnType: ChannelReturnType = {};
const generatedChannelPayloads: PayloadGenerationType = {};
const generatedOperationPayloads: PayloadGenerationType = {};
let otherModels: ChannelPayload[] = [];
if (asyncapiDocument.allChannels().all().length > 0) {
for (const channel of asyncapiDocument.allChannels().all()) {
let schemaObj: any = {
type: 'object',
$schema: 'http://json-schema.org/draft-07/schema'
};
const replyMessages = channel.messages().all();
const messages = channel.messages().all();
if (messages.length > 1) {
schemaObj.oneOf = [];
schemaObj['$id'] = pascalCase(
`${findNameFromChannel(channel)}_Payload`
);
for (const message of messages) {
const schema = AsyncAPIInputProcessor.convertToInternalSchema(
message.payload() as any
const processMessages = async (messagesToProcess: MessageInterface[], preId: string): Promise<{generatedMessages: any[], messageType: string} | undefined> => {
let schemaObj: any = {
type: 'object',
$schema: 'http://json-schema.org/draft-07/schema'
};
const messages = messagesToProcess;
if (messages.length > 1) {
schemaObj.oneOf = [];
schemaObj['$id'] = pascalCase(
`${preId}_Payload`
);
const payloadId = message.id() ?? message.name();
if (typeof schema === 'boolean') {
schemaObj.oneOf.push(schema);
for (const message of messages) {
const schema = AsyncAPIInputProcessor.convertToInternalSchema(
message.payload() as any
);
const payloadId = message.id() ?? message.name();
if (typeof schema === 'boolean') {
schemaObj.oneOf.push(schema);
} else {
schemaObj.oneOf.push({
...schema,
$id: payloadId
});
}
}
} else if (messages.length === 1) {
const message = messages[0];
if (message.hasPayload()) {
const schema = AsyncAPIInputProcessor.convertToInternalSchema(
message.payload() as any
);
let payloadId = message.id() ?? message.name();
if (payloadId.includes('AnonymousSchema_')) {
payloadId = pascalCase(`${preId}_Payload`);
}
if (typeof schema === 'boolean') {
schemaObj = schema;
} else {
schemaObj = {
...schemaObj,
...(schema as any),
$id: payloadId
};
}
} else {
schemaObj.oneOf.push({
...schema,
$id: payloadId
});
return;
}
} else {
return;
}
} else if (messages.length === 1) {
const messagePayload = messages[0].payload();
const schema = AsyncAPIInputProcessor.convertToInternalSchema(
messagePayload as any
);
const message = messages[0];
let payloadId = message.id() ?? message.name();
if (payloadId.includes('AnonymousSchema_')) {
payloadId = pascalCase(`${findNameFromChannel(channel)}_Payload`);
const models = await generator(schemaObj);
const messageModel = models[0].model;
let messageType = messageModel.type;
// Workaround from Modelina as rendering root payload model can create simple types and `.type` is no longer valid for what we use it for
if (!(messageModel instanceof ConstrainedObjectModel)) {
messageType = messageModel.name;
}
if (typeof schema === 'boolean') {
schemaObj = schema;
} else {
schemaObj = {
...schemaObj,
...(schema as any),
$id: payloadId
return {generatedMessages: models, messageType};
};
for (const operation of channel.operations().all()) {
//const operationMessages = operation.messages().all().filter((message) => message.id() !== undefined);
const operationMessages = operation.messages().all();
const operationReply = operation.reply();
if (operationReply) {
const operationReplyId = findReplyId(operation, operationReply, channel);
const operationReplyGeneratedMessages = await processMessages(operationReply.messages().all(), operationReplyId);
if (operationReplyGeneratedMessages) {
generatedOperationPayloads[operationReplyId] = {
messageModel: operationReplyGeneratedMessages.generatedMessages[0],
messageType: operationReplyGeneratedMessages.messageType
};
}
}
const operationId = findOperationId(operation, channel);
const operationGeneratedMessages = await processMessages(operationMessages, operationId);
if (operationGeneratedMessages) {
generatedOperationPayloads[operationId] = {
messageModel: operationGeneratedMessages.generatedMessages[0],
messageType: operationGeneratedMessages.messageType
};
}
} else {
continue;
}
const models = await generator(schemaObj);
const messageModel = models[0].model;
let messageType = messageModel.type;
// Workaround from Modelina as rendering root payload model can create simple types and `.type` is no longer valid for what we use it for
if (!(messageModel instanceof ConstrainedObjectModel)) {
messageType = messageModel.name;
const channelGeneratedMessages = await processMessages(channel.messages().all(), findNameFromChannel(channel));
if (channelGeneratedMessages) {
generatedChannelPayloads[channel.id()] = {
messageModel: channelGeneratedMessages.generatedMessages[0],
messageType: channelGeneratedMessages.messageType
};
}
channelReturnType[channel.id()] = {
messageModel: models[0],
messageType
};
}
} else {
const generatedModels = await generator(asyncapiDocument);
Expand All @@ -91,8 +124,24 @@ export async function generateAsyncAPIPayloads<GeneratorType>(
});
}
return {
channelModels: channelReturnType,
channelModels: generatedChannelPayloads,
operationModels: generatedOperationPayloads,
otherModels,
generator: generatorConfig
};
}

export function findReplyId(operation: OperationInterface, reply: OperationReplyInterface, channel: ChannelInterface) {
return (reply.json() as any)?.id ?? `${findOperationId(operation, reply.channel() ?? channel)}_reply`;
}
export function findOperationId(operation: OperationInterface, channel: ChannelInterface) {
let operationId = operation.id();
operationId = operation.hasOperationId() ? operation.operationId() : operationId;
const userSpecificName = findExtensionObject(operation)
? findExtensionObject(operation)['channelName']
: undefined;
if (userSpecificName) {
return userSpecificName;
}
return operationId ?? channel.id();
}
5 changes: 3 additions & 2 deletions src/codegen/generators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ export {
TypeScriptChannelsGeneratorInternal,
TypeScriptClientGeneratorInternal,
TypescriptParametersGeneratorInternal,
TypeScriptparameterRenderType,
TypeScriptParameterRenderType,
TypescriptHeadersContext,
TypescriptHeadersGenerator,
defaultTypeScriptHeadersOptions,
generateTypescriptHeaders
generateTypescriptHeaders,
ChannelFunctionTypes
} from './typescript';
export {defaultCustomGenerator, CustomGenerator} from './generic/custom';
import {RunGeneratorContext} from '../types';
Expand Down
48 changes: 48 additions & 0 deletions src/codegen/generators/typescript/channels/asyncapi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { ChannelFunctionTypes } from "./types";

type Action = 'send' | 'receive' | 'subscribe' | 'publish'
const sendingFunctionTypes = [
ChannelFunctionTypes.NATS_JETSTREAM_PUBLISH,
ChannelFunctionTypes.NATS_PUBLISH,
ChannelFunctionTypes.NATS_REQUEST
];
const receivingFunctionTypes = [
ChannelFunctionTypes.NATS_JETSTREAM_PULL_SUBSCRIBE,
ChannelFunctionTypes.NATS_JETSTREAM_PUSH_SUBSCRIBE,
ChannelFunctionTypes.NATS_REPLY,
ChannelFunctionTypes.NATS_SUBSCRIBE
];

// eslint-disable-next-line sonarjs/cognitive-complexity
export function shouldRenderFunctionType(
givenFunctionTypes: ChannelFunctionTypes[] | undefined,
functionTypesToCheckFor: ChannelFunctionTypes | ChannelFunctionTypes[],
action: Action,
reverseOperation: boolean
) {
const listToCheck = [...(Array.isArray(functionTypesToCheckFor) ? functionTypesToCheckFor : [functionTypesToCheckFor])];
const hasSendingOperation = action === 'send' || action === 'subscribe';
const hasReceivingOperation = action === 'receive' || action === 'publish';
const hasFunctionMappingConfig = givenFunctionTypes !== undefined;
const checkForSending = listToCheck.some(item => sendingFunctionTypes.includes(item));
const checkForReceiving = listToCheck.some(item => receivingFunctionTypes.includes(item));
const hasFunctionType = (givenFunctionTypes ?? []).some(item => listToCheck.includes(item));
if (hasFunctionMappingConfig) {
if (hasFunctionType) {
const renderForSending = checkForSending && hasSendingOperation;
const renderForReceiving = checkForReceiving && hasReceivingOperation;
return renderForSending || renderForReceiving;
}
return false;
}

if (reverseOperation) {
const renderForSending = hasSendingOperation && checkForReceiving;
const renderForReceiving = hasReceivingOperation && checkForSending;
return renderForSending || renderForReceiving;
}

const renderForSending = checkForSending && hasSendingOperation;
const renderForReceiving = checkForReceiving && hasReceivingOperation;
return renderForSending || renderForReceiving;
}
Loading
Loading