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
5 changes: 5 additions & 0 deletions .changeset/735-binding-excess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@asyncapi/parser': patch
---

Allow protocol-specific excess properties on the `Binding` type (issue #735).
1 change: 1 addition & 0 deletions packages/parser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"build:esm": "tsc",
"build:cjs": "tsc --project ./tsconfig.cjs.json",
"build:browser": "webpack",
"type-check:tests": "tsc --project ./tsconfig.tests.json",
"test": "npm run test:unit && npm run test:browser",
"test:unit": "cross-env CI=true jest --coverage --testPathIgnorePatterns=test/browser/*",
"test:browser": "npm run playwright:install && npm run build:browser && cross-env CI=true jest -- ./test/browser/*",
Expand Down
2 changes: 2 additions & 0 deletions packages/parser/src/spec-types/v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,8 @@ export interface CorrelationIDObject extends SpecificationExtensions {

export interface Binding {
bindingVersion?: string;
// protocol-specific binding fields (excess properties)
[propName: string]: any;
}

export interface SpecificationExtensions {
Expand Down
2 changes: 2 additions & 0 deletions packages/parser/src/spec-types/v3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,8 @@ export interface AsyncAPISchemaDefinition extends SpecificationExtensions {

export interface Binding {
bindingVersion?: string;
// protocol-specific binding fields (excess properties)
[propName: string]: any;
}

export interface SpecificationExtensions {
Expand Down
62 changes: 62 additions & 0 deletions packages/parser/test/models/v2/bindings-types.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { Binding } from '../../../src/spec-types/v2';
import type { Binding as BindingV3 } from '../../../src/spec-types/v3';

/**
* Type-level coverage for issue #735.
*
* The fix for #735 is a change to an *interface* (`Binding` gained an index
* signature). Jest does not type-check, so the runtime test in
* `bindings.spec.ts` passes both before and after that change -- it exercises
* `BindingV2`'s accessors, not the declaration. Only `tsc` can observe the fix.
*
* Verified by control experiment: removing the index signature from
* `spec-types/v2.ts` and type-checking THIS FILE fails with
*
* TS2322: Type '{ bindingVersion: string; groupId: ...; }' is not assignable
* to type 'Binding'. Object literal may only specify known
* properties, and 'groupId' does not exist in type 'Binding'.
* TS2339: Property 'groupId' does not exist on type 'Binding'.
*
* IMPORTANT, and the reason for the `type-check:tests` script this PR adds:
* `build:esm` is plain `tsc`, and `packages/parser/tsconfig.json` sets
* `"include": ["src"]`. It therefore never compiles `test/`, so on its own this
* file is inert -- `tsc --listFiles` does not list it, and the mutation above
* produces no build error. An earlier revision of this comment claimed "the
* build breaks here"; that was wrong, as was the error code it quoted (TS2353).
*
* `npm run type-check:tests` is what makes the guard real: it type-checks the
* test tree against the same sources, so removing the index signature fails
* there instead of regressing silently for every user writing a protocol
* binding.
*/
describe('Binding excess properties (issue #735) - type level', function () {
it('accepts protocol-specific fields on v2 Binding', function () {
// Kafka operation binding: groupId/clientId are not declared members.
const kafka: Binding = {
bindingVersion: '0.4.0',
groupId: { type: 'string', enum: ['myGroupId'] },
clientId: { type: 'string', enum: ['myClientId'] },
};
expect(kafka.bindingVersion).toEqual('0.4.0');
expect(kafka.groupId).toBeDefined();
});

it('accepts protocol-specific fields on v3 Binding', function () {
// AMQP channel binding shape.
const amqp: BindingV3 = {
bindingVersion: '0.3.0',
is: 'routingKey',
exchange: { name: 'myExchange', type: 'topic', durable: true },
};
expect(amqp.is).toEqual('routingKey');
});

it('still type-checks the declared member', function () {
// @ts-expect-error bindingVersion is declared as string | undefined, so a
// number must remain an error even though excess properties are now open.
// If the index signature were ever widened to swallow this too, the
// directive becomes unused and tsc fails -- which is the intent.
const bad: Binding = { bindingVersion: 42 };
expect(bad).toBeDefined();
});
});
9 changes: 9 additions & 0 deletions packages/parser/test/models/v2/bindings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,13 @@ describe('Bindings model', function () {
expect(bindings.extensions().get('x-anotherOne')?.value()).toEqual({ someKey: 123 });
});
});

describe('excess properties (issue #735)', function () {
it('should preserve protocol-specific excess fields on a binding value', function () {
const b = new Binding({ clientId: 'my-client', clean: true } as any, { asyncapi: {} as any, pointer: '', protocol: 'mqtt' });
const value = b.value() as Record<string, any>;
expect(value).toHaveProperty('clientId', 'my-client');
expect(value).toHaveProperty('clean', true);
});
});
});
11 changes: 11 additions & 0 deletions packages/parser/tsconfig.tests.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"types": ["jest", "node"]
},
"include": [
"src",
"test/models/v2/bindings-types.spec.ts"
]
}
Loading