Skip to content

Commit b569a35

Browse files
authored
fix: doc.allSchemas() skips now duplicates (#897)
1 parent 0a765c1 commit b569a35

File tree

5 files changed

+51
-44
lines changed

5 files changed

+51
-44
lines changed

src/models/utils.ts

+25
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import type { BaseModel, ModelMetadata } from './base';
22
import type { DetailedAsyncAPI } from '../types';
3+
import { SchemaInterface } from './schema';
4+
import { SchemaTypesToIterate, traverseAsyncApiDocument } from '../iterator';
5+
import { AsyncAPIDocumentInterface } from './asyncapi';
6+
import { SchemasInterface } from 'models';
37

48
export interface Constructor<T> extends Function {
59
new (...any: any[]): T;
@@ -12,3 +16,24 @@ export type InferModelMetadata<T> = T extends BaseModel<infer _, infer M> ? M :
1216
export function createModel<T extends BaseModel>(Model: Constructor<T>, value: InferModelData<T>, meta: Omit<ModelMetadata, 'asyncapi'> & { asyncapi?: DetailedAsyncAPI } & InferModelMetadata<T>, parent?: BaseModel): T {
1317
return new Model(value, { ...meta, asyncapi: meta.asyncapi || parent?.meta().asyncapi });
1418
}
19+
20+
export function schemasFromDocument<T extends SchemasInterface>(document: AsyncAPIDocumentInterface, SchemasModel: Constructor<T>, includeComponents: boolean): T {
21+
const jsonInstances: Set<any> = new Set();
22+
const schemas: Set<SchemaInterface> = new Set();
23+
24+
function callback(schema: SchemaInterface) {
25+
// comparing the reference (and not just the value) to the .json() object
26+
if (!jsonInstances.has(schema.json())) {
27+
jsonInstances.add(schema.json());
28+
schemas.add(schema); // unique schemas
29+
}
30+
}
31+
32+
let toIterate = Object.values(SchemaTypesToIterate);
33+
if (!includeComponents) {
34+
toIterate = toIterate.filter(s => s !== SchemaTypesToIterate.Components);
35+
}
36+
traverseAsyncApiDocument(document, callback, toIterate);
37+
38+
return new SchemasModel(Array.from(schemas));
39+
}

src/models/v2/asyncapi.ts

+3-20
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import { SecurityScheme } from './security-scheme';
1212
import { Schemas } from './schemas';
1313

1414
import { extensions } from './mixins';
15-
import { traverseAsyncApiDocument, SchemaTypesToIterate } from '../../iterator';
1615
import { tilde } from '../../utils';
16+
import { schemasFromDocument } from '../utils';
1717

1818
import type { AsyncAPIDocumentInterface } from '../asyncapi';
1919
import type { InfoInterface } from '../info';
@@ -27,7 +27,6 @@ import type { OperationInterface } from '../operation';
2727
import type { MessagesInterface } from '../messages';
2828
import type { MessageInterface } from '../message';
2929
import type { SchemasInterface } from '../schemas';
30-
import type { SchemaInterface } from '../schema';
3130
import type { SecuritySchemesInterface } from '../security-schemes';
3231
import type { ExtensionsInterface } from '../extensions';
3332

@@ -81,7 +80,7 @@ export class AsyncAPIDocument extends BaseModel<v2.AsyncAPIObject> implements As
8180
}
8281

8382
schemas(): SchemasInterface {
84-
return this.__schemas(false);
83+
return schemasFromDocument(this, Schemas, false);
8584
}
8685

8786
securitySchemes(): SecuritySchemesInterface {
@@ -130,26 +129,10 @@ export class AsyncAPIDocument extends BaseModel<v2.AsyncAPIObject> implements As
130129
}
131130

132131
allSchemas(): SchemasInterface {
133-
return this.__schemas(true);
132+
return schemasFromDocument(this, Schemas, true);
134133
}
135134

136135
extensions(): ExtensionsInterface {
137136
return extensions(this);
138137
}
139-
140-
private __schemas(withComponents: boolean) {
141-
const schemas: Set<SchemaInterface> = new Set();
142-
function callback(schema: SchemaInterface) {
143-
if (!schemas.has(schema.json())) {
144-
schemas.add(schema);
145-
}
146-
}
147-
148-
let toIterate = Object.values(SchemaTypesToIterate);
149-
if (!withComponents) {
150-
toIterate = toIterate.filter(s => s !== SchemaTypesToIterate.Components);
151-
}
152-
traverseAsyncApiDocument(this, callback, toIterate);
153-
return new Schemas(Array.from(schemas));
154-
}
155138
}

src/models/v3/asyncapi.ts

+8-24
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { Schemas } from './schemas';
1414

