Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
5 changes: 5 additions & 0 deletions .changeset/modern-coats-sink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@redocly/openapi-core": major
---

Added AsyncAPI support to the stats command.
52 changes: 52 additions & 0 deletions packages/core/src/rules/other/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,58 @@ export const Stats = (statsAccumulator: StatsAccumulator) => {
},
},
},
// AsyncAPI 2.x support
ChannelMap: {
Channel: {
leave() {
statsAccumulator.pathItems.total++;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PathItem in OAS is not quite the same as Channel in AsyncAPI, although they serve analogous purposes in their respective specifications.
Have you considered a different accumulator options? Like maybe have a separate collector and tune the output with a different label, like Channels?

},
Operation: {
leave(operation: any) {
statsAccumulator.operations.total++;
if (operation.tags) {
for (const tag of operation.tags) {
statsAccumulator.tags.items!.add(tag);
}
}
},
},
Parameter: {
leave(parameter: any) {
if (parameter.name) {
statsAccumulator.parameters.items!.add(parameter.name);
}
},
},
},
},
// AsyncAPI 3.x support
Copy link
Contributor

@DmitryAnansky DmitryAnansky Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered a different implementation approach using detectSpec with separate stats handlers for each specification? This would help to cleanly separate the concepts of different specifications.
In case specs related, e.g. async2/async3, some implementation can be reused/extended.

NamedChannels: {
Channel: {
leave() {
statsAccumulator.pathItems.total++;
},
Parameter: {
leave(parameter: any) {
if (parameter.name) {
statsAccumulator.parameters.items!.add(parameter.name);
}
},
},
},
},
NamedOperations: {
Operation: {
leave(operation: any) {
statsAccumulator.operations.total++;
if (operation.tags) {
for (const tag of operation.tags) {
statsAccumulator.tags.items!.add(tag);
}
}
},
},
},
NamedSchemas: {
Schema: {
leave() {
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/build-docs/simple-build-docs/pets.yaml
21 changes: 21 additions & 0 deletions tests/e2e/commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -965,5 +965,26 @@ describe('E2E', () => {
const result = getCommandOutput(args, { testPath });
await expect(cleanupOutput(result)).toMatchFileSnapshot(join(testPath, 'snapshot.txt'));
});

test('stats should support AsyncAPI 2.x (stylish format)', async () => {
const testPath = join(folderPath, 'stats-async2-stylish');
const args = getParams(indexEntryPoint, ['stats', 'async.yaml']);
const result = getCommandOutput(args, { testPath });
await expect(cleanupOutput(result)).toMatchFileSnapshot(join(testPath, 'snapshot.txt'));
});

test('stats should support AsyncAPI 2.x (JSON format)', async () => {
const testPath = join(folderPath, 'stats-async2-json');
const args = getParams(indexEntryPoint, ['stats', 'async.yaml', '--format=json']);
const result = getCommandOutput(args, { testPath });
await expect(cleanupOutput(result)).toMatchFileSnapshot(join(testPath, 'snapshot.txt'));
});

test('stats should support AsyncAPI 3.x (stylish format)', async () => {
const testPath = join(folderPath, 'stats-async3-stylish');
const args = getParams(indexEntryPoint, ['stats', 'asyncapi3.yaml']);
const result = getCommandOutput(args, { testPath });
await expect(cleanupOutput(result)).toMatchFileSnapshot(join(testPath, 'snapshot.txt'));
});
});
});
Loading