diff --git a/src/models/utils.ts b/src/models/utils.ts index 298a4dde0..ced726791 100644 --- a/src/models/utils.ts +++ b/src/models/utils.ts @@ -1,5 +1,9 @@ import type { BaseModel, ModelMetadata } from './base'; import type { DetailedAsyncAPI } from '../types'; +import { SchemaInterface } from './schema'; +import { SchemaTypesToIterate, traverseAsyncApiDocument } from '../iterator'; +import { AsyncAPIDocumentInterface } from './asyncapi'; +import { SchemasInterface } from 'models'; export interface Constructor extends Function { new (...any: any[]): T; @@ -12,3 +16,24 @@ export type InferModelMetadata = T extends BaseModel ? M : export function createModel(Model: Constructor, value: InferModelData, meta: Omit & { asyncapi?: DetailedAsyncAPI } & InferModelMetadata, parent?: BaseModel): T { return new Model(value, { ...meta, asyncapi: meta.asyncapi || parent?.meta().asyncapi }); } + +export function schemasFromDocument(document: AsyncAPIDocumentInterface, SchemasModel: Constructor, includeComponents: boolean): T { + const jsonInstances: Set = new Set(); + const schemas: Set = new Set(); + + function callback(schema: SchemaInterface) { + // comparing the reference (and not just the value) to the .json() object + if (!jsonInstances.has(schema.json())) { + jsonInstances.add(schema.json()); + schemas.add(schema); // unique schemas + } + } + + let toIterate = Object.values(SchemaTypesToIterate); + if (!includeComponents) { + toIterate = toIterate.filter(s => s !== SchemaTypesToIterate.Components); + } + traverseAsyncApiDocument(document, callback, toIterate); + + return new SchemasModel(Array.from(schemas)); +} diff --git a/src/models/v2/asyncapi.ts b/src/models/v2/asyncapi.ts index c7329c1b6..a5b5ebfbc 100644 --- a/src/models/v2/asyncapi.ts +++ b/src/models/v2/asyncapi.ts @@ -12,8 +12,8 @@ import { SecurityScheme } from './security-scheme'; import { Schemas } from './schemas'; import { extensions } from './mixins'; -import { traverseAsyncApiDocument, SchemaTypesToIterate } from '../../iterator'; import { tilde } from '../../utils'; +import { schemasFromDocument } from '../utils'; import type { AsyncAPIDocumentInterface } from '../asyncapi'; import type { InfoInterface } from '../info'; @@ -81,7 +81,7 @@ export class AsyncAPIDocument extends BaseModel implements As } schemas(): SchemasInterface { - return this.__schemas(false); + return schemasFromDocument(this, Schemas, false); } securitySchemes(): SecuritySchemesInterface { @@ -130,26 +130,10 @@ export class AsyncAPIDocument extends BaseModel implements As } allSchemas(): SchemasInterface { - return this.__schemas(true); + return schemasFromDocument(this, Schemas, true); } extensions(): ExtensionsInterface { return extensions(this); } - - private __schemas(withComponents: boolean) { - const schemas: Set = new Set(); - function callback(schema: SchemaInterface) { - if (!schemas.has(schema.json())) { - schemas.add(schema); - } - } - - let toIterate = Object.values(SchemaTypesToIterate); - if (!withComponents) { - toIterate = toIterate.filter(s => s !== SchemaTypesToIterate.Components); - } - traverseAsyncApiDocument(this, callback, toIterate); - return new Schemas(Array.from(schemas)); - } } diff --git a/src/models/v3/asyncapi.ts b/src/models/v3/asyncapi.ts index 671616690..756140b0b 100644 --- a/src/models/v3/asyncapi.ts +++ b/src/models/v3/asyncapi.ts @@ -14,7 +14,7 @@ import { Schemas } from './schemas'; import { extensions } from './mixins'; import { tilde } from '../../utils'; -import { SchemaTypesToIterate, traverseAsyncApiDocument } from '../../iterator'; +import { schemasFromDocument } from '../utils'; import type { AsyncAPIDocumentInterface } from '../asyncapi'; import type { InfoInterface } from '../info'; @@ -26,12 +26,12 @@ import type { MessageInterface } from '../message'; import type { ComponentsInterface } from '../components'; import type { SecuritySchemesInterface } from '../security-schemes'; import type { ExtensionsInterface } from '../extensions'; -import type { SchemaInterface } from '../schema'; import type { SchemasInterface } from '../schemas'; +import type { OperationInterface } from '../operation'; +import type { ChannelInterface } from '../channel'; +import type { ServerInterface } from '../server'; + import type { v3 } from '../../spec-types'; -import { OperationInterface } from '../operation'; -import { ChannelInterface } from '../channel'; -import { ServerInterface } from '../server'; export class AsyncAPIDocument extends BaseModel implements AsyncAPIDocumentInterface { version(): string { @@ -89,8 +89,8 @@ export class AsyncAPIDocument extends BaseModel implements As return new Messages(messages); } - schemas() { - return this.__schemas(false); + schemas(): SchemasInterface { + return schemasFromDocument(this, Schemas, false); } securitySchemes(): SecuritySchemesInterface { @@ -138,26 +138,10 @@ export class AsyncAPIDocument extends BaseModel implements As } allSchemas(): SchemasInterface { - return this.__schemas(true); + return schemasFromDocument(this, Schemas, true); } extensions(): ExtensionsInterface { return extensions(this); } - - private __schemas(withComponents: boolean) { - const schemas: Set = new Set(); - function callback(schema: SchemaInterface) { - if (!schemas.has(schema.json())) { - schemas.add(schema); - } - } - - let toIterate = Object.values(SchemaTypesToIterate); - if (!withComponents) { - toIterate = toIterate.filter(s => s !== SchemaTypesToIterate.Components); - } - traverseAsyncApiDocument(this, callback, toIterate); - return new Schemas(Array.from(schemas)); - } } diff --git a/test/models/v2/asyncapi.spec.ts b/test/models/v2/asyncapi.spec.ts index 7f89795ae..3b5146ca9 100644 --- a/test/models/v2/asyncapi.spec.ts +++ b/test/models/v2/asyncapi.spec.ts @@ -308,6 +308,14 @@ describe('AsyncAPIDocument model', function() { const d = new AsyncAPIDocument(doc); expect(d.allSchemas()).toBeInstanceOf(Schemas); }); + + it('should return a collection of schemas (with schemas from components) without duplicates', function() { + const sharedMessage = { payload: {} }; + const doc = serializeInput({ channels: { 'user/signup': { publish: { message: sharedMessage }, subscribe: { message: { oneOf: [{ payload: {} }, {}] } } }, 'user/logout': { publish: { message: { payload: {} } } } }, components: { messages: { aMessage: sharedMessage } } }); + const d = new AsyncAPIDocument(doc); + expect(d.allSchemas()).toBeInstanceOf(Schemas); + expect(d.allSchemas()).toHaveLength(3); + }); }); describe('mixins', function() { diff --git a/test/models/v3/asyncapi.spec.ts b/test/models/v3/asyncapi.spec.ts index c326796cc..85e28e371 100644 --- a/test/models/v3/asyncapi.spec.ts +++ b/test/models/v3/asyncapi.spec.ts @@ -213,6 +213,13 @@ describe('AsyncAPIDocument model', function() { const d = new AsyncAPIDocument(doc); expect(d.allSchemas()).toBeInstanceOf(Schemas); }); + it('should return a collection of schemas (with schemas from components) without duplicates', function() { + const sharedMessage = { payload: {} }; + const doc = serializeInput({ channels: { userSignup: { address: 'user/signup', messages: { someMessage1: { payload: {}}, someMessage2: sharedMessage } } }, components: { messages: { aMessage: sharedMessage } } }); + const d = new AsyncAPIDocument(doc); + expect(d.allSchemas()).toBeInstanceOf(Schemas); + expect(d.allSchemas()).toHaveLength(2); + }); }); describe('.allServers()', function() {