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
4 changes: 2 additions & 2 deletions package-lock.json

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

47 changes: 22 additions & 25 deletions packages/components/src/components/QueryParamsVariables.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,38 +113,35 @@ function resolveQueryParamLogic(language, framework = '') {
* @param {string} [props.framework=''] - Optional framework for the language.
* @param {string[][]} props.queryParams - Array of query parameters, each represented as [paramName, paramType?].
* @returns {JSX.Element[]|null} Array of Text components for each query parameter, or null if queryParams is invalid.
*
* @example
* import path from "path";
* import { Parser, fromFile } from "@asyncapi/parser";
* import { getQueryParams } from "@asyncapi/generator-helpers";
* import { QueryParamsVariables } from "@asyncapi/generator-components";
*

* async function renderQueryParamsVariable(){
* const parser = new Parser();
* const asyncapi_v3_path = path.resolve(__dirname, "../__fixtures__/asyncapi-v3.yml");
*
* // Parse the AsyncAPI document
* const parseResult = await fromFile(parser, asyncapi_v3_path).parse();
* const parsedAsyncAPIDocument = parseResult.document;
*
* const channels = parsedAsyncAPIDocument.channels();
* const queryParamsObject = getQueryParams(channels);
* const queryParamsArray = queryParamsObject ? Array.from(queryParamsObject.entries()) : [];
*
* const language = "java";
* const framework = "quarkus";
*
* return (
* <QueryParamsVariables
* language={language}
* framework={framework}
* queryParams={queryParamsArray}
* />
* )
* const parser = new Parser();
* const asyncapi_v3_path = path.resolve(__dirname, "../__fixtures__/asyncapi-v3.yml");
* // Parse the AsyncAPI document
* const parseResult = await fromFile(parser, asyncapi_v3_path).parse();
* const parsedAsyncAPIDocument = parseResult.document;
* const channels = parsedAsyncAPIDocument.channels();
* const queryParamsObject = getQueryParams(channels);
* let queryParamsArray = [];
* if (queryParamsObject) {
* // For this example, extract parameters from the first available channel
* const firstChannelName = Object.keys(queryParamsObject)[0];
* queryParamsArray = Object.entries(queryParamsObject[firstChannelName]);
* }
* const language = "java";
* const framework = "quarkus";
* * return (
* <QueryParamsVariables
* language={language}
* framework={framework}
* queryParams={queryParamsArray}
* />
* )
* }
*
* renderQueryParamsVariable().catch(console.error);
*/
export function QueryParamsVariables({ language, framework = '', queryParams }) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ describe('Testing of QueryParamsVariables component', () => {
test('renders python query params correctly with query parameters', () => {
const channels = parsedAsyncAPIDocument.channels();
const queryParamsObject = getQueryParams(channels);
const queryParamsArray = queryParamsObject ? Array.from(queryParamsObject.entries()) : [];

let queryParamsArray = [];
if (queryParamsObject) {
const firstChannelName = Object.keys(queryParamsObject)[0];
queryParamsArray = Object.entries(queryParamsObject[firstChannelName]);
}

const result = render(
<QueryParamsVariables
language='python'
Expand All @@ -31,7 +37,13 @@ describe('Testing of QueryParamsVariables component', () => {
test('renders js query params correctly with query parameters', () => {
const channels = parsedAsyncAPIDocument.channels();
const queryParamsObject = getQueryParams(channels);
const queryParamsArray = queryParamsObject ? Array.from(queryParamsObject.entries()) : [];

let queryParamsArray = [];
if (queryParamsObject) {
const firstChannelName = Object.keys(queryParamsObject)[0];
queryParamsArray = Object.entries(queryParamsObject[firstChannelName]);
}

const result = render(
<QueryParamsVariables
language='javascript'
Expand All @@ -44,7 +56,13 @@ describe('Testing of QueryParamsVariables component', () => {
test('renders java quarkus query params correctly with query parameters', () => {
const channels = parsedAsyncAPIDocument.channels();
const queryParamsObject = getQueryParams(channels);
const queryParamsArray = queryParamsObject ? Array.from(queryParamsObject.entries()) : [];

let queryParamsArray = [];
if (queryParamsObject) {
const firstChannelName = Object.keys(queryParamsObject)[0];
queryParamsArray = Object.entries(queryParamsObject[firstChannelName]);
}

const result = render(
<QueryParamsVariables
language='java'
Expand Down Expand Up @@ -73,4 +91,4 @@ describe('Testing of QueryParamsVariables component', () => {
/>);
expect(result.trim()).toMatchSnapshot();
});
});
});
99 changes: 65 additions & 34 deletions packages/helpers/src/bindings.js
Original file line number Diff line number Diff line change
@@ -1,52 +1,83 @@
/**
* Extracts default query parameters from a channel’s WebSocket binding.
* Extracts default query parameters from all channels' 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 object representing all AsyncAPI channels. It assumes this object provides `.isEmpty()` and `.all()` methods.
* @returns {Object|null} An object whose keys are channel names and whose values are objects of their defaults (or `''`), or null if none exist.
*
* @example
* // Suppose channel.bindings().get("ws").values() returns:
* // Suppose your AsyncAPI document has a 'chat' channel with a WS binding:
* // { query: { properties: { foo: { default: 'bar' }, baz: {} } } }
* const params = getQueryParams(channel);
* console.log(params.get('foo')); // → 'bar'
* console.log(params.get('baz')); // → ''
* const params = getQueryParams(channels);
* console.log(params.chat.foo); // → 'bar'
* console.log(params.chat.baz); // → ''
*/
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();

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

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

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;
const result = Object.create(null);

// Loop through every single channel
for (const [channelName, channel] of channels.all().entries()) {
const bindings = channel?.bindings?.();
const hasWsBinding = bindings?.has('ws');

// If this specific channel doesn't have a ws binding, skip it and move to the next
if (!hasWsBinding) {
continue;
}

const wsBinding = bindings.get('ws');
const query = wsBinding.value()?.query;

// If no query, skip to the next channel
if (!query) {
continue;
}

// the JSON Schema properties
const properties = query.properties;
if (!properties || typeof properties !== 'object') {
continue;
}

// Populate the parameters for this specific channel
const channelParams = Object.create(null);
for (const [key, schema] of Object.entries(properties)) {
const value = schema.default ?? '';
channelParams[key] = String(value);
}

// Add the populated object to our main result object under the channel's name
if (Object.keys(channelParams).length > 0) {
result[channelName] = channelParams;
}
}

// If we didn't find any ws query params across any channels, return null to preserve the old behavior
return Object.keys(result).length > 0 ? result : null;
}

/**
* Retrieves the query parameters for the first available WebSocket channel.
* This acts as a backward-compatibility helper for templates expecting a single Map of parameters.
*
* @param {Object} channels - The AsyncAPI channels collection object.
* @returns {Map|null} A Map of query parameters for the first channel, or null if none exist.
*/
function getFirstChannelQueryParams(channels) {
const allQueryParams = getQueryParams(channels);

// Drill into the JSON Schema properties
const properties = query.properties;
if (!properties || typeof properties !== 'object') {
return null;
if (allQueryParams && Object.keys(allQueryParams).length > 0) {
const firstChannelName = Object.keys(allQueryParams)[0];
return new Map(Object.entries(allQueryParams[firstChannelName]));
}

// 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 null;
}

module.exports = {
getQueryParams
getQueryParams,
getFirstChannelQueryParams
};
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,getFirstChannelQueryParams } = 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,
getFirstChannelQueryParams,
getOperationMessages,
getMessageExamples,
getTitle,
Expand Down
14 changes: 10 additions & 4 deletions packages/helpers/test/bindings.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,16 @@ describe('getQueryParams integration test with AsyncAPI', () => {
const params = getQueryParams(channels);

expect(params).not.toBeNull();
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('');

// We dynamically grab the first channel's name since our new structure is keyed by channel name
const firstChannelName = Object.keys(params)[0];
const channelParams = params[firstChannelName];

// Assert using standard object property access instead of Map's .get()
expect(channelParams.heartbeat).toBe('false');
expect(channelParams.top_of_book).toBe('false');
expect(channelParams.bids).toBe('true');
expect(channelParams.offers).toBe('');
});

it('should return null for channel without WebSocket binding', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getClientName, getInfo, getQueryParams, getServer, getTitle } from '@asyncapi/generator-helpers';
import { getClientName, getInfo, getFirstChannelQueryParams, getServer, getTitle } from '@asyncapi/generator-helpers';
import { File } from '@asyncapi/generator-react-sdk';
import { FileHeaderInfo } from '@asyncapi/generator-components';
import { ClientDependencies } from '../../../../../../components/dependencies/ClientDependencies.js';
Expand All @@ -8,7 +8,10 @@ export default async function ({ asyncapi, params }) {
const server = getServer(asyncapi.servers(), params.server);
const info = getInfo(asyncapi);
const title = getTitle(asyncapi);
const queryParams = getQueryParams(asyncapi.channels());

// Use the new helper to get the first channel's parameters directly as a Map
const queryParams = getFirstChannelQueryParams(asyncapi.channels());

const clientName = getClientName(asyncapi, params.appendClientSuffix, params.customClientName);
const operations = asyncapi.operations();
const clientJavaName = `${clientName}.java`;
Expand Down
Loading
Loading