diff --git a/src/models/components.ts b/src/models/components.ts index 8e5d16a8c..0da724b2d 100644 --- a/src/models/components.ts +++ b/src/models/components.ts @@ -1,33 +1,30 @@ import type { BaseModel } from './base'; -import type { Collection } from './collection'; -import type { ServersInterface } from './servers'; -import type { ChannelsInterface } from './channels'; -import type { OperationsInterface } from './operations'; -import type { OperationTraitsInterface } from './operation-traits'; -import type { MessagesInterface } from './messages'; -import type { MessageTraitsInterface } from './message-traits'; -import type { SchemasInterface } from './schemas'; -import type { ChannelParametersInterface } from './channel-parameters'; -import type { ServerVariablesInterface } from './server-variables'; +import type { ServerInterface } from './server'; +import type { ChannelInterface } from './channel'; +import type { OperationTraitInterface } from './operation-trait'; +import type { MessageInterface } from './message'; +import type { MessageTraitInterface } from './message-trait'; +import type { SchemaInterface } from './schema'; +import type { ChannelParameterInterface } from './channel-parameter'; +import type { ServerVariableInterface } from './server-variable'; import type { CorrelationIdInterface } from './correlation-id'; import type { BindingsInterface } from './bindings'; -import type { SecuritySchemesInterface } from './security-schemes'; +import type { SecuritySchemeInterface } from './security-scheme'; import type { ExtensionsMixinInterface } from './mixins'; -export interface Components extends BaseModel, ExtensionsMixinInterface { - servers(): ServersInterface; - channels(): ChannelsInterface; - operations(): OperationsInterface; - messages(): MessagesInterface; - schemas(): SchemasInterface; - channelParameters(): ChannelParametersInterface; - serverVariables(): ServerVariablesInterface; - operationTraits(): OperationTraitsInterface; - messageTraits(): MessageTraitsInterface; - correlationIds(): Collection; - securitySchemes(): SecuritySchemesInterface; - serverBindings(): Collection; - channelBindings(): Collection; - operationBindings(): Collection; - messageBindings(): Collection; +export interface ComponentsInterface extends BaseModel, ExtensionsMixinInterface { + servers(): Record; + channels(): Record; + messages(): Record; + schemas(): Record; + channelParameters(): Record; + serverVariables(): Record; + operationTraits(): Record; + messageTraits(): Record; + correlationIds(): Record; + securitySchemes(): Record; + serverBindings(): Record; + channelBindings(): Record; + operationBindings(): Record; + messageBindings(): Record; } diff --git a/src/models/v2/asyncapi.ts b/src/models/v2/asyncapi.ts index 338a7043a..f1ced823f 100644 --- a/src/models/v2/asyncapi.ts +++ b/src/models/v2/asyncapi.ts @@ -20,7 +20,7 @@ export class AsyncAPIDocument extends Mixin(BaseModel, ExtensionsMixin) implemen servers(): ServersInterface { return new Servers( - Object.entries(this._json.servers).map(([serverName, server]) => + Object.entries(this._json.servers || {}).map(([serverName, server]) => this.createModel(Server, server, { id: serverName, pointer: `/servers/${serverName}` }) ) ); diff --git a/src/models/v2/components.ts b/src/models/v2/components.ts new file mode 100644 index 000000000..89bf62364 --- /dev/null +++ b/src/models/v2/components.ts @@ -0,0 +1,105 @@ +import { BaseModel } from "../base"; +import { Channel } from "./channel"; +import { ChannelParameter } from "./channel-parameter"; +import { CorrelationId } from "./correlation-id"; +import { Message } from "./message"; +import { MessageTrait } from "./message-trait"; +import { OperationTrait } from "./operation-trait"; +import { Schema } from "./schema"; +import { SecurityScheme } from "./security-scheme"; +import { Server } from "./server"; +import { ServerVariable } from "./server-variable"; + +import { Mixin } from '../utils'; +import { Bindings, Binding } from "./mixins/bindings"; +import { ExtensionsMixin } from './mixins/extensions'; + +import type { BindingsInterface } from "../bindings"; +import type { ComponentsInterface } from "../components"; +import type { ChannelInterface } from "../channel"; +import type { ChannelParameterInterface } from "../channel-parameter"; +import type { CorrelationIdInterface } from "../correlation-id"; +import type { MessageInterface } from "../message"; +import type { MessageTraitInterface } from "../message-trait"; +import type { OperationTraitInterface } from "../operation-trait"; +import type { SchemaInterface } from "../schema"; +import type { SecuritySchemeInterface } from "../security-scheme"; +import type { ServerInterface } from "../server"; +import type { ServerVariableInterface } from "../server-variable"; +import type { Constructor } from "../utils"; + +export class Components extends Mixin(BaseModel, ExtensionsMixin) implements ComponentsInterface { + servers(): Record { + return this.createMap('servers', Server); + } + + channels(): Record { + return this.createMap('channels', Channel); + } + + messages(): Record { + return this.createMap('messages', Message); + } + + schemas(): Record { + return this.createMap('schemas', Schema); + } + + channelParameters(): Record { + return this.createMap('parameters', ChannelParameter); + } + + serverVariables(): Record { + return this.createMap('serverVariables', ServerVariable); + } + + operationTraits(): Record { + return this.createMap('operationTraits', OperationTrait); + } + + messageTraits(): Record { + return this.createMap('messageTraits', MessageTrait); + } + + correlationIds(): Record { + return this.createMap('correlationIds', CorrelationId); + } + + securitySchemes(): Record { + return this.createMap('securitySchemes', SecurityScheme); + } + + serverBindings(): Record { + return this.createBindings('serverBindings'); + } + + channelBindings(): Record { + return this.createBindings('channelBindings'); + } + + operationBindings(): Record { + return this.createBindings('operationBindings'); + } + + messageBindings(): Record { + return this.createBindings('messageBindings'); + } + + protected createMap(itemsName: string, model: Constructor): Record { + return Object.entries(this._json[itemsName] || {}).reduce((items, [itemName, item]) => { + items[itemName] = this.createModel(model, item, { id: itemName, pointer: `/components/${itemsName}/${itemName}` }) + return items; + }, {} as Record); + } + + protected createBindings(itemsName: string): Record { + return Object.entries(this._json[itemsName] || {}).reduce((bindings, [name, item]) => { + bindings[name] = new Bindings( + Object.entries(item as any || {}).map(([protocol, binding]) => + this.createModel(Binding, binding, { id: protocol, pointer: `components/${itemsName}/${name}/${protocol}` }) + ) + ); + return bindings; + }, {} as Record); + } +} diff --git a/src/models/v2/mixins/bindings.ts b/src/models/v2/mixins/bindings.ts index 10135f31f..d15d8ddb1 100644 --- a/src/models/v2/mixins/bindings.ts +++ b/src/models/v2/mixins/bindings.ts @@ -44,9 +44,8 @@ export class Bindings extends Collection implements BindingsIn export abstract class BindingsMixin extends BaseModel implements BindingsMixinInterface { bindings(): BindingsInterface { - const bindings: Record = this._json.bindings || {}; return new Bindings( - Object.entries(bindings).map(([protocol, binding]) => + Object.entries(this._json.bindings || {}).map(([protocol, binding]) => this.createModel(Binding, binding, { id: protocol, pointer: `${this._meta.pointer}/bindings/${protocol}` }) ) ); diff --git a/src/models/v2/mixins/tags.ts b/src/models/v2/mixins/tags.ts index 4cc0a4fa0..eaf49931b 100644 --- a/src/models/v2/mixins/tags.ts +++ b/src/models/v2/mixins/tags.ts @@ -28,9 +28,8 @@ export class Tags extends Collection implements TagsInterface { export abstract class TagsMixin extends BaseModel implements TagsMixinInterface { tags(): TagsInterface { - const tags = this._json.tags || []; return new Tags( - tags.map((tag: any, idx: number) => + (this._json.tags || []).map((tag: any, idx: number) => this.createModel(Tag, tag, { pointer: `${this._meta.pointer}/tags/${idx}` }) ) ); diff --git a/src/models/v2/schemas.ts b/src/models/v2/schemas.ts new file mode 100644 index 000000000..dea892a87 --- /dev/null +++ b/src/models/v2/schemas.ts @@ -0,0 +1,14 @@ +import { Collection } from '../collection'; + +import type { SchemasInterface } from '../schemas'; +import type { SchemaInterface } from '../schema'; + +export class Schemas extends Collection implements SchemasInterface { + override get(id: string): SchemaInterface | undefined { + return this.collections.find(schema => schema.uid() === id); + } + + override has(id: string): boolean { + return this.collections.some(schema => schema.uid() === id); + } +} diff --git a/test/models/v2/components.spec.ts b/test/models/v2/components.spec.ts new file mode 100644 index 000000000..6265ed543 --- /dev/null +++ b/test/models/v2/components.spec.ts @@ -0,0 +1,260 @@ +import { Components } from '../../../src/models/v2/components'; +import { Bindings } from '../../../src/models/v2/mixins/bindings'; +import { Channel } from '../../../src/models/v2/channel'; +import { ChannelParameter } from '../../../src/models/v2/channel-parameter'; +import { CorrelationId } from '../../../src/models/v2/correlation-id'; +import { OperationTrait } from '../../../src/models/v2/operation-trait'; +import { Message } from '../../../src/models/v2/message'; +import { MessageTrait } from '../../../src/models/v2/message-trait'; +import { Schema } from '../../../src/models/v2/schema'; +import { Server } from '../../../src/models/v2/server'; +import { ServerVariable } from '../../../src/models/v2/server-variable'; +import { SecurityScheme } from '../../../src/models/v2/security-scheme'; + +import { + assertExtensionsMixinInheritance, +} from './mixins/inheritance'; + +describe('Components model', function() { + describe('.servers()', function() { + it('should return map of servers', function() { + const doc = { servers: { server: {} } }; + const d = new Components(doc); + expect(typeof d.servers()).toEqual('object'); + expect(Object.keys(d.servers())).toHaveLength(1); + expect(d.servers()['server']).toBeInstanceOf(Server); + }); + + it('should return empty map when servers are not defined', function() { + const doc = {}; + const d = new Components(doc); + expect(typeof d.servers()).toEqual('object'); + expect(Object.keys(d.servers())).toHaveLength(0); + }); + }); + + describe('.channels()', function() { + it('should return map of channels', function() { + const doc = { channels: { channel: {} } }; + const d = new Components(doc); + expect(typeof d.channels()).toEqual('object'); + expect(Object.keys(d.channels())).toHaveLength(1); + expect(d.channels()['channel']).toBeInstanceOf(Channel); + }); + + it('should return empty map when channels are not defined', function() { + const doc = {}; + const d = new Components(doc); + expect(typeof d.channels()).toEqual('object'); + expect(Object.keys(d.channels())).toHaveLength(0); + }); + }); + + describe('.messages()', function() { + it('should return map of messages', function() { + const doc = { messages: { message: {} } }; + const d = new Components(doc); + expect(typeof d.messages()).toEqual('object'); + expect(Object.keys(d.messages())).toHaveLength(1); + expect(d.messages()['message']).toBeInstanceOf(Message); + }); + + it('should return empty map when messages are not defined', function() { + const doc = {}; + const d = new Components(doc); + expect(typeof d.messages()).toEqual('object'); + expect(Object.keys(d.messages())).toHaveLength(0); + }); + }); + + describe('.schemas()', function() { + it('should return map of schemas', function() { + const doc = { schemas: { schema: {} } }; + const d = new Components(doc); + expect(typeof d.schemas()).toEqual('object'); + expect(Object.keys(d.schemas())).toHaveLength(1); + expect(d.schemas()['schema']).toBeInstanceOf(Schema); + }); + + it('should return empty map when schemas are not defined', function() { + const doc = {}; + const d = new Components(doc); + expect(typeof d.schemas()).toEqual('object'); + expect(Object.keys(d.schemas())).toHaveLength(0); + }); + }); + + describe('.channelParameters()', function() { + it('should return map of channelParameters', function() { + const doc = { parameters: { parameter: {} } }; + const d = new Components(doc); + expect(typeof d.channelParameters()).toEqual('object'); + expect(Object.keys(d.channelParameters())).toHaveLength(1); + expect(d.channelParameters()['parameter']).toBeInstanceOf(ChannelParameter); + }); + + it('should return empty map when channelParameters are not defined', function() { + const doc = {}; + const d = new Components(doc); + expect(typeof d.channelParameters()).toEqual('object'); + expect(Object.keys(d.channelParameters())).toHaveLength(0); + }); + }); + + describe('.serverVariables()', function() { + it('should return map of serverVariables', function() { + const doc = { serverVariables: { variable: {} } }; + const d = new Components(doc); + expect(typeof d.serverVariables()).toEqual('object'); + expect(Object.keys(d.serverVariables())).toHaveLength(1); + expect(d.serverVariables()['variable']).toBeInstanceOf(ServerVariable); + }); + + it('should return empty map when serverVariables are not defined', function() { + const doc = {}; + const d = new Components(doc); + expect(typeof d.serverVariables()).toEqual('object'); + expect(Object.keys(d.serverVariables())).toHaveLength(0); + }); + }); + + describe('.operationTraits()', function() { + it('should return map of operationTraits', function() { + const doc = { operationTraits: { trait: {} } }; + const d = new Components(doc); + expect(typeof d.operationTraits()).toEqual('object'); + expect(Object.keys(d.operationTraits())).toHaveLength(1); + expect(d.operationTraits()['trait']).toBeInstanceOf(OperationTrait); + }); + + it('should return empty map when operationTraits are not defined', function() { + const doc = {}; + const d = new Components(doc); + expect(typeof d.operationTraits()).toEqual('object'); + expect(Object.keys(d.operationTraits())).toHaveLength(0); + }); + }); + + describe('.messageTraits()', function() { + it('should return map of messageTraits', function() { + const doc = { messageTraits: { trait: {} } }; + const d = new Components(doc); + expect(typeof d.messageTraits()).toEqual('object'); + expect(Object.keys(d.messageTraits())).toHaveLength(1); + expect(d.messageTraits()['trait']).toBeInstanceOf(MessageTrait); + }); + + it('should return empty map when messageTraits are not defined', function() { + const doc = {}; + const d = new Components(doc); + expect(typeof d.messageTraits()).toEqual('object'); + expect(Object.keys(d.messageTraits())).toHaveLength(0); + }); + }); + + describe('.correlationIds()', function() { + it('should return map of correlationIds', function() { + const doc = { correlationIds: { id: {} } }; + const d = new Components(doc); + expect(typeof d.correlationIds()).toEqual('object'); + expect(Object.keys(d.correlationIds())).toHaveLength(1); + expect(d.correlationIds()['id']).toBeInstanceOf(CorrelationId); + }); + + it('should return empty map when correlationIds are not defined', function() { + const doc = {}; + const d = new Components(doc); + expect(typeof d.correlationIds()).toEqual('object'); + expect(Object.keys(d.correlationIds())).toHaveLength(0); + }); + }); + + describe('.securitySchemes()', function() { + it('should return map of securitySchemes', function() { + const doc = { securitySchemes: { scheme: {} } }; + const d = new Components(doc); + expect(typeof d.securitySchemes()).toEqual('object'); + expect(Object.keys(d.securitySchemes())).toHaveLength(1); + expect(d.securitySchemes()['scheme']).toBeInstanceOf(SecurityScheme); + }); + + it('should return empty map when securitySchemes are not defined', function() { + const doc = {}; + const d = new Components(doc); + expect(typeof d.securitySchemes()).toEqual('object'); + expect(Object.keys(d.securitySchemes())).toHaveLength(0); + }); + }); + + describe('.serverBindings()', function() { + it('should return map of serverBindings', function() { + const doc = { serverBindings: { bidning: {} } }; + const d = new Components(doc); + expect(typeof d.serverBindings()).toEqual('object'); + expect(Object.keys(d.serverBindings())).toHaveLength(1); + expect(d.serverBindings()['bidning']).toBeInstanceOf(Bindings); + }); + + it('should return empty map when serverBindings are not defined', function() { + const doc = {}; + const d = new Components(doc); + expect(typeof d.serverBindings()).toEqual('object'); + expect(Object.keys(d.serverBindings())).toHaveLength(0); + }); + }); + + describe('.channelBindings()', function() { + it('should return map of channelBindings', function() { + const doc = { channelBindings: { bidning: {} } }; + const d = new Components(doc); + expect(typeof d.channelBindings()).toEqual('object'); + expect(Object.keys(d.channelBindings())).toHaveLength(1); + expect(d.channelBindings()['bidning']).toBeInstanceOf(Bindings); + }); + + it('should return empty map when channelBindings are not defined', function() { + const doc = {}; + const d = new Components(doc); + expect(typeof d.channelBindings()).toEqual('object'); + expect(Object.keys(d.channelBindings())).toHaveLength(0); + }); + }); + + describe('.operationBindings()', function() { + it('should return map of operationBindings', function() { + const doc = { operationBindings: { bidning: {} } }; + const d = new Components(doc); + expect(typeof d.operationBindings()).toEqual('object'); + expect(Object.keys(d.operationBindings())).toHaveLength(1); + expect(d.operationBindings()['bidning']).toBeInstanceOf(Bindings); + }); + + it('should return empty map when operationBindings are not defined', function() { + const doc = {}; + const d = new Components(doc); + expect(typeof d.operationBindings()).toEqual('object'); + expect(Object.keys(d.operationBindings())).toHaveLength(0); + }); + }); + + describe('.messageBindings()', function() { + it('should return map of messageBindings', function() { + const doc = { messageBindings: { bidning: {} } }; + const d = new Components(doc); + expect(typeof d.messageBindings()).toEqual('object'); + expect(Object.keys(d.messageBindings())).toHaveLength(1); + expect(d.messageBindings()['bidning']).toBeInstanceOf(Bindings); + }); + + it('should return empty map when messageBindings are not defined', function() { + const doc = {}; + const d = new Components(doc); + expect(typeof d.messageBindings()).toEqual('object'); + expect(Object.keys(d.messageBindings())).toHaveLength(0); + }); + }); + + describe('mixins inheritance', function() { + assertExtensionsMixinInheritance(Components); + }); +});