Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: doc.allSchemas() skips now duplicates #897

Merged
merged 2 commits into from
Nov 14, 2023
Merged
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
25 changes: 25 additions & 0 deletions src/models/utils.ts
Original file line number Diff line number Diff line change
@@ -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<T> extends Function {
new (...any: any[]): T;
Expand All @@ -12,3 +16,24 @@ export type InferModelMetadata<T> = T extends BaseModel<infer _, infer M> ? M :
export function createModel<T extends BaseModel>(Model: Constructor<T>, value: InferModelData<T>, meta: Omit<ModelMetadata, 'asyncapi'> & { asyncapi?: DetailedAsyncAPI } & InferModelMetadata<T>, parent?: BaseModel): T {
return new Model(value, { ...meta, asyncapi: meta.asyncapi || parent?.meta().asyncapi });
}

export function schemasFromDocument<T extends SchemasInterface>(document: AsyncAPIDocumentInterface, SchemasModel: Constructor<T>, includeComponents: boolean): T {
Copy link
Member Author

@smoya smoya Nov 14, 2023

Choose a reason for hiding this comment

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

Since the logic is exactly the same for both v2 and v3 models, I moved this to this common file.

const jsonInstances: Set<any> = new Set();
const schemas: Set<SchemaInterface> = 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));
}
23 changes: 3 additions & 20 deletions src/models/v2/asyncapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -27,7 +27,6 @@ import type { OperationInterface } from '../operation';
import type { MessagesInterface } from '../messages';
import type { MessageInterface } from '../message';
import type { SchemasInterface } from '../schemas';
import type { SchemaInterface } from '../schema';
import type { SecuritySchemesInterface } from '../security-schemes';
import type { ExtensionsInterface } from '../extensions';

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

schemas(): SchemasInterface {
return this.__schemas(false);
return schemasFromDocument(this, Schemas, false);
}

securitySchemes(): SecuritySchemesInterface {
Expand Down Expand Up @@ -130,26 +129,10 @@ export class AsyncAPIDocument extends BaseModel<v2.AsyncAPIObject> 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<SchemaInterface> = 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));
}
}
32 changes: 8 additions & 24 deletions src/models/v3/asyncapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Copy link
Member Author

Choose a reason for hiding this comment

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

Just reordering and tiding up imports


import type { v3 } from '../../spec-types';
import { OperationInterface } from '../operation';
import { ChannelInterface } from '../channel';
import { ServerInterface } from '../server';

export class AsyncAPIDocument extends BaseModel<v3.AsyncAPIObject> implements AsyncAPIDocumentInterface {
version(): string {
Expand Down Expand Up @@ -89,8 +89,8 @@ export class AsyncAPIDocument extends BaseModel<v3.AsyncAPIObject> implements As
return new Messages(messages);
}

schemas() {
return this.__schemas(false);
schemas(): SchemasInterface {
return schemasFromDocument(this, Schemas, false);
}

securitySchemes(): SecuritySchemesInterface {
Expand Down Expand Up @@ -138,26 +138,10 @@ export class AsyncAPIDocument extends BaseModel<v3.AsyncAPIObject> 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<SchemaInterface> = 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));
}
}
8 changes: 8 additions & 0 deletions test/models/v2/asyncapi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<v2.AsyncAPIObject>({ 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() {
Expand Down
7 changes: 7 additions & 0 deletions test/models/v3/asyncapi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {} };
Copy link
Member Author

Choose a reason for hiding this comment

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

Remember, we compare by "reference" and not by value.

const doc = serializeInput<v3.AsyncAPIObject>({ 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() {
Expand Down