diff --git a/asyncapi-parser-2.0.0-next-major.8.tgz b/asyncapi-parser-2.0.0-next-major.8.tgz new file mode 100644 index 000000000..53346f295 Binary files /dev/null and b/asyncapi-parser-2.0.0-next-major.8.tgz differ diff --git a/src/models/components.ts b/src/models/components.ts index f96524f1e..c88d88991 100644 --- a/src/models/components.ts +++ b/src/models/components.ts @@ -3,6 +3,7 @@ import type { BindingsInterface } from './bindings'; import type { ExtensionsMixinInterface } from './mixins'; import type { ServersInterface } from './servers'; import type { ChannelsInterface } from './channels'; +import type { OperationsInterface } from './operations'; import type { MessagesInterface } from './messages'; import type { SchemasInterface } from './schemas'; import type { ChannelParametersInterface } from './channel-parameters'; @@ -11,20 +12,23 @@ import type { OperationTraitsInterface } from './operation-traits'; import type { MessageTraitsInterface } from './message-traits'; import type { SecuritySchemesInterface } from './security-schemes'; import type { CorrelationIdsInterface } from './correlation-ids'; -import type { OperationsInterface } from './operations'; +import type { ExternalDocumentationsInterface } from './external-documentations'; +import type { TagsInterface } from './tags'; export interface ComponentsInterface extends BaseModel, ExtensionsMixinInterface { servers(): ServersInterface; channels(): ChannelsInterface; + operations(): OperationsInterface; messages(): MessagesInterface; schemas(): SchemasInterface; channelParameters(): ChannelParametersInterface; serverVariables(): ServerVariablesInterface; - operations(): OperationsInterface; operationTraits(): OperationTraitsInterface; messageTraits(): MessageTraitsInterface; correlationIds(): CorrelationIdsInterface; securitySchemes(): SecuritySchemesInterface; + tags(): TagsInterface; + externalDocs(): ExternalDocumentationsInterface; serverBindings(): Record; channelBindings(): Record; operationBindings(): Record; diff --git a/src/models/external-docs.ts b/src/models/external-documentation.ts similarity index 100% rename from src/models/external-docs.ts rename to src/models/external-documentation.ts diff --git a/src/models/external-documentations.ts b/src/models/external-documentations.ts new file mode 100644 index 000000000..e9f14b451 --- /dev/null +++ b/src/models/external-documentations.ts @@ -0,0 +1,11 @@ +import { Collection } from './collection'; + +import type { ExternalDocumentationInterface } from './external-documentation'; + +export type ExternalDocumentationsInterface = Collection + +export class ExternalDocumentations extends Collection implements ExternalDocumentationsInterface { + override get(id: string): ExternalDocumentationInterface | undefined { + return this.collections.find(externalDocs => externalDocs.meta('id' as any) === id); + } +} \ No newline at end of file diff --git a/src/models/index.ts b/src/models/index.ts index 1161d0a38..cb0aa250f 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -16,7 +16,7 @@ export * from './correlation-id'; export * from './correlation-ids'; export * from './extension'; export * from './extensions'; -export * from './external-docs'; +export * from './external-documentation'; export * from './info'; export * from './license'; export * from './message-example'; diff --git a/src/models/mixins.ts b/src/models/mixins.ts index 7cd305208..42d8cff71 100644 --- a/src/models/mixins.ts +++ b/src/models/mixins.ts @@ -1,6 +1,6 @@ import type { BindingsInterface } from './bindings'; import type { ExtensionsInterface } from './extensions'; -import type { ExternalDocumentationInterface } from './external-docs'; +import type { ExternalDocumentationInterface } from './external-documentation'; import type { TagsInterface } from './tags'; export interface BindingsMixinInterface { diff --git a/src/models/tags.ts b/src/models/tags.ts index 475cec00d..23a3260de 100644 --- a/src/models/tags.ts +++ b/src/models/tags.ts @@ -1,4 +1,5 @@ import { Collection } from './collection'; + import type { TagInterface } from './tag'; export type TagsInterface = Collection diff --git a/src/models/v2/channel.ts b/src/models/v2/channel.ts index 5ba192a9d..8265c2024 100644 --- a/src/models/v2/channel.ts +++ b/src/models/v2/channel.ts @@ -14,7 +14,7 @@ import type { BindingsInterface } from '../bindings'; import type { ChannelInterface } from '../channel'; import type { ChannelParametersInterface } from '../channel-parameters'; import type { ExtensionsInterface } from '../extensions'; -import type { ExternalDocumentationInterface } from '../external-docs'; +import type { ExternalDocumentationInterface } from '../external-documentation'; import type { MessagesInterface } from '../messages'; import type { MessageInterface } from '../message'; import type { OperationsInterface } from '../operations'; diff --git a/src/models/v2/components.ts b/src/models/v2/components.ts index 8f2ace0af..d66dc06d9 100644 --- a/src/models/v2/components.ts +++ b/src/models/v2/components.ts @@ -25,6 +25,8 @@ import { SecuritySchemes } from '../security-schemes'; import { CorrelationIds } from '../correlation-ids'; import { Operations } from '../operations'; import { Message } from './message'; +import { ExternalDocumentations } from '../external-documentations'; +import { Tags } from '../tags'; import { tilde } from '../../utils'; @@ -42,7 +44,8 @@ import type { OperationTraitsInterface } from '../operation-traits'; import type { SecuritySchemesInterface } from '../security-schemes'; import type { MessageTraitsInterface } from '../message-traits'; import type { OperationsInterface } from '../operations'; -import type { OperationInterface } from '../operation'; +import type { ExternalDocumentationsInterface } from '../external-documentations'; +import type { TagsInterface } from '../tags'; import type { v2 } from '../../spec-types'; @@ -52,11 +55,11 @@ export class Components extends BaseModel implements Compon } channels(): ChannelsInterface { - return new Channels( - Object.entries(this._json.channels || {}).map(([channelAddress, channel]) => - this.createModel(Channel, channel as v2.ChannelObject, { id: channelAddress, address: '', pointer: `/components/channels/${tilde(channelAddress)}` }) - ) - ); + return this.createCollection('channels', Channels, Channel); + } + + operations(): OperationsInterface { + return new Operations([]); } messages(): MessagesInterface { @@ -75,12 +78,6 @@ export class Components extends BaseModel implements Compon return this.createCollection('serverVariables', ServerVariables, ServerVariable); } - operations(): OperationsInterface { - const operations: OperationInterface[] = []; - this.channels().forEach(channel => operations.push(...channel.operations().all())); - return new Operations(operations); - } - operationTraits(): OperationTraitsInterface { return this.createCollection('operationTraits', OperationTraits, OperationTrait); } @@ -97,6 +94,14 @@ export class Components extends BaseModel implements Compon return this.createCollection('securitySchemes', SecuritySchemes, SecurityScheme); } + tags(): TagsInterface { + return new Tags([]); + } + + externalDocs(): ExternalDocumentationsInterface { + return new ExternalDocumentations([]); + } + serverBindings(): Record { return this.createBindings('serverBindings'); } @@ -124,7 +129,7 @@ export class Components extends BaseModel implements Compon protected createCollection, T extends BaseModel>(itemsName: keyof v2.ComponentsObject, collectionModel: Constructor, itemModel: Constructor): M { const collectionItems: T[] = []; Object.entries(this._json[itemsName] || {}).forEach(([id, item]) => { - collectionItems.push(this.createModel(itemModel, item as any, { id, pointer: `/components/${itemsName}/${id}` } as any)); + collectionItems.push(this.createModel(itemModel, item as any, { id, pointer: `/components/${itemsName}/${tilde(id)}` } as any)); }); return new collectionModel(collectionItems); } diff --git a/src/models/v2/external-docs.ts b/src/models/v2/external-documentation.ts similarity index 85% rename from src/models/v2/external-docs.ts rename to src/models/v2/external-documentation.ts index 5c6e513e8..ccaf9d7a3 100644 --- a/src/models/v2/external-docs.ts +++ b/src/models/v2/external-documentation.ts @@ -2,12 +2,12 @@ import { BaseModel } from '../base'; import { hasDescription, description, extensions } from './mixins'; -import type { ExternalDocumentationInterface } from '../external-docs'; +import type { ExternalDocumentationInterface } from '../external-documentation'; import type { ExtensionsInterface } from '../extensions'; import type { v2 } from '../../spec-types'; -export class ExternalDocumentation extends BaseModel implements ExternalDocumentationInterface { +export class ExternalDocumentation extends BaseModel implements ExternalDocumentationInterface { url(): string { return this._json.url; } diff --git a/src/models/v2/index.ts b/src/models/v2/index.ts index 17b34f9b6..f23a56bf7 100644 --- a/src/models/v2/index.ts +++ b/src/models/v2/index.ts @@ -10,7 +10,7 @@ export { Contact as ContactV2 } from './contact'; export { CorrelationId as CorrelationIdV2 } from './correlation-id'; export { Extension as ExtensionV2 } from './extension'; export { Extensions as ExtensionsV2 } from '../extensions'; -export { ExternalDocumentation as ExternalDocumentationV2 } from './external-docs'; +export { ExternalDocumentation as ExternalDocumentationV2 } from './external-documentation'; export { Info as InfoV2 } from './info'; export { License as LicenseV2 } from './license'; export { MessageExample as MessageExampleV2 } from './message-example'; diff --git a/src/models/v2/info.ts b/src/models/v2/info.ts index 5afae182b..ad21214f9 100644 --- a/src/models/v2/info.ts +++ b/src/models/v2/info.ts @@ -1,6 +1,6 @@ import { BaseModel } from '../base'; import { Contact } from './contact'; -import { ExternalDocumentation } from './external-docs'; +import { ExternalDocumentation } from './external-documentation'; import { License } from './license'; import { Tags } from '../tags'; import { Tag } from './tag'; @@ -10,7 +10,7 @@ import { hasDescription, description, extensions } from './mixins'; import type { ContactInterface } from '../contact'; import type { InfoInterface } from '../info'; import type { ExtensionsInterface } from '../extensions'; -import type { ExternalDocumentationInterface } from '../external-docs'; +import type { ExternalDocumentationInterface } from '../external-documentation'; import type { LicenseInterface } from '../license'; import type { TagsInterface } from '../tags'; diff --git a/src/models/v2/message-trait.ts b/src/models/v2/message-trait.ts index 2f1333acb..5948331a6 100644 --- a/src/models/v2/message-trait.ts +++ b/src/models/v2/message-trait.ts @@ -11,7 +11,7 @@ import { bindings, hasDescription, description, extensions, hasExternalDocs, ext import type { BindingsInterface } from '../bindings'; import type { CorrelationIdInterface } from '../correlation-id'; import type { ExtensionsInterface } from '../extensions'; -import type { ExternalDocumentationInterface } from '../external-docs'; +import type { ExternalDocumentationInterface } from '../external-documentation'; import type { MessageExamplesInterface } from '../message-examples'; import type { MessageTraitInterface } from '../message-trait'; import type { SchemaInterface } from '../schema'; diff --git a/src/models/v2/mixins.ts b/src/models/v2/mixins.ts index d93f099cf..464b1847a 100644 --- a/src/models/v2/mixins.ts +++ b/src/models/v2/mixins.ts @@ -2,7 +2,7 @@ import { Bindings } from './bindings'; import { Binding } from './binding'; import { Extensions } from '../extensions'; import { Extension } from './extension'; -import { ExternalDocumentation } from './external-docs'; +import { ExternalDocumentation } from './external-documentation'; import { Tags } from '../tags'; import { Tag } from './tag'; @@ -13,7 +13,7 @@ import type { BaseModel } from '../base'; import type { BindingsInterface } from '../bindings'; import type { ExtensionsInterface } from '../extensions'; import type { ExtensionInterface } from '../extension'; -import type { ExternalDocumentationInterface } from '../external-docs'; +import type { ExternalDocumentationInterface } from '../external-documentation'; import type { TagsInterface } from '../tags'; import type { v2 } from '../../spec-types'; diff --git a/src/models/v2/operation-trait.ts b/src/models/v2/operation-trait.ts index 7b9d78a5a..d7910b6dd 100644 --- a/src/models/v2/operation-trait.ts +++ b/src/models/v2/operation-trait.ts @@ -8,7 +8,7 @@ import { bindings, hasDescription, description, extensions, hasExternalDocs, ext import type { BindingsInterface } from '../bindings'; import type { ExtensionsInterface } from '../extensions'; -import type { ExternalDocumentationInterface } from '../external-docs'; +import type { ExternalDocumentationInterface } from '../external-documentation'; import type { ChannelsInterface } from '../channels'; import type { OperationAction } from '../operation'; import type { OperationTraitInterface } from '../operation-trait'; diff --git a/src/models/v2/schema.ts b/src/models/v2/schema.ts index 49c5ac1d8..d986706d2 100644 --- a/src/models/v2/schema.ts +++ b/src/models/v2/schema.ts @@ -6,7 +6,7 @@ import { retrievePossibleRef, hasRef } from '../../utils'; import type { ModelMetadata } from '../base'; import type { ExtensionsInterface } from '../extensions'; -import type { ExternalDocumentationInterface } from '../external-docs'; +import type { ExternalDocumentationInterface } from '../external-documentation'; import type { SchemaInterface } from '../schema'; import type { v2 } from '../../spec-types'; diff --git a/src/models/v2/tag.ts b/src/models/v2/tag.ts index 758043b05..e67144333 100644 --- a/src/models/v2/tag.ts +++ b/src/models/v2/tag.ts @@ -3,12 +3,12 @@ import { BaseModel } from '../base'; import { hasDescription, description, extensions, hasExternalDocs, externalDocs } from './mixins'; import type { ExtensionsInterface } from '../extensions'; -import type{ ExternalDocumentationInterface } from '../external-docs'; +import type{ ExternalDocumentationInterface } from '../external-documentation'; import type { TagInterface } from '../tag'; import type { v2 } from '../../spec-types'; -export class Tag extends BaseModel implements TagInterface { +export class Tag extends BaseModel implements TagInterface { name(): string { return this._json.name; } diff --git a/src/models/v3/asyncapi.ts b/src/models/v3/asyncapi.ts index 81b3ea604..dae3d17bb 100644 --- a/src/models/v3/asyncapi.ts +++ b/src/models/v3/asyncapi.ts @@ -1,15 +1,29 @@ import { BaseModel } from '../base'; +import { Info } from './info'; +import { Servers } from '../servers'; +import { Server } from './server'; import { Channels } from '../channels'; import { Channel } from './channel'; import { Operations } from '../operations'; import { Operation } from './operation'; +import { Messages } from '../messages'; +import { SecuritySchemes } from '../security-schemes'; +import { SecurityScheme } from './security-scheme'; +import { Components } from './components'; import { extensions } from './mixins'; import { tilde } from '../../utils'; import type { AsyncAPIDocumentInterface } from '../asyncapi'; +import type { InfoInterface } from '../info'; +import type { ServersInterface } from '../servers'; import type { ChannelsInterface } from '../channels'; import type { OperationsInterface } from '../operations'; +import type { MessagesInterface } from '../messages'; +import type { MessageInterface } from '../message'; +import type { ComponentsInterface } from '../components'; +import type { SecuritySchemesInterface } from '../security-schemes'; +import type { ExtensionsInterface } from '../extensions'; import type { v3 } from '../../spec-types'; @@ -26,12 +40,16 @@ export class AsyncAPIDocument extends BaseModel implements As return !!this._json.defaultContentType; } - info() { - return null as any; + info(): InfoInterface { + return this.createModel(Info, this._json.info, { pointer: '/info' }); } - servers() { - return null as any; + servers(): ServersInterface { + return new Servers( + Object.entries(this._json.servers || {}).map(([serverName, server]) => + this.createModel(Server, server, { id: serverName, pointer: `/servers/${tilde(serverName)}` }) + ) + ); } channels(): ChannelsInterface { @@ -50,23 +68,37 @@ export class AsyncAPIDocument extends BaseModel implements As ); } - messages() { - return null as any; + messages(): MessagesInterface { + const messages: MessageInterface[] = []; + const messagesData: any[] = []; + this.channels().forEach(channel => { + channel.messages().forEach(message => { + if (!messagesData.includes(message.json())) { + messagesData.push(message.json()); + messages.push(message); + } + }); + }); + return new Messages(messages); } schemas() { return null as any; } - securitySchemes() { - return null as any; + securitySchemes(): SecuritySchemesInterface { + return new SecuritySchemes( + Object.entries(this._json.components?.securitySchemes || {}).map(([securitySchemeName, securityScheme]) => + this.createModel(SecurityScheme, securityScheme as v3.SecuritySchemeObject, { id: securitySchemeName, pointer: `/components/securitySchemes/${securitySchemeName}` }) + ) + ); } - components() { - return null as any; + components(): ComponentsInterface { + return this.createModel(Components, this._json.components || {}, { pointer: '/components' }); } - extensions() { + extensions(): ExtensionsInterface { return extensions(this); } } diff --git a/src/models/v3/channel.ts b/src/models/v3/channel.ts index c1e65cbc6..16e2211d8 100644 --- a/src/models/v3/channel.ts +++ b/src/models/v3/channel.ts @@ -14,7 +14,7 @@ import type { BindingsInterface } from '../bindings'; import type { ChannelInterface } from '../channel'; import type { ChannelParametersInterface } from '../channel-parameters'; import type { ExtensionsInterface } from '../extensions'; -import type { ExternalDocumentationInterface } from '../external-docs'; +import type { ExternalDocumentationInterface } from '../external-documentation'; import type { MessagesInterface } from '../messages'; import type { OperationsInterface } from '../operations'; import type { OperationInterface } from '../operation'; diff --git a/src/models/v3/components.ts b/src/models/v3/components.ts new file mode 100644 index 000000000..b58501a79 --- /dev/null +++ b/src/models/v3/components.ts @@ -0,0 +1,154 @@ +import { BaseModel } from '../base'; +import { Collection } from '../collection'; + +import { Bindings } from './bindings'; +import { Binding } from './binding'; +import { Channel } from './channel'; +import { ChannelParameter } from './channel-parameter'; +import { CorrelationId } from './correlation-id'; +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 { extensions } from './mixins'; +import { Servers } from '../servers'; +import { Channels } from '../channels'; +import { Messages } from '../messages'; +import { Schemas } from '../schemas'; +import { ChannelParameters } from '../channel-parameters'; +import { ServerVariables } from '../server-variables'; +import { OperationTraits } from '../operation-traits'; +import { MessageTraits } from '../message-traits'; +import { SecuritySchemes } from '../security-schemes'; +import { CorrelationIds } from '../correlation-ids'; +import { Operations } from '../operations'; +import { Operation } from './operation'; +import { Message } from './message'; +import { ExternalDocumentations } from '../external-documentations'; +import { ExternalDocumentation } from './external-documentation'; +import { Tags } from '../tags'; +import { Tag } from './tag'; + +import { tilde } from '../../utils'; + +import type { BindingsInterface } from '../bindings'; +import type { ComponentsInterface } from '../components'; +import type { ExtensionsInterface } from '../extensions'; +import type { Constructor } from '../utils'; +import type { ServersInterface } from '../servers'; +import type { ChannelsInterface } from '../channels'; +import type { MessagesInterface } from '../messages'; +import type { SchemasInterface } from '../schemas'; +import type { ChannelParametersInterface } from '../channel-parameters'; +import type { ServerVariablesInterface } from '../server-variables'; +import type { OperationTraitsInterface } from '../operation-traits'; +import type { SecuritySchemesInterface } from '../security-schemes'; +import type { MessageTraitsInterface } from '../message-traits'; +import type { OperationsInterface } from '../operations'; +import type { ExternalDocumentationsInterface } from '../external-documentations'; +import type { TagsInterface } from '../tags'; + +import type { v3 } from '../../spec-types'; + +export class Components extends BaseModel implements ComponentsInterface { + servers(): ServersInterface { + return this.createCollection('servers', Servers, Server); + } + + channels(): ChannelsInterface { + return this.createCollection('channels', Channels, Channel); + } + + operations(): OperationsInterface { + return this.createCollection('operations', Operations, Operation); + } + + messages(): MessagesInterface { + return this.createCollection('messages', Messages, Message); + } + + schemas(): SchemasInterface { + return this.createCollection('schemas', Schemas, Schema); + } + + channelParameters(): ChannelParametersInterface { + return this.createCollection('parameters', ChannelParameters, ChannelParameter); + } + + serverVariables(): ServerVariablesInterface { + return this.createCollection('serverVariables', ServerVariables, ServerVariable); + } + + operationTraits(): OperationTraitsInterface { + return this.createCollection('operationTraits', OperationTraits, OperationTrait); + } + + messageTraits(): MessageTraitsInterface { + return this.createCollection('messageTraits', MessageTraits, MessageTrait); + } + + correlationIds(): CorrelationIds { + return this.createCollection('correlationIds', CorrelationIds, CorrelationId); + } + + securitySchemes(): SecuritySchemesInterface { + return this.createCollection('securitySchemes', SecuritySchemes, SecurityScheme); + } + + tags(): TagsInterface { + return this.createCollection('tags', Tags, Tag); + } + + externalDocs(): ExternalDocumentationsInterface { + return this.createCollection('externalDocs', ExternalDocumentations, ExternalDocumentation); + } + + serverBindings(): Record { + return this.createBindings('serverBindings'); + } + + channelBindings(): Record { + return this.createBindings('channelBindings'); + } + + operationBindings(): Record { + return this.createBindings('operationBindings'); + } + + messageBindings(): Record { + return this.createBindings('messageBindings'); + } + + extensions(): ExtensionsInterface { + return extensions(this); + } + + isEmpty(): boolean { + return Object.keys(this._json).length === 0; + } + + protected createCollection, T extends BaseModel>(itemsName: keyof v3.ComponentsObject, collectionModel: Constructor, itemModel: Constructor): M { + const collectionItems: T[] = []; + Object.entries(this._json[itemsName] || {}).forEach(([id, item]) => { + collectionItems.push(this.createModel(itemModel, item as any, { id, pointer: `/components/${itemsName}/${tilde(id)}` } as any)); + }); + return new collectionModel(collectionItems); + } + + protected createBindings(itemsName: 'serverBindings' | 'channelBindings' | 'operationBindings' | 'messageBindings'): Record { + return Object.entries(this._json[itemsName] || {}).reduce((bindings, [name, item]) => { + const bindingsData = item || {}; + const asyncapi = this.meta('asyncapi'); + const pointer = `components/${itemsName}/${name}`; + bindings[name] = new Bindings( + Object.entries(bindingsData).map(([protocol, binding]) => + this.createModel(Binding, binding, { protocol, pointer: `${pointer}/${protocol}` }) + ), + { originalData: bindingsData as any, asyncapi, pointer } + ); + return bindings; + }, {} as Record); + } +} diff --git a/src/models/v3/contact.ts b/src/models/v3/contact.ts new file mode 100644 index 000000000..69166bd18 --- /dev/null +++ b/src/models/v3/contact.ts @@ -0,0 +1,38 @@ +import { BaseModel } from '../base'; + +import { extensions } from './mixins'; + +import type { ContactInterface } from '../contact'; +import type { ExtensionsInterface } from '../extensions'; + +import type { v3 } from '../../spec-types'; + +export class Contact extends BaseModel implements ContactInterface { + hasName(): boolean { + return !!this._json.name; + } + + name(): string | undefined { + return this._json.name; + } + + hasUrl(): boolean { + return !!this._json.url; + } + + url(): string | undefined { + return this._json.url; + } + + hasEmail(): boolean { + return !!this._json.email; + } + + email(): string | undefined { + return this._json.email; + } + + extensions(): ExtensionsInterface { + return extensions(this); + } +} \ No newline at end of file diff --git a/src/models/v3/external-docs.ts b/src/models/v3/external-documentation.ts similarity index 97% rename from src/models/v3/external-docs.ts rename to src/models/v3/external-documentation.ts index edafb417c..27abd8aab 100644 --- a/src/models/v3/external-docs.ts +++ b/src/models/v3/external-documentation.ts @@ -2,7 +2,7 @@ import { BaseModel } from '../base'; import { hasDescription, description, extensions } from './mixins'; -import type { ExternalDocumentationInterface } from '../external-docs'; +import type { ExternalDocumentationInterface } from '../external-documentation'; import type { ExtensionsInterface } from '../extensions'; import type { v3 } from '../../spec-types'; diff --git a/src/models/v3/index.ts b/src/models/v3/index.ts index 4da27ddc5..6187a29e2 100644 --- a/src/models/v3/index.ts +++ b/src/models/v3/index.ts @@ -5,10 +5,14 @@ export { ChannelParameter as ChannelParameterV3 } from './channel-parameter'; export { ChannelParameters as ChannelParametersV3 } from '../channel-parameters'; export { Channel as ChannelV3 } from './channel'; export { Channels as ChannelsV3 } from '../channels'; +export { Components as ComponentsV3 } from './components'; +export { Contact as ContactV3 } from './contact'; export { CorrelationId as CorrelationIdV3 } from './correlation-id'; export { Extension as ExtensionV3 } from './extension'; export { Extensions as ExtensionsV3 } from '../extensions'; -export { ExternalDocumentation as ExternalDocumentationV3 } from './external-docs'; +export { ExternalDocumentation as ExternalDocumentationV3 } from './external-documentation'; +export { Info as InfoV3 } from './info'; +export { License as LicenseV3 } from './license'; export { MessageExample as MessageExampleV3 } from './message-example'; export { MessageExamples as MessageExamplesV3 } from '../message-examples'; export { MessageTrait as MessageTraitV3 } from './message-trait'; diff --git a/src/models/v3/info.ts b/src/models/v3/info.ts new file mode 100644 index 000000000..be5cfc68c --- /dev/null +++ b/src/models/v3/info.ts @@ -0,0 +1,82 @@ +import { BaseModel } from '../base'; +import { Contact } from './contact'; +import { License } from './license'; + +import { hasDescription, description, extensions, hasExternalDocs, externalDocs, tags } from './mixins'; + +import type { ContactInterface } from '../contact'; +import type { InfoInterface } from '../info'; +import type { ExtensionsInterface } from '../extensions'; +import type { ExternalDocumentationInterface } from '../external-documentation'; +import type { LicenseInterface } from '../license'; +import type { TagsInterface } from '../tags'; + +import type { v3 } from '../../spec-types'; + +export class Info extends BaseModel implements InfoInterface { + title(): string { + return this._json.title; + } + + version(): string { + return this._json.version; + } + + hasId(): boolean { + return !!this._meta.asyncapi.parsed.id; + } + + id(): string | undefined { + return this._meta.asyncapi.parsed.id; + } + + hasDescription(): boolean { + return hasDescription(this); + } + + description(): string | undefined { + return description(this); + } + + hasTermsOfService(): boolean { + return !!this._json.termsOfService; + } + + termsOfService(): string | undefined { + return this._json.termsOfService; + } + + hasContact(): boolean { + return Object.keys(this._json.contact || {}).length > 0; + } + + contact(): ContactInterface | undefined { + const contact = this._json.contact; + return contact && this.createModel(Contact, contact, { pointer: this.jsonPath('contact') }); + } + + hasLicense(): boolean { + return Object.keys(this._json.license || {}).length > 0; + } + + license(): LicenseInterface | undefined { + const license = this._json.license; + return license && this.createModel(License, license, { pointer: this.jsonPath('license') }); + } + + hasExternalDocs(): boolean { + return hasExternalDocs(this); + } + + externalDocs(): ExternalDocumentationInterface | undefined { + return externalDocs(this); + } + + tags(): TagsInterface { + return tags(this); + } + + extensions(): ExtensionsInterface { + return extensions(this); + } +} \ No newline at end of file diff --git a/src/models/v3/license.ts b/src/models/v3/license.ts new file mode 100644 index 000000000..8280080fc --- /dev/null +++ b/src/models/v3/license.ts @@ -0,0 +1,26 @@ +import { BaseModel } from '../base'; + +import { extensions } from './mixins'; + +import type { ExtensionsInterface } from '../extensions'; +import type { LicenseInterface } from '../license'; + +import type { v3 } from '../../spec-types'; + +export class License extends BaseModel implements LicenseInterface { + name(): string { + return this._json.name; + } + + hasUrl(): boolean { + return !!this._json.url; + } + + url(): string | undefined { + return this._json.url; + } + + extensions(): ExtensionsInterface { + return extensions(this); + } +} diff --git a/src/models/v3/message-trait.ts b/src/models/v3/message-trait.ts index eee94be89..180c90fe7 100644 --- a/src/models/v3/message-trait.ts +++ b/src/models/v3/message-trait.ts @@ -11,7 +11,7 @@ import { bindings, hasDescription, description, extensions, hasExternalDocs, ext import type { BindingsInterface } from '../bindings'; import type { CorrelationIdInterface } from '../correlation-id'; import type { ExtensionsInterface } from '../extensions'; -import type { ExternalDocumentationInterface } from '../external-docs'; +import type { ExternalDocumentationInterface } from '../external-documentation'; import type { MessageExamplesInterface } from '../message-examples'; import type { MessageTraitInterface } from '../message-trait'; import type { SchemaInterface } from '../schema'; diff --git a/src/models/v3/mixins.ts b/src/models/v3/mixins.ts index 605cafed2..9c45ef614 100644 --- a/src/models/v3/mixins.ts +++ b/src/models/v3/mixins.ts @@ -2,7 +2,7 @@ import { Bindings } from './bindings'; import { Binding } from './binding'; import { Extensions } from '../extensions'; import { Extension } from './extension'; -import { ExternalDocumentation } from './external-docs'; +import { ExternalDocumentation } from './external-documentation'; import { Tags } from '../tags'; import { Tag } from './tag'; @@ -13,7 +13,7 @@ import type { BaseModel } from '../base'; import type { BindingsInterface } from '../bindings'; import type { ExtensionsInterface } from '../extensions'; import type { ExtensionInterface } from '../extension'; -import type { ExternalDocumentationInterface } from '../external-docs'; +import type { ExternalDocumentationInterface } from '../external-documentation'; import type { TagsInterface } from '../tags'; import type { v3 } from '../../spec-types'; diff --git a/src/models/v3/operation-trait.ts b/src/models/v3/operation-trait.ts index a985182ad..1f5e158d0 100644 --- a/src/models/v3/operation-trait.ts +++ b/src/models/v3/operation-trait.ts @@ -9,7 +9,7 @@ import { bindings, hasDescription, description, extensions, hasExternalDocs, ext import type { BindingsInterface } from '../bindings'; import type { ExtensionsInterface } from '../extensions'; -import type { ExternalDocumentationInterface } from '../external-docs'; +import type { ExternalDocumentationInterface } from '../external-documentation'; import type { ChannelsInterface } from '../channels'; import type { OperationAction } from '../operation'; import type { OperationTraitInterface } from '../operation-trait'; diff --git a/src/models/v3/schema.ts b/src/models/v3/schema.ts index 889f254d7..b42d38c7a 100644 --- a/src/models/v3/schema.ts +++ b/src/models/v3/schema.ts @@ -6,7 +6,7 @@ import { retrievePossibleRef, hasRef } from '../../utils'; import type { ModelMetadata } from '../base'; import type { ExtensionsInterface } from '../extensions'; -import type { ExternalDocumentationInterface } from '../external-docs'; +import type { ExternalDocumentationInterface } from '../external-documentation'; import type { SchemaInterface } from '../schema'; import type { v3 } from '../../spec-types'; diff --git a/src/models/v3/tag.ts b/src/models/v3/tag.ts index 84ea47133..c3dec38a7 100644 --- a/src/models/v3/tag.ts +++ b/src/models/v3/tag.ts @@ -3,7 +3,7 @@ import { BaseModel } from '../base'; import { hasDescription, description, extensions, hasExternalDocs, externalDocs } from './mixins'; import type { ExtensionsInterface } from '../extensions'; -import type{ ExternalDocumentationInterface } from '../external-docs'; +import type{ ExternalDocumentationInterface } from '../external-documentation'; import type { TagInterface } from '../tag'; import type { v3 } from '../../spec-types'; diff --git a/src/spec-types/v3.ts b/src/spec-types/v3.ts index 250046dd1..6ce81c9cb 100644 --- a/src/spec-types/v3.ts +++ b/src/spec-types/v3.ts @@ -8,12 +8,35 @@ export interface AsyncAPIObject extends SpecificationExtensions { asyncapi: AsyncAPIVersion; id?: Identifier; defaultContentType?: DefaultContentType; + info: InfoObject; servers?: ServersObject; channels?: ChannelsObject; operations?: OperationsObject; components?: ComponentsObject; } +export interface InfoObject extends SpecificationExtensions { + title: string; + version: string; + description?: string; + termsOfService?: string; + contact?: ContactObject; + license?: LicenseObject; + tags?: TagsObject; + externalDocs?: ExternalDocumentationObject; +} + +export interface ContactObject extends SpecificationExtensions { + name?: string; + url?: string; + email?: string; +} + +export interface LicenseObject extends SpecificationExtensions { + name: string; + url?: string; +} + export type ServersObject = Record; export interface ServerObject extends SpecificationExtensions { @@ -200,8 +223,9 @@ export interface ExternalDocumentationObject extends SpecificationExtensions { } export interface ComponentsObject extends SpecificationExtensions { - channels?: Record; servers?: Record; + channels?: Record; + operations?: Record; schemas?: Record; messages?: Record; securitySchemes?: Record; @@ -210,6 +234,8 @@ export interface ComponentsObject extends SpecificationExtensions { correlationIds?: Record; operationTraits?: Record; messageTraits?: Record; + tags?: Record; + externalDocs?: Record; serverBindings?: Record; channelBindings?: Record; operationBindings?: Record; diff --git a/test/models/v2/components.spec.ts b/test/models/v2/components.spec.ts index 1fc09b4be..c9d624a99 100644 --- a/test/models/v2/components.spec.ts +++ b/test/models/v2/components.spec.ts @@ -23,12 +23,12 @@ import { OperationTraits } from '../../../src/models/operation-traits'; import { MessageTraits } from '../../../src/models/message-traits'; import { CorrelationIds } from '../../../src/models/correlation-ids'; import { SecuritySchemes } from '../../../src/models/security-schemes'; -import { Operation } from '../../../src/models/v2/operation'; import { Operations } from '../../../src/models/operations'; -import { OperationAction } from '../../../src/models/operation'; +import { Tags } from '../../../src/models/tags'; +import { ExternalDocumentations } from '../../../src/models/external-documentations'; import { serializeInput, assertExtensions } from './utils'; + import type { v2 } from '../../../src/spec-types'; -import { ChannelObject } from '../../../src/spec-types/v2'; describe('Components model', function() { describe('.servers()', function() { @@ -55,7 +55,7 @@ describe('Components model', function() { const items = d.channels(); expect(items).toBeInstanceOf(Channels); expect(items.all()).toEqual([ - new Channel(doc.channels?.channel as ChannelObject, {id: 'channel', address: '', pointer: '/components/channels/channel'} as ModelMetadata & { id: string, address: string } | undefined) + new Channel(doc.channels?.channel as v2.ChannelObject, {id: 'channel', pointer: '/components/channels/channel'} as ModelMetadata & { id: string, address: string } | undefined) ]); }); @@ -68,6 +68,16 @@ describe('Components model', function() { }); }); + describe('.operations()', function() { + it('should return Operations with empty collection', function() { + const doc = {}; + const d = new Components(doc); + const operations = d.operations(); + expect(operations).toBeInstanceOf(Operations); + expect(operations.all()).toHaveLength(0); + }); + }); + describe('.messages()', function() { it('should return Messages with Message Object', function() { const doc = serializeInput({ messages: { message: {} } }); @@ -180,28 +190,6 @@ describe('Components model', function() { }); }); - describe('.operations()', function() { - it('should return Operations with Operation Object', function() { - const doc = { channels: { channel: { publish: {} } } }; - const d = new Components(doc); - const expectedOperations: Operation[] = [ - new Operation({}, {action: 'publish', id: 'channel_publish', pointer: '/components/channels/channel/publish'} as ModelMetadata & { id: string, action: OperationAction }) - ]; - - const operations = d.operations(); - expect(operations).toBeInstanceOf(Operations); - expect(operations.all()).toEqual(expectedOperations); - }); - - it('should return Operations with empty operation objects when operations are not defined in channels', function() { - const doc = { channels: { channel: {} } }; - const d = new Components(doc); - const operations = d.operations(); - expect(operations).toBeInstanceOf(Operations); - expect(operations.all()).toHaveLength(0); - }); - }); - describe('.securitySchemes()', function() { it('should return SecuritySchemes with SecurityScheme Object', function() { const doc = serializeInput({ securitySchemes: { scheme: {} } }); @@ -218,6 +206,26 @@ describe('Components model', function() { }); }); + describe('.tags()', function() { + it('should return Tags with empty collection', function() { + const doc = {}; + const d = new Components(doc); + const tags = d.tags(); + expect(tags).toBeInstanceOf(Tags); + expect(tags.all()).toHaveLength(0); + }); + }); + + describe('.externalDocs()', function() { + it('should return ExternalDocumentations with empty collection', function() { + const doc = {}; + const d = new Components(doc); + const externalDocs = d.externalDocs(); + expect(externalDocs).toBeInstanceOf(ExternalDocumentations); + expect(externalDocs.all()).toHaveLength(0); + }); + }); + describe('.serverBindings()', function() { it('should return map of serverBindings', function() { const doc = serializeInput({ serverBindings: { binding: {} } }); diff --git a/test/models/v2/external-docs.spec.ts b/test/models/v2/external-documentation.spec.ts similarity index 96% rename from test/models/v2/external-docs.spec.ts rename to test/models/v2/external-documentation.spec.ts index e936f3842..986460541 100644 --- a/test/models/v2/external-docs.spec.ts +++ b/test/models/v2/external-documentation.spec.ts @@ -1,4 +1,4 @@ -import { ExternalDocumentation } from '../../../src/models/v2/external-docs'; +import { ExternalDocumentation } from '../../../src/models/v2/external-documentation'; import { serializeInput, assertDescription, assertExtensions } from './utils'; diff --git a/test/models/v2/info.spec.ts b/test/models/v2/info.spec.ts index 49c1d8c73..f986353ea 100644 --- a/test/models/v2/info.spec.ts +++ b/test/models/v2/info.spec.ts @@ -1,7 +1,7 @@ import { Info } from '../../../src/models/v2/info'; import { Contact } from '../../../src/models/v2/contact'; import { License } from '../../../src/models/v2/license'; -import { ExternalDocumentation } from '../../../src/models/v2/external-docs'; +import { ExternalDocumentation } from '../../../src/models/v2/external-documentation'; import { Tags } from '../../../src/models/tags'; import { Tag } from '../../../src/models/v2/tag'; import { createDetailedAsyncAPI } from '../../../src/utils'; diff --git a/test/models/v3/asyncapi.spec.ts b/test/models/v3/asyncapi.spec.ts new file mode 100644 index 000000000..40ec75337 --- /dev/null +++ b/test/models/v3/asyncapi.spec.ts @@ -0,0 +1,165 @@ +import { AsyncAPIDocument } from '../../../src/models/v3/asyncapi'; +import { Channels } from '../../../src/models/channels'; +import { Components } from '../../../src/models/v3/components'; +import { Info } from '../../../src/models/v3/info'; +import { Messages } from '../../../src/models/messages'; +import { Operations } from '../../../src/models/operations'; +import { SecuritySchemes } from '../../../src/models/security-schemes'; +import { Servers } from '../../../src/models/servers'; + +import { serializeInput, assertExtensions } from './utils'; + +import type { v3 } from '../../../src/spec-types'; + +describe('AsyncAPIDocument model', function() { + describe('.version()', function() { + it('should return the value', function() { + const doc = serializeInput({ asyncapi: '2.0.0' }); + const d = new AsyncAPIDocument(doc); + expect(d.version()).toEqual(doc.asyncapi); + }); + + it('should return undefined when there is no value', function() { + const doc = serializeInput({}); + const d = new AsyncAPIDocument(doc); + expect(d.version()).toBeUndefined(); + }); + }); + + describe('.hasDefaultContentType()', function() { + it('should return true when there is a value', function() { + const doc = serializeInput({ defaultContentType: '...' }); + const d = new AsyncAPIDocument(doc); + expect(d.hasDefaultContentType()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = serializeInput({}); + const d = new AsyncAPIDocument(doc); + expect(d.hasDefaultContentType()).toEqual(false); + }); + }); + + describe('.defaultContentType()', function() { + it('should return the value', function() { + const doc = serializeInput({ defaultContentType: '...' }); + const d = new AsyncAPIDocument(doc); + expect(d.defaultContentType()).toEqual(doc.defaultContentType); + }); + + it('should return undefined when there is no value', function() { + const doc = serializeInput({}); + const d = new AsyncAPIDocument(doc); + expect(d.defaultContentType()).toBeUndefined(); + }); + }); + + describe('.info()', function() { + it('should return an Info object', function() { + const doc = serializeInput({ info: {} }); + const d = new AsyncAPIDocument(doc); + expect(d.info()).toBeInstanceOf(Info); + }); + }); + + describe('.servers()', function() { + it('should return a collection of servers', function() { + const doc = serializeInput({ servers: { development: {} } }); + const d = new AsyncAPIDocument(doc); + expect(d.servers()).toBeInstanceOf(Servers); + expect(d.servers()).toHaveLength(1); + expect(d.servers().all()[0].id()).toEqual('development'); + }); + + it('should return a collection of servers even if servers are not defined', function() { + const doc = serializeInput({}); + const d = new AsyncAPIDocument(doc); + expect(d.servers()).toBeInstanceOf(Servers); + }); + }); + + describe('.channels()', function() { + it('should return a collection of channels', function() { + const doc = serializeInput({ channels: { userSignup: { address: 'user/signup' }, userLogout: { address: 'user/logout' } } }); + const d = new AsyncAPIDocument(doc); + expect(d.channels()).toBeInstanceOf(Channels); + expect(d.channels()).toHaveLength(2); + expect(d.channels().all()[0].address()).toEqual('user/signup'); + expect(d.channels().all()[1].address()).toEqual('user/logout'); + }); + + it('should return a collection of channels even if channels are not defined', function() { + const doc = serializeInput({}); + const d = new AsyncAPIDocument(doc); + expect(d.channels()).toBeInstanceOf(Channels); + }); + }); + + describe('.operations()', function() { + it('should return a collection of operations', function() { + const doc = serializeInput({ operations: { userSignup: {}, userLogout: {} } }); + const d = new AsyncAPIDocument(doc); + expect(d.operations()).toBeInstanceOf(Operations); + expect(d.operations()).toHaveLength(2); + }); + + it('should return a collection of operations even if operations are not defined', function() { + const doc = serializeInput({}); + const d = new AsyncAPIDocument(doc); + expect(d.operations()).toBeInstanceOf(Operations); + }); + }); + + describe('.messages()', function() { + it('should return a collection of messages', function() { + const duplicatedMessage = {}; + const doc = serializeInput({ channels: { userSignup: { address: 'user/signup', messages: { someMessage1: {}, someMessage2: duplicatedMessage } }, userLogout: { address: 'user/logout', messages: { someMessage3: duplicatedMessage } } } }); + const d = new AsyncAPIDocument(doc); + expect(d.messages()).toBeInstanceOf(Messages); + expect(d.messages()).toHaveLength(2); + }); + + it('should return a collection of messages even if messages are not defined', function() { + const doc = serializeInput({}); + const d = new AsyncAPIDocument(doc); + expect(d.messages()).toBeInstanceOf(Messages); + }); + }); + + describe('.schemas()', function() { + it.todo('should return a collection of schemas'); + }); + + describe('.securitySchemes()', function() { + it('should return a collection of securitySchemes', function() { + const doc = serializeInput({ components: { securitySchemes: { security1: { type: 'X509' }, security2: { type: 'apiKey' } } } }); + const d = new AsyncAPIDocument(doc); + expect(d.securitySchemes()).toBeInstanceOf(SecuritySchemes); + expect(d.securitySchemes()).toHaveLength(2); + }); + + it('should return a collection of securitySchemes even if securitySchemes are not defined', function() { + const doc = serializeInput({}); + const d = new AsyncAPIDocument(doc); + expect(d.securitySchemes()).toBeInstanceOf(SecuritySchemes); + }); + }); + + describe('.components()', function() { + it('should return a components model', function() { + const doc = serializeInput({ components: {} }); + const d = new AsyncAPIDocument(doc); + expect(d.components()).toBeInstanceOf(Components); + }); + + it('should return a components model even if components are not defined', function() { + const doc = serializeInput({}); + const d = new AsyncAPIDocument(doc); + expect(d.components()).toBeInstanceOf(Components); + }); + }); + + describe('mixins', function() { + assertExtensions(AsyncAPIDocument); + }); +}); diff --git a/test/models/v3/components.spec.ts b/test/models/v3/components.spec.ts new file mode 100644 index 000000000..48c06d7d6 --- /dev/null +++ b/test/models/v3/components.spec.ts @@ -0,0 +1,343 @@ +import { Components } from '../../../src/models/v3/components'; +import { Bindings } from '../../../src/models/v3/bindings'; +import { Channel } from '../../../src/models/v3/channel'; +import { ChannelParameter } from '../../../src/models/v3/channel-parameter'; +import { CorrelationId } from '../../../src/models/v3/correlation-id'; +import { OperationTrait } from '../../../src/models/v3/operation-trait'; +import { Message } from '../../../src/models/v3/message'; +import { MessageTrait } from '../../../src/models/v3/message-trait'; +import { Schema } from '../../../src/models/v3/schema'; +import { Server } from '../../../src/models/v3/server'; +import { ServerVariable } from '../../../src/models/v3/server-variable'; +import { SecurityScheme } from '../../../src/models/v3/security-scheme'; +import { BaseModel, ModelMetadata } from '../../../src/models'; +import { Servers } from '../../../src/models/servers'; +import { Channels } from '../../../src/models/channels'; +import { Messages } from '../../../src/models/messages'; +import { Collection } from '../../../src/models/collection'; +import { Constructor } from '../../../src/models/utils'; +import { Schemas } from '../../../src/models/schemas'; +import { ChannelParameters } from '../../../src/models/channel-parameters'; +import { ServerVariables } from '../../../src/models/server-variables'; +import { OperationTraits } from '../../../src/models/operation-traits'; +import { MessageTraits } from '../../../src/models/message-traits'; +import { CorrelationIds } from '../../../src/models/correlation-ids'; +import { SecuritySchemes } from '../../../src/models/security-schemes'; +import { Operation } from '../../../src/models/v3/operation'; +import { Operations } from '../../../src/models/operations'; +import { Tags } from '../../../src/models/tags'; +import { Tag } from '../../../src/models/v3/tag'; +import { ExternalDocumentations } from '../../../src/models/external-documentations'; +import { ExternalDocumentation } from '../../../src/models/v3/external-documentation'; +import { serializeInput, assertExtensions } from './utils'; + +import type { v3 } from '../../../src/spec-types'; + +describe('Components model', function() { + describe('.servers()', function() { + it('should return Servers with Server Object', function() { + const doc = serializeInput({ servers: { server: {} } }); + + const d = new Components(doc); + testCollection(doc, d.servers(), 'servers', Servers, Server); + }); + + it('should return Servers with empty server objects when servers are not defined', function() { + const doc = serializeInput({}); + const d = new Components(doc); + const items = d.servers(); + expect(items).toBeInstanceOf(Servers); + expect(items.all()).toHaveLength(0); + }); + }); + + describe('.channels()', function() { + it('should return Channels with Channel Object', function() { + const doc = serializeInput({ channels: { channel: {} } }); + const d = new Components(doc); + const items = d.channels(); + expect(items).toBeInstanceOf(Channels); + expect(items.all()).toEqual([ + new Channel(doc.channels?.channel as v3.ChannelObject, {id: 'channel', pointer: '/components/channels/channel'} as ModelMetadata & { id: string, address: string } | undefined) + ]); + }); + + it('should return Channels with empty channel objects when channels are not defined', function() { + const doc = serializeInput({}); + const d = new Components(doc); + const items = d.channels(); + expect(items).toBeInstanceOf(Channels); + expect(items.all()).toHaveLength(0); + }); + }); + + describe('.operations()', function() { + it('should return Operations with Operation Object', function() { + const doc = serializeInput({ operations: { someOperation: {} } }); + const d = new Components(doc); + testCollection(doc, d.operations(), 'operations', Operations, Operation); + }); + + it('should return Operations with empty operations objects when operations are not defined', function() { + const doc = serializeInput({}); + const d = new Components(doc); + const items = d.operations(); + expect(items).toBeInstanceOf(Operations); + expect(items.all()).toHaveLength(0); + }); + }); + + describe('.messages()', function() { + it('should return Messages with Message Object', function() { + const doc = serializeInput({ messages: { message: {} } }); + const d = new Components(doc); + testCollection(doc, d.messages(), 'messages', Messages, Message); + }); + + it('should return Messages with empty message objects when messages are not defined', function() { + const doc = serializeInput({}); + const d = new Components(doc); + const items = d.messages(); + expect(items).toBeInstanceOf(Messages); + expect(items.all()).toHaveLength(0); + }); + }); + + describe('.schemas()', function() { + it('should return Schemas with Schema Object', function() { + const doc = serializeInput({ schemas: { schema: {} } }); + const d = new Components(doc); + testCollection(doc, d.schemas(), 'schemas', Schemas, Schema); + }); + + it('should return Schemas with empty schema objects when schemas are not defined', function() { + const doc = serializeInput({}); + const d = new Components(doc); + const items = d.schemas(); + expect(items).toBeInstanceOf(Schemas); + expect(items.all()).toHaveLength(0); + }); + }); + + describe('.channelParameters()', function() { + it('should return ChannelParameters with ChannelParameter Object', function() { + const doc = serializeInput({ parameters: { parameter: {} } }); + const d = new Components(doc); + testCollection(doc, d.channelParameters(), 'parameters', ChannelParameters, ChannelParameter); + }); + + it('should return Schemas with empty schema objects when schemas are not defined', function() { + const doc = serializeInput({}); + const d = new Components(doc); + const items = d.channelParameters(); + expect(items).toBeInstanceOf(ChannelParameters); + expect(items.all()).toHaveLength(0); + }); + }); + + describe('.serverVariables()', function() { + it('should return ServerVariables with ServerVariable Object', function() { + const doc = serializeInput({ serverVariables: { variable: {} } }); + const d = new Components(doc); + testCollection(doc, d.serverVariables(), 'serverVariables', ServerVariables, ServerVariable); + }); + + it('should return ServerVariables with empty serverVariable objects when serverVariables are not defined', function() { + const doc = serializeInput({}); + const d = new Components(doc); + const items = d.serverVariables(); + expect(items).toBeInstanceOf(ServerVariables); + expect(items.all()).toHaveLength(0); + }); + }); + + describe('.operationTraits()', function() { + it('should return OperationTraits with OperationTrait Object', function() { + const doc = serializeInput({ operationTraits: { trait: {} } }); + const d = new Components(doc); + testCollection(doc, d.operationTraits(), 'operationTraits', OperationTraits, OperationTrait); + }); + + it('should return OperationTraits with empty operationTrait objects when operationTraits are not defined', function() { + const doc = serializeInput({}); + const d = new Components(doc); + const items = d.operationTraits(); + expect(items).toBeInstanceOf(OperationTraits); + expect(items.all()).toHaveLength(0); + }); + }); + + describe('.messageTraits()', function() { + it('should return MessageTraits with MessageTrait Object', function() { + const doc = serializeInput({ messageTraits: { trait: {} } }); + const d = new Components(doc); + testCollection(doc, d.messageTraits(), 'messageTraits', MessageTraits, MessageTrait); + }); + + it('should return MessageTraits with empty messageTrait objects when messageTraits are not defined', function() { + const doc = serializeInput({}); + const d = new Components(doc); + const items = d.messageTraits(); + expect(items).toBeInstanceOf(MessageTraits); + expect(items.all()).toHaveLength(0); + }); + }); + + describe('.correlationIds()', function() { + it('should return CorrelationIds with CorrelationId Object', function() { + const doc = serializeInput({ correlationIds: { id: {} } }); + const d = new Components(doc); + testCollection(doc, d.correlationIds(), 'correlationIds', CorrelationIds, CorrelationId); + }); + + it('should return CorrelationIds with empty correlationId objects when correlationIds are not defined', function() { + const doc = serializeInput({}); + const d = new Components(doc); + const items = d.correlationIds(); + expect(items).toBeInstanceOf(CorrelationIds); + expect(items.all()).toHaveLength(0); + }); + }); + + describe('.securitySchemes()', function() { + it('should return SecuritySchemes with SecurityScheme Object', function() { + const doc = serializeInput({ securitySchemes: { scheme: {} } }); + const d = new Components(doc); + testCollection(doc, d.securitySchemes(), 'securitySchemes', SecuritySchemes, SecurityScheme); + }); + + it('should return SecuritySchemes with empty securityScheme objects when securitySchemes are not defined', function() { + const doc = serializeInput({}); + const d = new Components(doc); + const items = d.securitySchemes(); + expect(items).toBeInstanceOf(SecuritySchemes); + expect(items.all()).toHaveLength(0); + }); + }); + + describe('.tags()', function() { + it('should return Tags with Tag Object', function() { + const doc = serializeInput({ tags: { tag: { name: 'tag' } } }); + const d = new Components(doc); + testCollection(doc, d.tags(), 'tags', Tags, Tag); + }); + + it('should return Tags with empty tag objects when tags are not defined', function() { + const doc = {}; + const d = new Components(doc); + const tags = d.tags(); + expect(tags).toBeInstanceOf(Tags); + expect(tags.all()).toHaveLength(0); + }); + }); + + describe('.externalDocs()', function() { + it('should return ExternalDocumentations with ExternalDocumentation Object', function() { + const doc = serializeInput({ externalDocs: { someDoc: {} } }); + const d = new Components(doc); + testCollection(doc, d.externalDocs(), 'externalDocs', ExternalDocumentations, ExternalDocumentation); + }); + + it('should return ExternalDocumentations with empty externalDocs objects when externalDocs are not defined', function() { + const doc = {}; + const d = new Components(doc); + const externalDocs = d.externalDocs(); + expect(externalDocs).toBeInstanceOf(ExternalDocumentations); + expect(externalDocs.all()).toHaveLength(0); + }); + }); + + describe('.serverBindings()', function() { + it('should return map of serverBindings', function() { + const doc = serializeInput({ serverBindings: { binding: {} } }); + const d = new Components(doc); + expect(typeof d.serverBindings()).toEqual('object'); + expect(Object.keys(d.serverBindings())).toHaveLength(1); + expect(d.serverBindings()['binding']).toBeInstanceOf(Bindings); + }); + + it('should return empty map when serverBindings are not defined', function() { + const doc = serializeInput({}); + 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 = serializeInput({ channelBindings: { binding: {} } }); + const d = new Components(doc); + expect(typeof d.channelBindings()).toEqual('object'); + expect(Object.keys(d.channelBindings())).toHaveLength(1); + expect(d.channelBindings()['binding']).toBeInstanceOf(Bindings); + }); + + it('should return empty map when channelBindings are not defined', function() { + const doc = serializeInput({}); + 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 = serializeInput({ operationBindings: { binding: {} } }); + const d = new Components(doc); + expect(typeof d.operationBindings()).toEqual('object'); + expect(Object.keys(d.operationBindings())).toHaveLength(1); + expect(d.operationBindings()['binding']).toBeInstanceOf(Bindings); + }); + + it('should return empty map when operationBindings are not defined', function() { + const doc = serializeInput({}); + 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 = serializeInput({ messageBindings: { binding: {} } }); + const d = new Components(doc); + expect(typeof d.messageBindings()).toEqual('object'); + expect(Object.keys(d.messageBindings())).toHaveLength(1); + expect(d.messageBindings()['binding']).toBeInstanceOf(Bindings); + }); + + it('should return empty map when messageBindings are not defined', function() { + const doc = serializeInput({}); + const d = new Components(doc); + expect(typeof d.messageBindings()).toEqual('object'); + expect(Object.keys(d.messageBindings())).toHaveLength(0); + }); + }); + + describe('.isEmpty()', function() { + it('should return true if _json is empty', function() { + const d = new Components({}); + expect(d.isEmpty()).toBeTruthy(); + }); + + it('should return false if _json is not empty', function() { + const d = new Components({ schemas: { test: {} } }); + expect(d.isEmpty()).toBeFalsy(); + }); + }); + + describe('mixins', function() { + assertExtensions(Components); + }); +}); + +function testCollection, T extends BaseModel>(doc: any, items: M, componentName: string, collectionModel: Constructor, itemModel: Constructor) { + expect(items).toBeInstanceOf(collectionModel); + const expectedItems: T[] = []; + Object.entries((doc[componentName] as M)).forEach(([itemName, item]) => { + expectedItems.push(new itemModel(item, {id: itemName, pointer: `/components/${componentName}/${itemName}`})); + }); + + expect(items.all()).toEqual(expectedItems); +} \ No newline at end of file diff --git a/test/models/v3/contact.spec.ts b/test/models/v3/contact.spec.ts new file mode 100644 index 000000000..99f6026ca --- /dev/null +++ b/test/models/v3/contact.spec.ts @@ -0,0 +1,95 @@ +import { Contact } from '../../../src/models/v3/contact'; + +import { serializeInput, assertExtensions } from './utils'; + +import type { v3 } from '../../../src/spec-types'; + +describe('Contact model', function() { + describe('.hasName()', function() { + it('should return true when there is a value', function() { + const doc = serializeInput({ name: 'LeChuck' }); + const d = new Contact(doc); + expect(d.hasName()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = serializeInput({}); + const d = new Contact(doc); + expect(d.hasName()).toEqual(false); + }); + }); + + describe('.name()', function() { + it('should return the value', function() { + const doc = serializeInput({ name: 'LeChuck' }); + const d = new Contact(doc); + expect(d.name()).toEqual(doc.name); + }); + + it('should return undefined when there is no value', function() { + const doc = serializeInput({}); + const d = new Contact(doc); + expect(d.name()).toBeUndefined(); + }); + }); + + describe('.hasUrl()', function() { + it('should return true when there is a value', function() { + const doc = serializeInput({ url: 'https://example.com' }); + const d = new Contact(doc); + expect(d.hasUrl()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = serializeInput({}); + const d = new Contact(doc); + expect(d.hasUrl()).toEqual(false); + }); + }); + + describe('.url()', function() { + it('should return the value', function() { + const doc = serializeInput({ url: 'https://example.com' }); + const d = new Contact(doc); + expect(d.url()).toEqual(doc.url); + }); + + it('should return undefined when there is no value', function() { + const doc = serializeInput({}); + const d = new Contact(doc); + expect(d.url()).toBeUndefined(); + }); + }); + + describe('.hasEmail()', function() { + it('should return true when there is a value', function() { + const doc = serializeInput({ email: 'lechuck@example.com' }); + const d = new Contact(doc); + expect(d.hasEmail()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = serializeInput({}); + const d = new Contact(doc); + expect(d.hasEmail()).toEqual(false); + }); + }); + + describe('.email()', function() { + it('should return the value', function() { + const doc = serializeInput({ email: 'lechuck@example.com' }); + const d = new Contact(doc); + expect(d.email()).toEqual(doc.email); + }); + + it('should return undefined when there is no value', function() { + const doc = serializeInput({}); + const d = new Contact(doc); + expect(d.email()).toBeUndefined(); + }); + }); + + describe('mixins', function() { + assertExtensions(Contact); + }); +}); diff --git a/test/models/v3/external-docs.spec.ts b/test/models/v3/external-documentation.spec.ts similarity index 96% rename from test/models/v3/external-docs.spec.ts rename to test/models/v3/external-documentation.spec.ts index c4b2025cf..5bf485e0d 100644 --- a/test/models/v3/external-docs.spec.ts +++ b/test/models/v3/external-documentation.spec.ts @@ -1,4 +1,4 @@ -import { ExternalDocumentation } from '../../../src/models/v3/external-docs'; +import { ExternalDocumentation } from '../../../src/models/v3/external-documentation'; import { serializeInput, assertDescription, assertExtensions } from './utils'; diff --git a/test/models/v3/info.spec.ts b/test/models/v3/info.spec.ts new file mode 100644 index 000000000..2453bb3f7 --- /dev/null +++ b/test/models/v3/info.spec.ts @@ -0,0 +1,149 @@ +import { Info } from '../../../src/models/v3/info'; +import { Contact } from '../../../src/models/v3/contact'; +import { License } from '../../../src/models/v3/license'; +import { createDetailedAsyncAPI } from '../../../src/utils'; + +import { serializeInput, assertDescription, assertExtensions, assertExternalDocumentation, assertTags } from './utils'; + +import type { v3 } from '../../../src/spec-types'; + +describe('Info model', function() { + describe('.title()', function() { + it('should return the value', function() { + const doc = serializeInput({ title: 'Example API' }); + const d = new Info(doc); + expect(d.title()).toEqual(doc.title); + }); + }); + + describe('.version()', function() { + it('should return the value', function() { + const doc = serializeInput({ version: '1.0.0' }); + const d = new Info(doc); + expect(d.version()).toEqual(doc.version); + }); + }); + + describe('.hasId()', function() { + it('should return true when there is a value', function() { + const doc = { asyncapi: '2.0.0', id: 'someId' }; + const asyncapi = createDetailedAsyncAPI(doc, doc as any); + const d = new Info(serializeInput({}), { asyncapi, pointer: '/info' }); + expect(d.hasId()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = { asyncapi: '2.0.0' }; + const asyncapi = createDetailedAsyncAPI(doc, doc as any); + const d = new Info(serializeInput({}), { asyncapi, pointer: '/info' }); + expect(d.hasId()).toEqual(false); + }); + }); + + describe('.id()', function() { + it('should return the value', function() { + const doc = { asyncapi: '2.0.0', id: 'someId' }; + const asyncapi = createDetailedAsyncAPI(doc, doc as any); + const d = new Info(serializeInput({}), { asyncapi, pointer: '/info' }); + expect(d.id()).toEqual(doc.id); + }); + + it('should return undefined when there is no value', function() { + const doc = { asyncapi: '2.0.0' }; + const asyncapi = createDetailedAsyncAPI(doc, doc as any); + const d = new Info(serializeInput({}), { asyncapi, pointer: '/info' }); + expect(d.id()).toEqual(undefined); + }); + }); + + describe('.hasTermsOfService()', function() { + it('should return true when there is a value', function() { + const doc = serializeInput({ termsOfService: 'These are the terms of service' }); + const d = new Info(doc); + expect(d.hasTermsOfService()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = serializeInput({}); + const d = new Info(doc); + expect(d.hasTermsOfService()).toEqual(false); + }); + }); + + describe('.termsOfService()', function() { + it('should return the value', function() { + const doc = serializeInput({ termsOfService: 'These are the terms of service' }); + const d = new Info(doc); + expect(d.termsOfService()).toEqual(doc.termsOfService); + }); + + it('should return undefined when there is no value', function() { + const doc = serializeInput({}); + const d = new Info(doc); + expect(d.termsOfService()).toBeUndefined(); + }); + }); + + describe('.hasContact()', function() { + it('should return true when there is a value', function() { + const doc = serializeInput({ contact: { name: 'LeChuck' } }); + const d = new Info(doc); + expect(d.hasContact()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = serializeInput({}); + const d = new Info(doc); + expect(d.hasContact()).toEqual(false); + }); + }); + + describe('.contact()', function() { + it('should return a Contact object', function() { + const doc = serializeInput({ contact: { name: 'LeChuck' } }); + const d = new Info(doc); + expect(d.contact()).toBeInstanceOf(Contact); + }); + + it('should return undefined when there is no value', function() { + const doc = serializeInput({}); + const d = new Info(doc); + expect(d.contact()).toBeUndefined(); + }); + }); + + describe('.hasLicense()', function() { + it('should return true when there is a value', function() { + const doc = serializeInput({ license: { name: 'Apache 2.0' } }); + const d = new Info(doc); + expect(d.hasLicense()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = serializeInput({}); + const d = new Info(doc); + expect(d.hasLicense()).toEqual(false); + }); + }); + + describe('.license()', function() { + it('should return a License object', function() { + const doc = serializeInput({ license: { name: 'Apache 2.0' } }); + const d = new Info(doc); + expect(d.license()).toBeInstanceOf(License); + }); + + it('should return undefined when there is no value', function() { + const doc = serializeInput({}); + const d = new Info(doc); + expect(d.license()).toBeUndefined(); + }); + }); + + describe('mixins', function() { + assertDescription(Info); + assertExtensions(Info); + assertExternalDocumentation(Info); + assertTags(Info); + }); +}); diff --git a/test/models/v3/license.spec.ts b/test/models/v3/license.spec.ts new file mode 100644 index 000000000..f8b12f804 --- /dev/null +++ b/test/models/v3/license.spec.ts @@ -0,0 +1,47 @@ +import { License } from '../../../src/models/v3/license'; + +import { serializeInput, assertExtensions } from './utils'; + +import type { v3 } from '../../../src/spec-types'; + +describe('License model', function() { + describe('.name()', function() { + it('should return the value', function() { + const doc = serializeInput({ name: 'Apache 2.0' }); + const d = new License(doc); + expect(d.name()).toEqual(doc.name); + }); + }); + + describe('.hasUrl()', function() { + it('should return true when there is a value', function() { + const doc = serializeInput({ url: 'https://www.apache.org/licenses/LICENSE-2.0.html' }); + const d = new License(doc); + expect(d.hasUrl()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = serializeInput({}); + const d = new License(doc); + expect(d.hasUrl()).toEqual(false); + }); + }); + + describe('.url()', function() { + it('should return the value', function() { + const doc = serializeInput({ url: 'https://www.apache.org/licenses/LICENSE-2.0.html' }); + const d = new License(doc); + expect(d.url()).toEqual(doc.url); + }); + + it('should return undefined when there is no value', function() { + const doc = serializeInput({}); + const d = new License(doc); + expect(d.url()).toBeUndefined(); + }); + }); + + describe('mixins', function() { + assertExtensions(License); + }); +});