Skip to content
Open
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
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

150 changes: 114 additions & 36 deletions packages/helpers/src/bindings.js
Original file line number Diff line number Diff line change
@@ -1,52 +1,130 @@
/**
* Extracts default query parameters from a channel’s WebSocket binding.
* Extracts query parameters from all channels with WebSocket bindings.
*
* @param {Map<string,string>} channels - A Map representing all AsyncAPI channels.
* @returns {Map<string,string>} A Map whose keys are parameter names and whose values are their defaults (or `''`).
* @param {Object} channels - An AsyncAPI channels collection object with isEmpty() and all() methods.
* @returns {Map<string, Map<string,string>>} A Map where keys are channel names and values are Maps of parameter names to their defaults (or `''`).
*
* @example
* // Suppose channel.bindings().get("ws").values() returns:
* // { query: { properties: { foo: { default: 'bar' }, baz: {} } } }
* const params = getQueryParams(channel);
* console.log(params.get('foo')); // → 'bar'
* console.log(params.get('baz')); // → ''
* // Suppose you have multiple channels with WS bindings:
* // channels:
* // chat:
* // bindings:
* // ws:
* // query:
* // properties:
* // token: { default: 'auth123' }
* // roomId: { }
* // notifications:
* // bindings:
* // ws:
* // query:
* // properties:
* // userId: { default: 'user456' }
* // token: { }
*
* const allParams = getQueryParamsForAllChannels(channels);
*
* // Returns a Map like:
* // {
* // 'chat' => { 'token' => 'auth123', 'roomId' => '' },
* // 'notifications' => { 'userId' => 'user456', 'token' => '' }
* // }
*
* const chatParams = allParams.get('chat');
* console.log(chatParams.get('token')); // → 'auth123'
* console.log(chatParams.get('roomId')); // → ''
*
* const notifParams = allParams.get('notifications');
* console.log(notifParams.get('userId')); // → 'user456'
* console.log(notifParams.get('token')); // → ''
*/
function getQueryParams(channels) {
// current implementation assumes there is always one channel
// at the moment only WebSocket binding support query params and the use case for WebSocket is that there is always one channel per AsyncAPI document
const channel = !channels.isEmpty() && channels.all().entries().next().value[1];

const queryMap = new Map();
function getQueryParamsForAllChannels(channels) {
const allChannelsParams = new Map();

const bindings = channel?.bindings?.();
const hasWsBinding = bindings?.has('ws');

if (!hasWsBinding) {
return null;
if (channels.isEmpty()) {
return allChannelsParams;
}

const wsBinding = bindings.get('ws');
const query = wsBinding.value()?.query;
//we do not throw error, as user do not have to use query params, we just exit with null as it doesn't make sense to continue with query building
if (!query) {
return null;
for (const channel of channels.all()) {
const channelName = channel.id();
const bindings = channel?.bindings?.();
if (!bindings?.has('ws')) {
continue;
}

const wsBinding = bindings.get('ws');
const query = wsBinding.value()?.query;
if (!query) {
continue;
}

const properties = query.properties;
if (!properties || typeof properties !== 'object' || Object.keys(properties).length === 0) {
continue;
}

const channelParams = new Map();
for (const [key, schema] of Object.entries(properties)) {
const value = schema.default ?? '';
channelParams.set(key, String(value));
}

allChannelsParams.set(channelName, channelParams);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

return allChannelsParams;
}

/**
* Extracts default query parameters from the first channel's WebSocket binding.
* (Maintained for backward compatibility)
*
* @param {Map<string,string>} channels - A Map representing all AsyncAPI channels.
* @returns {Map<string,string>|null} A Map of parameter names to defaults (or `''`), or null if no WS binding found.
*
* @example
* // Suppose you have multiple channels with WS bindings:
* // channels:
* // chat:
* // bindings:
* // ws:
* // query:
* // properties:
* // token: { default: 'auth123' }
* // roomId: { }
* // notifications:
* // bindings:
* // ws:
* // query:
* // properties:
* // userId: { default: 'user456' }
* // token: { }
*
* const params = getQueryParams(channels);
*
* // Returns only the first channel's params (e.g., 'chat'):
* // { 'token' => 'auth123', 'roomId' => '' }
*
* console.log(params.get('token')); // → 'auth123'
* console.log(params.get('roomId')); // → ''
*
* // Note: To access query parameters for all channels, use getQueryParamsForAllChannels()
* const allParams = getQueryParamsForAllChannels(channels);
* const notifParams = allParams.get('notifications');
* console.log(notifParams.get('userId')); // → 'user456'
*/
function getQueryParams(channels) {
const allChannelsParams = getQueryParamsForAllChannels(channels);

// Drill into the JSON Schema properties
const properties = query.properties;
if (!properties || typeof properties !== 'object') {
if (allChannelsParams.size === 0) {
return null;
}

// Populate the map, preserving defaults
for (const [key, schema] of Object.entries(properties)) {
const value = schema.default ?? '';
queryMap.set(key, String(value));
}

return queryMap;
// Return the first channel's params for backward compatibility
return allChannelsParams.values().next().value;
}

module.exports = {
getQueryParams
};
getQueryParams,
getQueryParamsForAllChannels
};
3 changes: 2 additions & 1 deletion packages/helpers/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const { getMessageExamples, getOperationMessages } = require('./operations');
const { getServerUrl, getServer, getServerHost, getServerProtocol } = require('./servers');
const { getClientName, getInfo, toSnakeCase, toCamelCase, getTitle, lowerFirst, upperFirst } = require('./utils');
const { getMessageDiscriminatorData, getMessageDiscriminatorsFromOperations } = require('./discriminators');
const { getQueryParams } = require('./bindings');
const { getQueryParams, getQueryParamsForAllChannels } = require('./bindings');
const { cleanTestResultPaths, verifyDirectoryStructure, getDirElementsRecursive, buildParams, listFiles, hasNestedConfig} = require('./testing');
const { JavaModelsPresets } = require('./ModelsPresets');

Expand All @@ -14,6 +14,7 @@ module.exports = {
getServerProtocol,
listFiles,
getQueryParams,
getQueryParamsForAllChannels,
Comment thread
Adi-204 marked this conversation as resolved.
getOperationMessages,
getMessageExamples,
getTitle,
Expand Down
88 changes: 87 additions & 1 deletion packages/helpers/test/bindings.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const path = require('path');
const { Parser, fromFile } = require('@asyncapi/parser');
const { getQueryParams } = require('@asyncapi/generator-helpers');
const { getQueryParams, getQueryParamsForAllChannels } = require('@asyncapi/generator-helpers');
const parser = new Parser();
const asyncapi_v3_path = path.resolve(__dirname, './__fixtures__/asyncapi-websocket-query.yml');

Expand Down Expand Up @@ -77,4 +77,90 @@ describe('getQueryParams integration test with AsyncAPI', () => {
const params = getQueryParamsForChannels(['wsBindingEmptyQuery']);
expect(params).toBeNull();
});
});

describe('getQueryParamsForAllChannels integration test with AsyncAPI', () => {
let parsedAsyncAPIDocument;

beforeAll(async () => {
const parseResult = await fromFile(parser, asyncapi_v3_path).parse();
parsedAsyncAPIDocument = parseResult.document;
});

/**
* Filters document channels by name and returns extracted WS query params for all matching channels.
*
* @param {string[]} channelNames - Channel IDs to include.
* @returns {Map<string, Map<string, string>>} Map of channel name -> query param defaults.
* Returns an empty Map when no matching channels with WS query properties are found.
*/
const getFilteredAllChannelsParams = (channelNames) => {
const channels = parsedAsyncAPIDocument.channels();
const filteredMap = new Map();
for (const channel of channels.all()) {
if (channelNames.includes(channel.id())) {
filteredMap.set(channel.id(), channel);
}
}
return getQueryParamsForAllChannels({
isEmpty: () => filteredMap.size === 0,
all: () => [...filteredMap.values()]
});
};

it('should extract query parameters from marketDataV1 channel', () => {
const allParams = getFilteredAllChannelsParams(['marketDataV1']);

expect(allParams.size).toBe(1);
expect(allParams.has('marketDataV1')).toBe(true);

const params = allParams.get('marketDataV1');
expect(params).toBeInstanceOf(Map);
expect(params.get('heartbeat')).toBe('false');
expect(params.get('top_of_book')).toBe('false');
expect(params.get('bids')).toBe('true');
expect(params.get('offers')).toBe('');
});

it('should return empty map for channel without WebSocket binding', () => {
const allParams = getFilteredAllChannelsParams(['marketDataV1NoBinding']);

expect(allParams.size).toBe(0);
});

it('should return empty map for empty channels', () => {
const emptyChannels = {
isEmpty: () => true,
all: () => []
};
const allParams = getQueryParamsForAllChannels(emptyChannels);

expect(allParams.size).toBe(0);
});

it('should return empty map for channel with empty binding', () => {
const allParams = getFilteredAllChannelsParams(['emptyChannel']);

expect(allParams.size).toBe(0);
});

it('should return empty map if WebSocket binding exists but has no query parameters', () => {
const allParams = getFilteredAllChannelsParams(['wsBindingNoQuery']);

expect(allParams.size).toBe(0);
});

it('should return empty map if WebSocket binding query exists but has no properties', () => {
const allParams = getFilteredAllChannelsParams(['wsBindingEmptyQuery']);

expect(allParams.size).toBe(0);
});

it('should skip channels without WS bindings and only include those with WS bindings', () => {
const allParams = getFilteredAllChannelsParams(['marketDataV1', 'marketDataV1NoBinding']);

expect(allParams.size).toBe(1);
expect(allParams.has('marketDataV1')).toBe(true);
expect(allParams.has('marketDataV1NoBinding')).toBe(false);
});
});
Loading