1515
import { extensions } from './mixins';
1616
import { tilde } from '../../utils';
17-
import { SchemaTypesToIterate, traverseAsyncApiDocument } from '../../iterator';
17+
import { schemasFromDocument } from '../utils';
1818

1919
import type { AsyncAPIDocumentInterface } from '../asyncapi';
2020
import type { InfoInterface } from '../info';
@@ -26,12 +26,12 @@ import type { MessageInterface } from '../message';
2626
import type { ComponentsInterface } from '../components';
2727
import type { SecuritySchemesInterface } from '../security-schemes';
2828
import type { ExtensionsInterface } from '../extensions';
29-
import type { SchemaInterface } from '../schema';
3029
import type { SchemasInterface } from '../schemas';
30+
import type { OperationInterface } from '../operation';
31+
import type { ChannelInterface } from '../channel';
32+
import type { ServerInterface } from '../server';
33+
3134
import type { v3 } from '../../spec-types';
32-
import { OperationInterface } from '../operation';
33-
import { ChannelInterface } from '../channel';
34-
import { ServerInterface } from '../server';
3535

3636
export class AsyncAPIDocument extends BaseModel<v3.AsyncAPIObject> implements AsyncAPIDocumentInterface {
3737
version(): string {
@@ -89,8 +89,8 @@ export class AsyncAPIDocument extends BaseModel<v3.AsyncAPIObject> implements As
8989
return new Messages(messages);
9090
}
9191

92-
schemas() {
93-
return this.__schemas(false);
92+
schemas(): SchemasInterface {
93+
return schemasFromDocument(this, Schemas, false);
9494
}
9595

9696
securitySchemes(): SecuritySchemesInterface {
@@ -138,26 +138,10 @@ export class AsyncAPIDocument extends BaseModel<v3.AsyncAPIObject> implements As
138138
}
139139

140140
allSchemas(): SchemasInterface {
141-
return this.__schemas(true);
141+
return schemasFromDocument(this, Schemas, true);
142142
}
143143

144144
extensions(): ExtensionsInterface {
145145
return extensions(this);
146146
}
147-
148-
private __schemas(withComponents: boolean) {
149-
const schemas: Set<SchemaInterface> = new Set();
150-
function callback(schema: SchemaInterface) {
151-
if (!schemas.has(schema.json())) {
152-
schemas.add(schema);
153-
}
154-
}
155-
156-
let toIterate = Object.values(SchemaTypesToIterate);
157-
if (!withComponents) {
158-
toIterate = toIterate.filter(s => s !== SchemaTypesToIterate.Components);
159-
}
160-
traverseAsyncApiDocument(this, callback, toIterate);
161-
return new Schemas(Array.from(schemas));
162-
}
163147
}

test/models/v2/asyncapi.spec.ts

+8
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,14 @@ describe('AsyncAPIDocument model', function() {
308308
const d = new AsyncAPIDocument(doc);
309309
expect(d.allSchemas()).toBeInstanceOf(Schemas);
310310
});
311+
312+
it('should return a collection of schemas (with schemas from components) without duplicates', function() {
313+
const sharedMessage = { payload: {} };
314+
const doc = serializeInput<v2.AsyncAPIObject>({ channels: { 'user/signup': { publish: { message: sharedMessage }, subscribe: { message: { oneOf: [{ payload: {} }, {}] } } }, 'user/logout': { publish: { message: { payload: {} } } } }, components: { messages: { aMessage: sharedMessage } } });
315+
const d = new AsyncAPIDocument(doc);
316+
expect(d.allSchemas()).toBeInstanceOf(Schemas);
317+
expect(d.allSchemas()).toHaveLength(3);
318+
});
311319
});
312320

313321
describe('mixins', function() {

test/models/v3/asyncapi.spec.ts

+7
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,13 @@ describe('AsyncAPIDocument model', function() {
213213
const d = new AsyncAPIDocument(doc);
214214
expect(d.allSchemas()).toBeInstanceOf(Schemas);
215215
});
216+
it('should return a collection of schemas (with schemas from components) without duplicates', function() {
217+
const sharedMessage = { payload: {} };
218+
const doc = serializeInput<v3.AsyncAPIObject>({ channels: { userSignup: { address: 'user/signup', messages: { someMessage1: { payload: {}}, someMessage2: sharedMessage } } }, components: { messages: { aMessage: sharedMessage } } });
219+
const d = new AsyncAPIDocument(doc);
220+
expect(d.allSchemas()).toBeInstanceOf(Schemas);
221+
expect(d.allSchemas()).toHaveLength(2);
222+
});
216223
});
217224

218225
describe('.allServers()', function() {

0 commit comments

Comments
 (0)