From 0791adca4f6187946b990eec764b6f56d4e055af Mon Sep 17 00:00:00 2001 From: Fran Mendez Date: Thu, 13 Oct 2022 17:22:21 +0200 Subject: [PATCH 01/17] Move collections out of the v2 directory because they are always the same implementation --- src/models/channel-parameters.ts | 10 ++++++++-- src/models/channels.ts | 18 ++++++++++++++++-- src/models/correlation-ids.ts | 10 ++++++++-- src/models/extensions.ts | 11 +++++++++-- src/models/message-examples.ts | 8 +++++++- src/models/message-traits.ts | 10 ++++++++-- src/models/messages.ts | 18 ++++++++++++++++-- src/models/operation-traits.ts | 10 ++++++++-- src/models/operations.ts | 17 ++++++++++++++++- src/models/schemas.ts | 8 +++++++- src/models/security-requirements.ts | 10 ++++++++-- src/models/security-schemes.ts | 8 +++++++- src/models/server-variables.ts | 10 ++++++++-- src/models/servers.ts | 16 +++++++++++++++- src/models/tags.ts | 10 ++++++++-- src/models/v2/asyncapi.ts | 12 ++++++------ src/models/v2/bindings.ts | 2 +- src/models/v2/channel-parameters.ts | 10 ---------- src/models/v2/channel.ts | 8 ++++---- src/models/v2/channels.ts | 18 ------------------ src/models/v2/components.ts | 22 +++++++++++----------- src/models/v2/correlation-ids.ts | 10 ---------- src/models/v2/extensions.ts | 11 ----------- src/models/v2/index.ts | 26 +++++++++++++------------- src/models/v2/info.ts | 11 +++++++---- src/models/v2/message-examples.ts | 10 ---------- src/models/v2/message-trait.ts | 2 +- src/models/v2/message-traits.ts | 10 ---------- src/models/v2/message.ts | 8 ++++---- src/models/v2/messages.ts | 18 ------------------ src/models/v2/mixins.ts | 4 ++-- src/models/v2/operation-trait.ts | 16 ++++++---------- src/models/v2/operation-traits.ts | 10 ---------- src/models/v2/operation.ts | 8 ++++---- src/models/v2/operations.ts | 18 ------------------ src/models/v2/schema.ts | 2 +- src/models/v2/schemas.ts | 10 ---------- src/models/v2/security-requirements.ts | 10 ---------- src/models/v2/security-schemes.ts | 10 ---------- src/models/v2/server-variables.ts | 10 ---------- src/models/v2/server.ts | 10 +++++----- src/models/v2/servers.ts | 17 ----------------- src/models/v2/tags.ts | 10 ---------- 43 files changed, 214 insertions(+), 273 deletions(-) delete mode 100644 src/models/v2/channel-parameters.ts delete mode 100644 src/models/v2/channels.ts delete mode 100644 src/models/v2/correlation-ids.ts delete mode 100644 src/models/v2/extensions.ts delete mode 100644 src/models/v2/message-examples.ts delete mode 100644 src/models/v2/message-traits.ts delete mode 100644 src/models/v2/messages.ts delete mode 100644 src/models/v2/operation-traits.ts delete mode 100644 src/models/v2/operations.ts delete mode 100644 src/models/v2/schemas.ts delete mode 100644 src/models/v2/security-requirements.ts delete mode 100644 src/models/v2/security-schemes.ts delete mode 100644 src/models/v2/server-variables.ts delete mode 100644 src/models/v2/servers.ts delete mode 100644 src/models/v2/tags.ts diff --git a/src/models/channel-parameters.ts b/src/models/channel-parameters.ts index 629c7c87a..1143bdf8a 100644 --- a/src/models/channel-parameters.ts +++ b/src/models/channel-parameters.ts @@ -1,4 +1,10 @@ -import type { Collection } from './collection'; +import { Collection } from './collection'; import type { ChannelParameterInterface } from './channel-parameter'; -export type ChannelParametersInterface = Collection \ No newline at end of file +export type ChannelParametersInterface = Collection; + +export class ChannelParameters extends Collection implements ChannelParametersInterface { + override get(id: string): ChannelParameterInterface | undefined { + return this.collections.find(parameter => parameter.id() === id); + } +} \ No newline at end of file diff --git a/src/models/channels.ts b/src/models/channels.ts index cafec9950..c10f5a6a1 100644 --- a/src/models/channels.ts +++ b/src/models/channels.ts @@ -1,7 +1,21 @@ -import type { Collection } from './collection'; +import { Collection } from './collection'; import type { ChannelInterface } from './channel'; export interface ChannelsInterface extends Collection { filterBySend(): ChannelInterface[]; filterByReceive(): ChannelInterface[]; -} \ No newline at end of file +} + +export class Channels extends Collection implements ChannelsInterface { + override get(id: string): ChannelInterface | undefined { + return this.collections.find(channel => channel.id() === id); + } + + filterBySend(): ChannelInterface[] { + return this.filterBy(channel => channel.operations().filterBySend().length > 0); + } + + filterByReceive(): ChannelInterface[] { + return this.filterBy(channel => channel.operations().filterByReceive().length > 0); + } +} diff --git a/src/models/correlation-ids.ts b/src/models/correlation-ids.ts index 9134efe9e..8355ea58d 100644 --- a/src/models/correlation-ids.ts +++ b/src/models/correlation-ids.ts @@ -1,4 +1,10 @@ -import type { Collection } from './collection'; +import { Collection } from './collection'; import type { CorrelationIdInterface } from './correlation-id'; -export type CorrelationIdsInterface = Collection \ No newline at end of file +export type CorrelationIdsInterface = Collection + +export class CorrelationIds extends Collection implements CorrelationIdsInterface { + override get(id: string): CorrelationIdInterface | undefined { + return this.collections.find(correlationId => correlationId.meta('id' as any) === id); + } +} diff --git a/src/models/extensions.ts b/src/models/extensions.ts index 7a1184461..7f3247428 100644 --- a/src/models/extensions.ts +++ b/src/models/extensions.ts @@ -1,4 +1,11 @@ -import type { Collection } from './collection'; +import { Collection } from './collection'; import type { ExtensionInterface } from './extension'; -export type ExtensionsInterface = Collection \ No newline at end of file +export type ExtensionsInterface = Collection + +export class Extensions extends Collection implements ExtensionsInterface { + override get(id: string): ExtensionInterface | undefined { + id = id.startsWith('x-') ? id : `x-${id}`; + return this.collections.find(ext => ext.id() === id); + } +} \ No newline at end of file diff --git a/src/models/message-examples.ts b/src/models/message-examples.ts index e0ff6110c..16dd35a03 100644 --- a/src/models/message-examples.ts +++ b/src/models/message-examples.ts @@ -1,4 +1,10 @@ -import type { Collection } from './collection'; +import { Collection } from './collection'; import type { MessageExampleInterface } from './message-example'; export type MessageExamplesInterface = Collection + +export class MessageExamples extends Collection implements MessageExamplesInterface { + override get(name: string): MessageExampleInterface | undefined { + return this.collections.find(example => example.name() === name); + } +} diff --git a/src/models/message-traits.ts b/src/models/message-traits.ts index 548b344d8..4dfcd6fa3 100644 --- a/src/models/message-traits.ts +++ b/src/models/message-traits.ts @@ -1,4 +1,10 @@ -import type { Collection } from './collection'; +import { Collection } from './collection'; import type { MessageTraitInterface } from './message-trait'; -export type MessageTraitsInterface = Collection \ No newline at end of file +export type MessageTraitsInterface = Collection + +export class MessageTraits extends Collection implements MessageTraitsInterface { + override get(id: string): MessageTraitInterface | undefined { + return this.collections.find(trait => trait.id() === id); + } +} \ No newline at end of file diff --git a/src/models/messages.ts b/src/models/messages.ts index eaf8cb178..32604b0b0 100644 --- a/src/models/messages.ts +++ b/src/models/messages.ts @@ -1,7 +1,21 @@ -import type { Collection } from './collection'; +import { Collection } from './collection'; import type { MessageInterface } from './message'; export interface MessagesInterface extends Collection { filterBySend(): MessageInterface[]; filterByReceive(): MessageInterface[]; -} \ No newline at end of file +} + +export class Messages extends Collection implements MessagesInterface { + override get(name: string): MessageInterface | undefined { + return this.collections.find(message => message.id() === name); + } + + filterBySend(): MessageInterface[] { + return this.filterBy(message => message.operations().filterBySend().length > 0); + } + + filterByReceive(): MessageInterface[] { + return this.filterBy(message => message.operations().filterByReceive().length > 0); + } +} diff --git a/src/models/operation-traits.ts b/src/models/operation-traits.ts index 310dbbfc7..fc6e06ddf 100644 --- a/src/models/operation-traits.ts +++ b/src/models/operation-traits.ts @@ -1,4 +1,10 @@ -import type { Collection } from './collection'; +import { Collection } from './collection'; import type { OperationTraitInterface } from './operation-trait'; -export type OperationTraitsInterface = Collection \ No newline at end of file +export type OperationTraitsInterface = Collection + +export class OperationTraits extends Collection implements OperationTraitsInterface { + override get(id: string): OperationTraitInterface | undefined { + return this.collections.find(trait => trait.id() === id); + } +} diff --git a/src/models/operations.ts b/src/models/operations.ts index 798e323a3..5d2726f59 100644 --- a/src/models/operations.ts +++ b/src/models/operations.ts @@ -1,7 +1,22 @@ -import type { Collection } from './collection'; +import { Collection } from './collection'; + import type { OperationInterface } from './operation'; export interface OperationsInterface extends Collection { filterBySend(): OperationInterface[]; filterByReceive(): OperationInterface[]; } + +export class Operations extends Collection implements OperationsInterface { + override get(id: string): OperationInterface | undefined { + return this.collections.find(operation => operation.id() === id); + } + + filterBySend(): OperationInterface[] { + return this.filterBy(operation => operation.isSend()); + } + + filterByReceive(): OperationInterface[] { + return this.filterBy(operation => operation.isReceive()); + } +} diff --git a/src/models/schemas.ts b/src/models/schemas.ts index f8d021ada..797b1a42c 100644 --- a/src/models/schemas.ts +++ b/src/models/schemas.ts @@ -1,4 +1,10 @@ -import type { Collection } from './collection'; +import { Collection } from './collection'; import type { SchemaInterface } from './schema'; export type SchemasInterface = Collection + +export class Schemas extends Collection implements SchemasInterface { + override get(id: string): SchemaInterface | undefined { + return this.collections.find(schema => schema.uid() === id); + } +} diff --git a/src/models/security-requirements.ts b/src/models/security-requirements.ts index c52070760..436abecc1 100644 --- a/src/models/security-requirements.ts +++ b/src/models/security-requirements.ts @@ -1,4 +1,10 @@ -import type { Collection} from './collection'; +import { Collection} from './collection'; import type { SecurityRequirementInterface } from './security-requirement'; -export type SecurityRequirementsInterface = Collection \ No newline at end of file +export type SecurityRequirementsInterface = Collection + +export class SecurityRequirements extends Collection implements SecurityRequirementsInterface { + override get(id: string): SecurityRequirementInterface | undefined { + return this.collections.find(securityRequirement => securityRequirement.meta('id' as any) === id); + } +} diff --git a/src/models/security-schemes.ts b/src/models/security-schemes.ts index 283216648..84e1b5860 100644 --- a/src/models/security-schemes.ts +++ b/src/models/security-schemes.ts @@ -1,4 +1,10 @@ -import type { Collection} from './collection'; +import { Collection} from './collection'; import type { SecuritySchemeInterface } from './security-scheme'; export type SecuritySchemesInterface = Collection + +export class SecuritySchemes extends Collection implements SecuritySchemesInterface { + override get(id: string): SecuritySchemeInterface | undefined { + return this.collections.find(securityScheme => securityScheme.id() === id); + } +} diff --git a/src/models/server-variables.ts b/src/models/server-variables.ts index ae4499749..5184045ca 100644 --- a/src/models/server-variables.ts +++ b/src/models/server-variables.ts @@ -1,4 +1,10 @@ -import type { Collection } from './collection'; +import { Collection } from './collection'; import type { ServerVariableInterface } from './server-variable'; -export type ServerVariablesInterface = Collection \ No newline at end of file +export type ServerVariablesInterface = Collection; + +export class ServerVariables extends Collection implements ServerVariablesInterface { + override get(id: string): ServerVariableInterface | undefined { + return this.collections.find(variable => variable.id() === id); + } +} diff --git a/src/models/servers.ts b/src/models/servers.ts index 8483ef759..6f91da73a 100644 --- a/src/models/servers.ts +++ b/src/models/servers.ts @@ -1,7 +1,21 @@ -import type { Collection } from './collection'; +import { Collection } from './collection'; import type { ServerInterface } from './server'; export interface ServersInterface extends Collection { filterBySend(): ServerInterface[]; filterByReceive(): ServerInterface[]; } + +export class Servers extends Collection implements ServersInterface { + override get(id: string): ServerInterface | undefined { + return this.collections.find(server => server.id() === id); + } + + filterBySend(): ServerInterface[] { + return this.filterBy(server => server.operations().filterBySend().length > 0); + } + + filterByReceive(): ServerInterface[] { + return this.filterBy(server => server.operations().filterByReceive().length > 0); + } +} \ No newline at end of file diff --git a/src/models/tags.ts b/src/models/tags.ts index 368812652..475cec00d 100644 --- a/src/models/tags.ts +++ b/src/models/tags.ts @@ -1,4 +1,10 @@ -import type { Collection } from './collection'; +import { Collection } from './collection'; import type { TagInterface } from './tag'; -export type TagsInterface = Collection \ No newline at end of file +export type TagsInterface = Collection + +export class Tags extends Collection implements TagsInterface { + override get(name: string): TagInterface | undefined { + return this.collections.find(tag => tag.name() === name); + } +} \ No newline at end of file diff --git a/src/models/v2/asyncapi.ts b/src/models/v2/asyncapi.ts index fc14f4689..1bb291025 100644 --- a/src/models/v2/asyncapi.ts +++ b/src/models/v2/asyncapi.ts @@ -1,15 +1,15 @@ import { BaseModel } from '../base'; import { Info } from './info'; -import { Channels } from './channels'; +import { Channels } from '../channels'; import { Channel } from './channel'; import { Components } from './components'; -import { Messages } from './messages'; -import { Operations } from './operations'; -import { Servers } from './servers'; +import { Messages } from '../messages'; +import { Operations } from '../operations'; +import { Servers } from '../servers'; import { Server } from './server'; -import { SecuritySchemes } from './security-schemes'; +import { SecuritySchemes } from '../security-schemes'; import { SecurityScheme } from './security-scheme'; -import { Schemas } from './schemas'; +import { Schemas } from '../schemas'; import { extensions } from './mixins'; diff --git a/src/models/v2/bindings.ts b/src/models/v2/bindings.ts index e9b5f46e9..2a394e9a0 100644 --- a/src/models/v2/bindings.ts +++ b/src/models/v2/bindings.ts @@ -1,5 +1,5 @@ import { Collection } from '../collection'; -import { Extensions } from './extensions'; +import { Extensions } from '../extensions'; import { Extension } from './extension'; import { createModel } from '../utils'; diff --git a/src/models/v2/channel-parameters.ts b/src/models/v2/channel-parameters.ts deleted file mode 100644 index 71d7cf10e..000000000 --- a/src/models/v2/channel-parameters.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Collection } from '../collection'; - -import type { ChannelParametersInterface } from '../channel-parameters'; -import type { ChannelParameterInterface } from '../channel-parameter'; - -export class ChannelParameters extends Collection implements ChannelParametersInterface { - override get(id: string): ChannelParameterInterface | undefined { - return this.collections.find(parameter => parameter.id() === id); - } -} diff --git a/src/models/v2/channel.ts b/src/models/v2/channel.ts index 60a6fc7dc..29b03522f 100644 --- a/src/models/v2/channel.ts +++ b/src/models/v2/channel.ts @@ -1,10 +1,10 @@ import { BaseModel } from '../base'; -import { ChannelParameters } from './channel-parameters'; +import { ChannelParameters } from '../channel-parameters'; import { ChannelParameter } from './channel-parameter'; -import { Messages } from './messages'; -import { Operations } from './operations'; +import { Messages } from '../messages'; +import { Operations } from '../operations'; import { Operation } from './operation'; -import { Servers } from './servers'; +import { Servers } from '../servers'; import { Server } from './server'; import { bindings, hasDescription, description, extensions } from './mixins'; diff --git a/src/models/v2/channels.ts b/src/models/v2/channels.ts deleted file mode 100644 index 2f285f51e..000000000 --- a/src/models/v2/channels.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Collection } from '../collection'; - -import type { ChannelsInterface } from '../channels'; -import type { ChannelInterface } from '../channel'; - -export class Channels extends Collection implements ChannelsInterface { - override get(id: string): ChannelInterface | undefined { - return this.collections.find(channel => channel.id() === id); - } - - filterBySend(): ChannelInterface[] { - return this.filterBy(channel => channel.operations().filterBySend().length > 0); - } - - filterByReceive(): ChannelInterface[] { - return this.filterBy(channel => channel.operations().filterByReceive().length > 0); - } -} diff --git a/src/models/v2/components.ts b/src/models/v2/components.ts index b6f16f425..8f2ace0af 100644 --- a/src/models/v2/components.ts +++ b/src/models/v2/components.ts @@ -13,17 +13,17 @@ 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 { 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 { Message } from './message'; import { tilde } from '../../utils'; diff --git a/src/models/v2/correlation-ids.ts b/src/models/v2/correlation-ids.ts deleted file mode 100644 index d0a921171..000000000 --- a/src/models/v2/correlation-ids.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Collection } from '../collection'; - -import type { CorrelationIdsInterface } from '../correlation-ids'; -import type { CorrelationIdInterface } from '../correlation-id'; - -export class CorrelationIds extends Collection implements CorrelationIdsInterface { - override get(id: string): CorrelationIdInterface | undefined { - return this.collections.find(correlationId => correlationId.meta('id' as any) === id); - } -} diff --git a/src/models/v2/extensions.ts b/src/models/v2/extensions.ts deleted file mode 100644 index e529b35d3..000000000 --- a/src/models/v2/extensions.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Collection } from '../collection'; - -import type { ExtensionsInterface } from '../extensions'; -import type { ExtensionInterface } from '../extension'; - -export class Extensions extends Collection implements ExtensionsInterface { - override get(id: string): ExtensionInterface | undefined { - id = id.startsWith('x-') ? id : `x-${id}`; - return this.collections.find(ext => ext.id() === id); - } -} diff --git a/src/models/v2/index.ts b/src/models/v2/index.ts index 69b94f651..17b34f9b6 100644 --- a/src/models/v2/index.ts +++ b/src/models/v2/index.ts @@ -2,36 +2,36 @@ export { AsyncAPIDocument as AsyncAPIDocumentV2 } from './asyncapi'; export { Binding as BindingV2 } from './binding'; export { Bindings as BindingsV2 } from './bindings'; export { ChannelParameter as ChannelParameterV2 } from './channel-parameter'; -export { ChannelParameters as ChannelParametersV2 } from './channel-parameters'; +export { ChannelParameters as ChannelParametersV2 } from '../channel-parameters'; export { Channel as ChannelV2 } from './channel'; -export { Channels as ChannelsV2 } from './channels'; +export { Channels as ChannelsV2 } from '../channels'; export { Components as ComponentsV2 } from './components'; 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 { Extensions as ExtensionsV2 } from '../extensions'; export { ExternalDocumentation as ExternalDocumentationV2 } from './external-docs'; export { Info as InfoV2 } from './info'; export { License as LicenseV2 } from './license'; export { MessageExample as MessageExampleV2 } from './message-example'; -export { MessageExamples as MessageExamplesV2 } from './message-examples'; +export { MessageExamples as MessageExamplesV2 } from '../message-examples'; export { MessageTrait as MessageTraitV2 } from './message-trait'; -export { MessageTraits as MessageTraitsV2 } from './message-traits'; +export { MessageTraits as MessageTraitsV2 } from '../message-traits'; export { Message as MessageV2 } from './message'; -export { Messages as MessagesV2 } from './messages'; +export { Messages as MessagesV2 } from '../messages'; export { OAuthFlow as OAuthFlowV2 } from './oauth-flow'; export { OAuthFlows as OAuthFlowsV2 } from './oauth-flows'; export { OperationTrait as OperationTraitV2 } from './operation-trait'; -export { OperationTraits as OperationTraitsV2 } from './operation-traits'; +export { OperationTraits as OperationTraitsV2 } from '../operation-traits'; export { Operation as OperationV2 } from './operation'; -export { Operations as OperationsV2 } from './operations'; +export { Operations as OperationsV2 } from '../operations'; export { Schema as SchemaV2 } from './schema'; -export { Schemas as SchemasV2 } from './schemas'; +export { Schemas as SchemasV2 } from '../schemas'; export { SecurityScheme as SecuritySchemeV2 } from './security-scheme'; -export { SecuritySchemes as SecuritySchemesV2 } from './security-schemes'; +export { SecuritySchemes as SecuritySchemesV2 } from '../security-schemes'; export { ServerVariable as ServerVariableV2 } from './server-variable'; -export { ServerVariables as ServerVariablesV2 } from './server-variables'; +export { ServerVariables as ServerVariablesV2 } from '../server-variables'; export { Server as ServerV2 } from './server'; -export { Servers as ServersV2 } from './servers'; +export { Servers as ServersV2 } from '../servers'; export { Tag as TagV2 } from './tag'; -export { Tags as TagsV2 } from './tags'; \ No newline at end of file +export { Tags as TagsV2 } from '../tags'; \ No newline at end of file diff --git a/src/models/v2/info.ts b/src/models/v2/info.ts index 5ca6dda00..5afae182b 100644 --- a/src/models/v2/info.ts +++ b/src/models/v2/info.ts @@ -2,7 +2,7 @@ import { BaseModel } from '../base'; import { Contact } from './contact'; import { ExternalDocumentation } from './external-docs'; import { License } from './license'; -import { Tags } from './tags'; +import { Tags } from '../tags'; import { Tag } from './tag'; import { hasDescription, description, extensions } from './mixins'; @@ -68,17 +68,20 @@ export class Info extends BaseModel implements InfoInterface { } hasExternalDocs(): boolean { - return Object.keys(this._meta.asyncapi.parsed.externalDocs || {}).length > 0; + const parsedAsyncAPI: v2.AsyncAPIObject = this._meta.asyncapi.parsed as v2.AsyncAPIObject; + return Object.keys(parsedAsyncAPI.externalDocs || {}).length > 0; } externalDocs(): ExternalDocumentationInterface | undefined { if (this.hasExternalDocs()) { - return this.createModel(ExternalDocumentation, this._meta.asyncapi.parsed.externalDocs as v2.ExternalDocumentationObject, { pointer: '/externalDocs' }); + const parsedAsyncAPI: v2.AsyncAPIObject = this._meta.asyncapi.parsed as v2.AsyncAPIObject; + return this.createModel(ExternalDocumentation, parsedAsyncAPI.externalDocs as v2.ExternalDocumentationObject, { pointer: '/externalDocs' }); } } tags(): TagsInterface { - const tags = this._meta.asyncapi.parsed.tags || []; + const parsedAsyncAPI: v2.AsyncAPIObject = this._meta.asyncapi.parsed as v2.AsyncAPIObject; + const tags = parsedAsyncAPI.tags || []; return new Tags(tags.map((tag: any, idx: number) => this.createModel(Tag, tag, { pointer: `/tags/${idx}` }))); } diff --git a/src/models/v2/message-examples.ts b/src/models/v2/message-examples.ts deleted file mode 100644 index fa07cfa2f..000000000 --- a/src/models/v2/message-examples.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Collection } from '../collection'; - -import type { MessageExamplesInterface } from '../message-examples'; -import type { MessageExampleInterface } from '../message-example'; - -export class MessageExamples extends Collection implements MessageExamplesInterface { - override get(name: string): MessageExampleInterface | undefined { - return this.collections.find(example => example.name() === name); - } -} diff --git a/src/models/v2/message-trait.ts b/src/models/v2/message-trait.ts index 914757e79..2f1333acb 100644 --- a/src/models/v2/message-trait.ts +++ b/src/models/v2/message-trait.ts @@ -1,6 +1,6 @@ import { BaseModel } from '../base'; import { CorrelationId } from './correlation-id'; -import { MessageExamples } from './message-examples'; +import { MessageExamples } from '../message-examples'; import { MessageExample } from './message-example'; import { Schema } from './schema'; diff --git a/src/models/v2/message-traits.ts b/src/models/v2/message-traits.ts deleted file mode 100644 index 3d8dadddb..000000000 --- a/src/models/v2/message-traits.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Collection } from '../collection'; - -import type { MessageTraitsInterface } from '../message-traits'; -import type { MessageTraitInterface } from '../message-trait'; - -export class MessageTraits extends Collection implements MessageTraitsInterface { - override get(id: string): MessageTraitInterface | undefined { - return this.collections.find(trait => trait.id() === id); - } -} diff --git a/src/models/v2/message.ts b/src/models/v2/message.ts index ad87eeba3..4c8583db1 100644 --- a/src/models/v2/message.ts +++ b/src/models/v2/message.ts @@ -1,9 +1,9 @@ -import { Channels } from './channels'; -import { Operations } from './operations'; +import { Channels } from '../channels'; +import { Operations } from '../operations'; import { Operation } from './operation'; -import { MessageTraits } from './message-traits'; +import { MessageTraits } from '../message-traits'; import { MessageTrait } from './message-trait'; -import { Servers } from './servers'; +import { Servers } from '../servers'; import { Schema } from './schema'; import { tilde } from '../../utils'; diff --git a/src/models/v2/messages.ts b/src/models/v2/messages.ts deleted file mode 100644 index 591c0d7ac..000000000 --- a/src/models/v2/messages.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Collection } from '../collection'; - -import type { MessagesInterface } from '../messages'; -import type { MessageInterface } from '../message'; - -export class Messages extends Collection implements MessagesInterface { - override get(name: string): MessageInterface | undefined { - return this.collections.find(message => message.id() === name); - } - - filterBySend(): MessageInterface[] { - return this.filterBy(message => message.operations().filterBySend().length > 0); - } - - filterByReceive(): MessageInterface[] { - return this.filterBy(message => message.operations().filterByReceive().length > 0); - } -} diff --git a/src/models/v2/mixins.ts b/src/models/v2/mixins.ts index 37ebc7927..d93f099cf 100644 --- a/src/models/v2/mixins.ts +++ b/src/models/v2/mixins.ts @@ -1,9 +1,9 @@ import { Bindings } from './bindings'; import { Binding } from './binding'; -import { Extensions } from './extensions'; +import { Extensions } from '../extensions'; import { Extension } from './extension'; import { ExternalDocumentation } from './external-docs'; -import { Tags } from './tags'; +import { Tags } from '../tags'; import { Tag } from './tag'; import { createModel } from '../utils'; diff --git a/src/models/v2/operation-trait.ts b/src/models/v2/operation-trait.ts index 98a4ca631..fe24cfb65 100644 --- a/src/models/v2/operation-trait.ts +++ b/src/models/v2/operation-trait.ts @@ -11,24 +11,20 @@ import type { OperationTraitInterface } from '../operation-trait'; import type { TagsInterface } from '../tags'; import type { v2 } from '../../spec-types'; -import { SecurityRequirements } from './security-requirements'; +import { SecurityRequirements } from '../security-requirements'; import { SecurityRequirement } from './security-requirement'; -export class OperationTrait extends BaseModel implements OperationTraitInterface { - id(): string { - return this.operationId() || this._meta.id; +export class OperationTrait extends BaseModel implements OperationTraitInterface { + id(): string | undefined { + return this._json.operationId; } action(): OperationAction { return this._meta.action; } - hasOperationId(): boolean { - return !!this._json.operationId; - } - - operationId(): string | undefined { - return this._json.operationId; + hasId(): boolean { + return this.id() !== undefined; } hasSummary(): boolean { diff --git a/src/models/v2/operation-traits.ts b/src/models/v2/operation-traits.ts deleted file mode 100644 index 32cae7902..000000000 --- a/src/models/v2/operation-traits.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Collection } from '../collection'; - -import type { OperationTraitsInterface } from '../operation-traits'; -import type { OperationTraitInterface } from '../operation-trait'; - -export class OperationTraits extends Collection implements OperationTraitsInterface { - override get(id: string): OperationTraitInterface | undefined { - return this.collections.find(trait => trait.id() === id); - } -} diff --git a/src/models/v2/operation.ts b/src/models/v2/operation.ts index 84dcda87b..b0a0614e5 100644 --- a/src/models/v2/operation.ts +++ b/src/models/v2/operation.ts @@ -1,10 +1,10 @@ -import { Channels } from './channels'; +import { Channels } from '../channels'; import { Channel } from './channel'; -import { Messages } from './messages'; +import { Messages } from '../messages'; import { Message } from './message'; -import { OperationTraits } from './operation-traits'; +import { OperationTraits } from '../operation-traits'; import { OperationTrait } from './operation-trait'; -import { Servers } from './servers'; +import { Servers } from '../servers'; import { tilde } from '../../utils'; diff --git a/src/models/v2/operations.ts b/src/models/v2/operations.ts deleted file mode 100644 index f61b8c11c..000000000 --- a/src/models/v2/operations.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Collection } from '../collection'; - -import type { OperationsInterface } from '../operations'; -import type { OperationInterface } from '../operation'; - -export class Operations extends Collection implements OperationsInterface { - override get(id: string): OperationInterface | undefined { - return this.collections.find(operation => operation.id() === id); - } - - filterBySend(): OperationInterface[] { - return this.filterBy(operation => operation.isSend()); - } - - filterByReceive(): OperationInterface[] { - return this.filterBy(operation => operation.isReceive()); - } -} diff --git a/src/models/v2/schema.ts b/src/models/v2/schema.ts index ee551ae63..a025f5619 100644 --- a/src/models/v2/schema.ts +++ b/src/models/v2/schema.ts @@ -16,7 +16,7 @@ export class Schema extends BaseModel implements SchemasInterface { - override get(id: string): SchemaInterface | undefined { - return this.collections.find(schema => schema.uid() === id); - } -} diff --git a/src/models/v2/security-requirements.ts b/src/models/v2/security-requirements.ts deleted file mode 100644 index 5fcf7dd30..000000000 --- a/src/models/v2/security-requirements.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Collection } from '../collection'; - -import type { SecurityRequirementsInterface } from '../security-requirements'; -import type { SecurityRequirementInterface } from '../security-requirement'; - -export class SecurityRequirements extends Collection implements SecurityRequirementsInterface { - override get(id: string): SecurityRequirementInterface | undefined { - return this.collections.find(securityRequirement => securityRequirement.meta('id' as any) === id); - } -} diff --git a/src/models/v2/security-schemes.ts b/src/models/v2/security-schemes.ts deleted file mode 100644 index 93ca01e1b..000000000 --- a/src/models/v2/security-schemes.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Collection } from '../collection'; - -import type { SecuritySchemesInterface } from '../security-schemes'; -import type { SecuritySchemeInterface } from '../security-scheme'; - -export class SecuritySchemes extends Collection implements SecuritySchemesInterface { - override get(id: string): SecuritySchemeInterface | undefined { - return this.collections.find(securityScheme => securityScheme.id() === id); - } -} diff --git a/src/models/v2/server-variables.ts b/src/models/v2/server-variables.ts deleted file mode 100644 index 5faf082db..000000000 --- a/src/models/v2/server-variables.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Collection } from '../collection'; - -import type { ServerVariablesInterface } from '../server-variables'; -import type { ServerVariableInterface } from '../server-variable'; - -export class ServerVariables extends Collection implements ServerVariablesInterface { - override get(id: string): ServerVariableInterface | undefined { - return this.collections.find(variable => variable.id() === id); - } -} diff --git a/src/models/v2/server.ts b/src/models/v2/server.ts index d063ee735..162527087 100644 --- a/src/models/v2/server.ts +++ b/src/models/v2/server.ts @@ -1,12 +1,12 @@ import { BaseModel } from '../base'; -import { Channels } from './channels'; +import { Channels } from '../channels'; import { Channel } from './channel'; -import { Messages } from './messages'; -import { Operations } from './operations'; +import { Messages } from '../messages'; +import { Operations } from '../operations'; import { SecurityScheme } from './security-scheme'; -import { ServerVariables } from './server-variables'; +import { ServerVariables } from '../server-variables'; import { ServerVariable } from './server-variable'; -import { SecurityRequirements } from './security-requirements'; +import { SecurityRequirements } from '../security-requirements'; import { SecurityRequirement } from './security-requirement'; import { bindings, hasDescription, description, extensions } from './mixins'; diff --git a/src/models/v2/servers.ts b/src/models/v2/servers.ts deleted file mode 100644 index 3c6ce0f4a..000000000 --- a/src/models/v2/servers.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Collection } from '../collection'; -import { ServerInterface } from '../server'; -import { ServersInterface } from '../servers'; - -export class Servers extends Collection implements ServersInterface { - override get(id: string): ServerInterface | undefined { - return this.collections.find(server => server.id() === id); - } - - filterBySend(): ServerInterface[] { - return this.filterBy(server => server.operations().filterBySend().length > 0); - } - - filterByReceive(): ServerInterface[] { - return this.filterBy(server => server.operations().filterByReceive().length > 0); - } -} diff --git a/src/models/v2/tags.ts b/src/models/v2/tags.ts deleted file mode 100644 index 6048620ff..000000000 --- a/src/models/v2/tags.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Collection } from '../collection'; - -import type { TagsInterface } from '../tags'; -import type { TagInterface } from '../tag'; - -export class Tags extends Collection implements TagsInterface { - override get(name: string): TagInterface | undefined { - return this.collections.find(tag => tag.name() === name); - } -} From 56b9fd1a05160899f3c7119773cba51aaca9c7e1 Mon Sep 17 00:00:00 2001 From: Fran Mendez Date: Thu, 13 Oct 2022 17:30:48 +0200 Subject: [PATCH 02/17] Adding v3 models --- src/models/v3/asyncapi.ts | 10 +- src/models/v3/channel-parameter.ts | 45 ++++ src/models/v3/channel.ts | 91 +++++++ src/models/v3/oauth-flow.ts | 34 +++ src/models/v3/oauth-flows.ts | 52 ++++ src/models/v3/operation-trait.ts | 87 +++++++ src/models/v3/operation.ts | 136 +++++++++++ src/models/v3/schema.ts | 315 ++++++++++++++++++++++++ src/models/v3/security-requirement.ts | 14 ++ src/models/v3/security-scheme.ts | 57 +++++ src/models/v3/server-variable.ts | 46 ++++ src/models/v3/server.ts | 120 ++++++++++ src/spec-types/v3.ts | 331 ++++++++++++++++++++++++++ 13 files changed, 1337 insertions(+), 1 deletion(-) create mode 100644 src/models/v3/channel-parameter.ts create mode 100644 src/models/v3/channel.ts create mode 100644 src/models/v3/oauth-flow.ts create mode 100644 src/models/v3/oauth-flows.ts create mode 100644 src/models/v3/operation-trait.ts create mode 100644 src/models/v3/operation.ts create mode 100644 src/models/v3/schema.ts create mode 100644 src/models/v3/security-requirement.ts create mode 100644 src/models/v3/security-scheme.ts create mode 100644 src/models/v3/server-variable.ts create mode 100644 src/models/v3/server.ts diff --git a/src/models/v3/asyncapi.ts b/src/models/v3/asyncapi.ts index 29e3b8687..d6789556a 100644 --- a/src/models/v3/asyncapi.ts +++ b/src/models/v3/asyncapi.ts @@ -1,4 +1,8 @@ +import { tilde } from '../../utils'; import { BaseModel } from '../base'; +import { OperationInterface } from '../operation'; +import { Operations } from '../operations'; +import { Operation } from './operation'; import type { AsyncAPIDocumentInterface } from '../asyncapi'; @@ -30,7 +34,11 @@ export class AsyncAPIDocument extends BaseModel implements As } operations() { - return null as any; + const operations: OperationInterface[] = Object.entries(this._json.operations || {}).map(([operationId, operation]) => + this.createModel(Operation, operation, { id: operationId, address: operationId, pointer: `/operations/${tilde(operationId)}` }) + ) + + return new Operations(operations); } messages() { diff --git a/src/models/v3/channel-parameter.ts b/src/models/v3/channel-parameter.ts new file mode 100644 index 000000000..b5fc343e4 --- /dev/null +++ b/src/models/v3/channel-parameter.ts @@ -0,0 +1,45 @@ +import { BaseModel } from '../base'; +import { Schema } from './schema'; + +import { hasDescription, description, extensions } from './mixins'; + +import type { ChannelParameterInterface } from '../channel-parameter'; +import type { SchemaInterface } from '../schema'; +import type { ExtensionsInterface } from '../extensions'; + +import type { v3 } from '../../spec-types'; + +export class ChannelParameter extends BaseModel implements ChannelParameterInterface { + id(): string { + return this._meta.id; + } + + hasSchema(): boolean { + return !!this._json.schema; + } + + schema(): SchemaInterface | undefined { + if (!this._json.schema) return undefined; + return this.createModel(Schema, this._json.schema, { pointer: `${this._meta.pointer}/schema` }); + } + + hasLocation(): boolean { + return !!this._json.location; + } + + location(): string | undefined { + return this._json.location; + } + + hasDescription(): boolean { + return hasDescription(this); + } + + description(): string | undefined { + return description(this); + } + + extensions(): ExtensionsInterface { + return extensions(this); + } +} diff --git a/src/models/v3/channel.ts b/src/models/v3/channel.ts new file mode 100644 index 000000000..0d4be3396 --- /dev/null +++ b/src/models/v3/channel.ts @@ -0,0 +1,91 @@ +import { BaseModel } from '../base'; +import { ChannelParameters } from '../channel-parameters'; +import { ChannelParameter } from './channel-parameter'; +import { Messages } from '../messages'; +import { Operations } from '../operations'; +import { Operation } from './operation'; +import { Servers } from '../servers'; +import { Server } from './server'; + +import { bindings, hasDescription, description, extensions } from './mixins'; + +import type { BindingsInterface } from '../bindings'; +import type { ChannelInterface } from '../channel'; +import type { ChannelParametersInterface } from '../channel-parameters'; +import type { ExtensionsInterface } from '../extensions'; +import type { MessagesInterface } from '../messages'; +import type { MessageInterface } from '../message'; +import type { OperationsInterface } from '../operations'; +import type { OperationAction, OperationInterface } from '../operation'; +import type { ServersInterface } from '../servers'; +import type { ServerInterface } from '../server'; + +import type { v3 } from '../../spec-types'; + +export class Channel extends BaseModel implements ChannelInterface { + id(): string { + return this._meta.id; + } + + address(): string | null | undefined { + return this._meta.address; + } + + hasDescription(): boolean { + return hasDescription(this); + } + + description(): string | undefined { + return description(this); + } + + servers(): ServersInterface { + let allowedServers: v3.ServerObject[]; + if (Array.isArray(this._json.servers)) { + allowedServers = this._json.servers || []; + } else { + const parsedAsyncAPI = this._meta.asyncapi!.parsed as v3.AsyncAPIObject + allowedServers = parsedAsyncAPI.servers || []; + } + const servers: ServerInterface[] = allowedServers.map((server: v3.ServerObject) => new Server(server)); + return new Servers(servers); + } + + operations(): OperationsInterface { + const operations: OperationInterface[] = []; + // ['publish', 'subscribe'].forEach(operationAction => { + // const id = this._json[operationAction as 'publish' | 'subscribe'] && (this._json[operationAction as 'publish' | 'subscribe'] as v2.OperationObject).operationId || `${this.meta().id }_${ operationAction}`; + // if (this._json[operationAction as 'publish' | 'subscribe']) { + // operations.push( + // this.createModel(Operation, this._json[operationAction as 'publish' | 'subscribe'] as v2.OperationObject, { id, action: operationAction as OperationAction, pointer: `${this._meta.pointer}/${operationAction}` }), + // ); + // } + // }); + return new Operations(operations); + } + + messages(): MessagesInterface { + const messages: MessageInterface[] = []; + this.operations().forEach(operation => messages.push(...operation.messages().all())); + return new Messages(messages); + } + + parameters(): ChannelParametersInterface { + return new ChannelParameters( + Object.entries(this._json.parameters || {}).map(([channelParameterName, channelParameter]) => { + return this.createModel(ChannelParameter, channelParameter as v3.ParameterObject, { + id: channelParameterName, + pointer: `${this._meta.pointer}/parameters/${channelParameterName}` + }); + }) + ); + } + + bindings(): BindingsInterface { + return bindings(this); + } + + extensions(): ExtensionsInterface { + return extensions(this); + } +} diff --git a/src/models/v3/oauth-flow.ts b/src/models/v3/oauth-flow.ts new file mode 100644 index 000000000..464d3dd7d --- /dev/null +++ b/src/models/v3/oauth-flow.ts @@ -0,0 +1,34 @@ +import { BaseModel } from '../base'; + +import { extensions } from './mixins'; + +import type { ExtensionsInterface } from '../extensions'; +import type { OAuthFlowInterface } from '../oauth-flow'; + +import type { v2 } from '../../spec-types'; + +export class OAuthFlow extends BaseModel implements OAuthFlowInterface { + authorizationUrl(): string | undefined { + return this.json().authorizationUrl; + } + + hasRefreshUrl(): boolean { + return !!this._json.refreshUrl; + } + + refreshUrl(): string | undefined { + return this._json.refreshUrl; + } + + scopes(): Record | undefined { + return this._json.scopes; + } + + tokenUrl(): string | undefined { + return this.json>().tokenUrl; + } + + extensions(): ExtensionsInterface { + return extensions(this); + } +} \ No newline at end of file diff --git a/src/models/v3/oauth-flows.ts b/src/models/v3/oauth-flows.ts new file mode 100644 index 000000000..39ff0de3a --- /dev/null +++ b/src/models/v3/oauth-flows.ts @@ -0,0 +1,52 @@ +import { BaseModel } from '../base'; +import { OAuthFlow } from './oauth-flow'; + +import { extensions } from './mixins'; + +import type { ExtensionsInterface } from '../extensions'; +import type { OAuthFlowsInterface } from '../oauth-flows'; +import type { OAuthFlowInterface } from '../oauth-flow'; + +import type { v3 } from '../../spec-types'; + +export class OAuthFlows extends BaseModel implements OAuthFlowsInterface { + hasAuthorizationCode(): boolean { + return !!this._json.authorizationCode; + } + + authorizationCode(): OAuthFlowInterface | undefined { + if (!this._json.authorizationCode) return undefined; + return new OAuthFlow(this._json.authorizationCode); + } + + hasClientCredentials(): boolean { + return !!this._json.clientCredentials; + } + + clientCredentials(): OAuthFlowInterface | undefined { + if (!this._json.clientCredentials) return undefined; + return new OAuthFlow(this._json.clientCredentials); + } + + hasImplicit(): boolean { + return !!this._json.implicit; + } + + implicit(): OAuthFlowInterface | undefined { + if (!this._json.implicit) return undefined; + return new OAuthFlow(this._json.implicit); + } + + hasPassword(): boolean { + return !!this._json.password; + } + + password(): OAuthFlowInterface | undefined { + if (!this._json.password) return undefined; + return new OAuthFlow(this._json.password); + } + + extensions(): ExtensionsInterface { + return extensions(this); + } +} \ No newline at end of file diff --git a/src/models/v3/operation-trait.ts b/src/models/v3/operation-trait.ts new file mode 100644 index 000000000..53f05e684 --- /dev/null +++ b/src/models/v3/operation-trait.ts @@ -0,0 +1,87 @@ +import { BaseModel } from '../base'; +import { SecurityScheme } from './security-scheme'; + +import { bindings, hasDescription, description, extensions, hasExternalDocs, externalDocs, tags } from './mixins'; + +import type { BindingsInterface } from '../bindings'; +import type { ExtensionsInterface } from '../extensions'; +import type { ExternalDocumentationInterface } from '../external-docs'; +import type { OperationAction } from '../operation'; +import type { OperationTraitInterface } from '../operation-trait'; +import type { TagsInterface } from '../tags'; + +import type { v3 } from '../../spec-types'; +import { SecurityRequirements } from '../security-requirements'; +import { SecurityRequirement } from './security-requirement'; + +export class OperationTrait extends BaseModel implements OperationTraitInterface { + id(): string | undefined { + return this._meta.id; + } + + action(): OperationAction | undefined { + return this._meta.action; + } + + hasId(): boolean { + return this.id() === undefined; + } + + hasSummary(): boolean { + return !!this._json.summary; + } + + summary(): string | undefined { + return this._json.summary; + } + + hasDescription(): boolean { + return hasDescription(this); + } + + description(): string | undefined { + return description(this); + } + + hasExternalDocs(): boolean { + return hasExternalDocs(this); + } + + isSend(): boolean { + return this.action() === 'send'; + } + + isReceive(): boolean { + return this.action() === 'receive'; + } + + externalDocs(): ExternalDocumentationInterface | undefined { + return externalDocs(this); + } + + security(): SecurityRequirements[] { + const securitySchemes = (this._meta?.asyncapi?.parsed?.components?.securitySchemes || {}) as Record; + return (this._json.security || []).map((requirement, index) => { + const requirements: SecurityRequirement[] = []; + Object.entries(requirement).forEach(([security, scopes]) => { + const scheme = this.createModel(SecurityScheme, securitySchemes[security], { id: security, pointer: `/components/securitySchemes/${security}` }); + requirements.push( + this.createModel(SecurityRequirement, { scheme, scopes }, { id: security, pointer: `${this.meta().pointer}/security/${index}/${security}` }) + ); + }); + return new SecurityRequirements(requirements); + }); + } + + tags(): TagsInterface { + return tags(this); + } + + bindings(): BindingsInterface { + return bindings(this); + } + + extensions(): ExtensionsInterface { + return extensions(this); + } +} diff --git a/src/models/v3/operation.ts b/src/models/v3/operation.ts new file mode 100644 index 000000000..5c55b63e8 --- /dev/null +++ b/src/models/v3/operation.ts @@ -0,0 +1,136 @@ +import type { v3 } from '../../spec-types'; +import { tilde } from '../../utils'; +import { BaseModel } from '../base'; +import { OperationAction, OperationInterface } from '../operation'; +import { ChannelInterface } from '../channel'; +import { Channels, ChannelsInterface } from '../channels'; +import { Channel } from './channel'; +import { SecurityRequirements } from '../security-requirements'; +import { SecurityRequirement } from './security-requirement'; +import { SecurityScheme } from './security-scheme'; +import { TagsInterface } from '../tags'; +import { bindings, extensions, tags } from './mixins'; +import { BindingsInterface } from '../bindings'; +import { ExtensionsInterface } from '../extensions'; +import { ServersInterface, Servers } from '../servers'; +import { ServerInterface } from '../server'; +import { MessagesInterface, Messages } from '../messages'; +import { MessageInterface } from '../message'; +import { ExternalDocumentationInterface } from '../external-docs'; +import { ExternalDocumentation } from './external-docs'; +import { OperationTraits, OperationTraitsInterface } from '../operation-traits'; +import { OperationTrait } from './operation-trait'; + +export class Operation extends BaseModel implements OperationInterface { + id(): string { + return this._json['x-parser-id']; + } + + hasId(): boolean { + return true; + } + + action(): OperationAction { + return this._json.action; + } + + isSend(): boolean { + return this._json.action === 'send'; + } + + isReceive(): boolean { + return this._json.action === 'receive'; + } + + servers(): ServersInterface { + const servers: ServerInterface[] = []; + const serversData: v3.ServerObject[] = []; + this.channels().forEach(channel => { + channel.servers().forEach(server => { + if (!serversData.includes(server.json())) { + serversData.push(server.json()); + servers.push(server); + } + }); + }); + return new Servers(servers); + } + + messages(): MessagesInterface { + const messages: MessageInterface[] = []; + this.channels().forEach(channel => { + channel.messages().forEach(message => { + messages.push(message); + }); + }); + return new Messages(messages); + } + + channels(): ChannelsInterface { + const channels: ChannelInterface[] = []; + const pointer = this._json.channel.address ? tilde(`/channels/${this._json.channel.address}`) : undefined; + channels.push( + this.createModel(Channel, this._json.channel, { id: this._json.channel['x-parser-id'], address: this._json.channel.address, pointer }) + ); + return new Channels(channels); + } + + summary(): string | undefined { + return this._json.summary; + } + + hasSummary(): boolean { + return this._json.summary !== undefined; + } + + description(): string | undefined { + return this._json.description; + } + + hasDescription(): boolean { + return this._json.description !== undefined; + } + + security(): SecurityRequirements[] { + const securitySchemes = (this._meta?.asyncapi?.parsed?.components?.securitySchemes || {}) as Record; + return (this._json.security || []).map((requirement, index) => { + const requirements: SecurityRequirement[] = []; + Object.entries(requirement).forEach(([security, scopes]) => { + const scheme = this.createModel(SecurityScheme, securitySchemes[security], { id: security, pointer: `/components/securitySchemes/${security}` }); + requirements.push( + this.createModel(SecurityRequirement, { scheme, scopes }, { id: security, pointer: `${this.meta().pointer}/security/${index}/${security}` }) + ); + }); + return new SecurityRequirements(requirements); + }); + } + + tags(): TagsInterface { + return tags(this); + } + + bindings(): BindingsInterface { + return bindings(this); + } + + externalDocs(): ExternalDocumentationInterface | undefined { + if (!this.hasExternalDocs()) return; + return new ExternalDocumentation(this._json.externalDocs as v3.ExternalDocumentationObject); + } + + hasExternalDocs(): boolean { + return this._json.externalDocs === undefined; + } + + traits(): OperationTraitsInterface { + return new OperationTraits( + (this._json.traits || []).map((trait: v3.OperationTraitObject, index: number) => { + return this.createModel(OperationTrait, trait, { id: '', pointer: `${this._meta.pointer}/traits/${index}`, action: trait.action as OperationAction }); + }) + ); + } + + extensions(): ExtensionsInterface { + return extensions(this); + } +} diff --git a/src/models/v3/schema.ts b/src/models/v3/schema.ts new file mode 100644 index 000000000..1441cf782 --- /dev/null +++ b/src/models/v3/schema.ts @@ -0,0 +1,315 @@ +import { BaseModel } from '../base'; + +import { xParserSchemaId } from '../../constants'; +import { extensions, hasExternalDocs, externalDocs } from './mixins'; +import { retrievePossibleRef, hasRef } from '../../utils'; + +import type { ModelMetadata } from '../base'; +import type { ExtensionsInterface } from '../extensions'; +import type { ExternalDocumentationInterface } from '../external-docs'; +import type { SchemaInterface } from '../schema'; + +import type { v3 } from '../../spec-types'; + +export class Schema extends BaseModel implements SchemaInterface { + constructor( + _json: v3.AsyncAPISchemaObject, + _meta: ModelMetadata & { id: string, parent?: Schema } = {} as any, + ) { + _json = retrievePossibleRef(_json, _meta.pointer as string, _meta.asyncapi?.parsed); + super(_json, _meta); + } + + uid(): string { + return this._meta.id || this.extensions().get(xParserSchemaId)?.value() as string; + } + + $comment(): string | undefined { + if (typeof this._json === 'boolean') return; + return this._json.$comment; + } + + $id(): string | undefined { + if (typeof this._json === 'boolean') return; + return this._json.$id; + } + + $schema(): string { + if (typeof this._json === 'boolean') return 'http://json-schema.org/draft-07/schema#'; + return this._json.$schema || 'http://json-schema.org/draft-07/schema#'; + } + + additionalItems(): boolean | SchemaInterface { + if (typeof this._json === 'boolean') return this._json; + if (this._json.additionalItems === undefined) return true; + if (typeof this._json.additionalItems === 'boolean') return this._json.additionalItems; + return this.createModel(Schema, this._json.additionalItems, { pointer: `${this._meta.pointer}/additionalItems`, parent: this }); + } + + additionalProperties(): boolean | SchemaInterface { + if (typeof this._json === 'boolean') return this._json; + if (this._json.additionalProperties === undefined) return true; + if (typeof this._json.additionalProperties === 'boolean') return this._json.additionalProperties; + return this.createModel(Schema, this._json.additionalProperties, { pointer: `${this._meta.pointer}/additionalProperties`, parent: this }); + } + + allOf(): Array | undefined { + if (typeof this._json === 'boolean') return; + if (!Array.isArray(this._json.allOf)) return undefined; + return this._json.allOf.map((s, index) => this.createModel(Schema, s, { pointer: `${this._meta.pointer}/allOf/${index}`, parent: this })); + } + + anyOf(): Array | undefined { + if (typeof this._json === 'boolean') return; + if (!Array.isArray(this._json.anyOf)) return undefined; + return this._json.anyOf.map((s, index) => this.createModel(Schema, s, { pointer: `${this._meta.pointer}/anyOf/${index}`, parent: this })); + } + + const(): any { + if (typeof this._json === 'boolean') return; + return this._json.const; + } + + contains(): SchemaInterface | undefined { + if (typeof this._json === 'boolean' || typeof this._json.contains !== 'object') return; + return this.createModel(Schema, this._json.contains, { pointer: `${this._meta.pointer}/contains`, parent: this }); + } + + contentEncoding(): string | undefined { + if (typeof this._json === 'boolean') return; + return this._json.contentEncoding; + } + + contentMediaType(): string | undefined { + if (typeof this._json === 'boolean') return; + return this._json.contentMediaType; + } + + default(): any { + if (typeof this._json === 'boolean') return; + return this._json.default; + } + + definitions(): Record | undefined { + if (typeof this._json === 'boolean' || typeof this._json.definitions !== 'object') return; + return Object.entries(this._json.definitions).reduce((acc: Record, [key, s]: [string, any]) => { + acc[key] = this.createModel(Schema, s, { pointer: `${this._meta.pointer}/definitions/${key}`, parent: this }); + return acc; + }, {}); + } + + description(): string | undefined { + if (typeof this._json === 'boolean') return; + return this._json.description; + } + + dependencies(): Record> | undefined { + if (typeof this._json === 'boolean') return; + if (typeof this._json.dependencies !== 'object') return undefined; + return Object.entries(this._json.dependencies).reduce((acc: Record>, [key, s]: [string, any]) => { + acc[key] = Array.isArray(s) ? s : this.createModel(Schema, s, { pointer: `${this._meta.pointer}/dependencies/${key}`, parent: this }); + return acc; + }, {}); + } + + deprecated(): boolean { + if (typeof this._json === 'boolean') return false; + return this._json.deprecated || false; + } + + discriminator(): string | undefined { + if (typeof this._json === 'boolean') return; + return this._json.discriminator; + } + + else(): SchemaInterface | undefined { + if (typeof this._json === 'boolean' || typeof this._json.else !== 'object') return; + return this.createModel(Schema, this._json.else, { pointer: `${this._meta.pointer}/else`, parent: this }); + } + + enum(): Array | undefined { + if (typeof this._json === 'boolean') return; + return this._json.enum; + } + + examples(): Array | undefined { + if (typeof this._json === 'boolean') return; + return this._json.examples; + } + + exclusiveMaximum(): number | undefined { + if (typeof this._json === 'boolean') return; + return this._json.exclusiveMaximum; + } + + exclusiveMinimum(): number | undefined { + if (typeof this._json === 'boolean') return; + return this._json.exclusiveMinimum; + } + + format(): string | undefined { + if (typeof this._json === 'boolean') return; + return this._json.format; + } + + isBooleanSchema(): boolean { + return typeof this._json === 'boolean'; + } + + if(): SchemaInterface | undefined { + if (typeof this._json === 'boolean' || typeof this._json.if !== 'object') return; + return this.createModel(Schema, this._json.if, { pointer: `${this._meta.pointer}/if`, parent: this }); + } + + isCircular(): boolean { + if (hasRef(this._json)) return true; + let parent = this._meta.parent; + while (parent) { + if (parent._json === this._json) return true; + parent = parent._meta.parent; + } + return false; + } + + items(): SchemaInterface | Array | undefined { + if (typeof this._json === 'boolean' || typeof this._json.items !== 'object') return; + if (Array.isArray(this._json.items)) { + return this._json.items.map((s, index) => this.createModel(Schema, s, { pointer: `${this._meta.pointer}/items/${index}`, parent: this })); + } + return this.createModel(Schema, this._json.items, { pointer: `${this._meta.pointer}/items`, parent: this }); + } + + maximum(): number | undefined { + if (typeof this._json === 'boolean') return; + return this._json.maximum; + } + + maxItems(): number | undefined { + if (typeof this._json === 'boolean') return; + return this._json.maxItems; + } + + maxLength(): number | undefined { + if (typeof this._json === 'boolean') return; + return this._json.maxLength; + } + + maxProperties(): number | undefined { + if (typeof this._json === 'boolean') return; + return this._json.maxProperties; + } + + minimum(): number | undefined { + if (typeof this._json === 'boolean') return; + return this._json.minimum; + } + + minItems(): number | undefined { + if (typeof this._json === 'boolean') return; + return this._json.minItems; + } + + minLength(): number | undefined { + if (typeof this._json === 'boolean') return; + return this._json.minLength; + } + + minProperties(): number | undefined { + if (typeof this._json === 'boolean') return; + return this._json.minProperties; + } + + multipleOf(): number | undefined { + if (typeof this._json === 'boolean') return; + return this._json.multipleOf; + } + + not(): SchemaInterface | undefined { + if (typeof this._json === 'boolean' || typeof this._json.not !== 'object') return; + return this.createModel(Schema, this._json.not, { pointer: `${this._meta.pointer}/not`, parent: this }); + } + + oneOf(): Array | undefined { + if (typeof this._json === 'boolean') return; + if (!Array.isArray(this._json.oneOf)) return undefined; + return this._json.oneOf.map((s, index) => this.createModel(Schema, s, { pointer: `${this._meta.pointer}/oneOf/${index}`, parent: this })); + } + + pattern(): string | undefined { + if (typeof this._json === 'boolean') return; + return this._json.pattern; + } + + patternProperties(): Record | undefined { + if (typeof this._json === 'boolean' || typeof this._json.patternProperties !== 'object') return; + return Object.entries(this._json.patternProperties).reduce((acc: Record, [key, s]: [string, any]) => { + acc[key] = this.createModel(Schema, s, { pointer: `${this._meta.pointer}/patternProperties/${key}`, parent: this }); + return acc; + }, {}); + } + + properties(): Record | undefined { + if (typeof this._json === 'boolean' || typeof this._json.properties !== 'object') return; + return Object.entries(this._json.properties).reduce((acc: Record, [key, s]: [string, any]) => { + acc[key] = this.createModel(Schema, s, { pointer: `${this._meta.pointer}/properties/${key}`, parent: this }); + return acc; + }, {}); + } + + property(name: string): SchemaInterface | undefined { + if (typeof this._json === 'boolean' || typeof this._json.properties !== 'object' || typeof this._json.properties[name] !== 'object') return; + return this.createModel(Schema, this._json.properties[name], { pointer: `${this._meta.pointer}/properties/${name}`, parent: this }); + } + + propertyNames(): SchemaInterface | undefined { + if (typeof this._json === 'boolean' || typeof this._json.propertyNames !== 'object') return; + return this.createModel(Schema, this._json.propertyNames, { pointer: `${this._meta.pointer}/propertyNames`, parent: this }); + } + + readOnly(): boolean | undefined { + if (typeof this._json === 'boolean') return false; + return this._json.readOnly || false; + } + + required(): Array | undefined { + if (typeof this._json === 'boolean') return; + return this._json.required; + } + + then(): SchemaInterface | undefined { + if (typeof this._json === 'boolean' || typeof this._json.then !== 'object') return; + return this.createModel(Schema, this._json.then, { pointer: `${this._meta.pointer}/then`, parent: this }); + } + + title(): string | undefined { + if (typeof this._json === 'boolean') return; + return this._json.title; + } + + type(): string | Array | undefined { + if (typeof this._json === 'boolean') return; + return this._json.type; + } + + uniqueItems(): boolean | undefined { + if (typeof this._json === 'boolean') return false; + return this._json.uniqueItems || false; + } + + writeOnly(): boolean | undefined { + if (typeof this._json === 'boolean') return false; + return this._json.writeOnly || false; + } + + hasExternalDocs(): boolean { + return hasExternalDocs(this as BaseModel); + } + + externalDocs(): ExternalDocumentationInterface | undefined { + return externalDocs(this as BaseModel); + } + + extensions(): ExtensionsInterface { + return extensions(this as BaseModel); + } +} diff --git a/src/models/v3/security-requirement.ts b/src/models/v3/security-requirement.ts new file mode 100644 index 000000000..fdab68df1 --- /dev/null +++ b/src/models/v3/security-requirement.ts @@ -0,0 +1,14 @@ +import { BaseModel } from '../base'; + +import type { SecuritySchemeInterface } from '../security-scheme'; +import type { SecurityRequirementInterface } from '../security-requirement'; + +export class SecurityRequirement extends BaseModel<{ scopes?: string[], scheme: SecuritySchemeInterface }, { id?: string }> implements SecurityRequirementInterface { + scheme(): SecuritySchemeInterface { + return this._json.scheme; + } + + scopes(): string[] { + return this._json.scopes || []; + } +} \ No newline at end of file diff --git a/src/models/v3/security-scheme.ts b/src/models/v3/security-scheme.ts new file mode 100644 index 000000000..09040e9bf --- /dev/null +++ b/src/models/v3/security-scheme.ts @@ -0,0 +1,57 @@ +import { BaseModel } from '../base'; +import { OAuthFlows } from './oauth-flows'; + +import { hasDescription, description, extensions } from './mixins'; + +import type { ExtensionsInterface } from '../extensions'; +import type { SecuritySchemeInterface } from '../security-scheme'; +import type { OAuthFlowsInterface } from '../oauth-flows'; + +import type { v3 } from '../../spec-types'; + +export class SecurityScheme extends BaseModel implements SecuritySchemeInterface { + id(): string { + return this._meta.id; + } + + hasDescription(): boolean { + return hasDescription(this); + } + + description(): string | undefined { + return description(this); + } + + hasBearerFormat(): boolean { + return !!this._json.bearerFormat; + } + + bearerFormat(): string | undefined { + return this._json.bearerFormat; + } + + openIdConnectUrl(): string | undefined { + return this._json.openIdConnectUrl; + } + + scheme(): string | undefined { + return this._json.scheme; + } + + flows(): OAuthFlowsInterface | undefined { + if (!this._json.flows) return undefined; + return new OAuthFlows(this._json.flows); + } + + type(): v3.SecuritySchemeType { + return this._json.type; + } + + in(): v3.SecuritySchemaLocation | undefined { + return this._json.in; + } + + extensions(): ExtensionsInterface { + return extensions(this); + } +} diff --git a/src/models/v3/server-variable.ts b/src/models/v3/server-variable.ts new file mode 100644 index 000000000..75c21715c --- /dev/null +++ b/src/models/v3/server-variable.ts @@ -0,0 +1,46 @@ +import { BaseModel } from '../base'; + +import { hasDescription, description, extensions } from './mixins'; + +import type { ExtensionsInterface } from '../extensions'; +import type { ServerVariableInterface } from '../server-variable'; + +import type { v3 } from '../../spec-types'; + +export class ServerVariable extends BaseModel implements ServerVariableInterface { + id(): string { + return this._meta.id; + } + + hasDescription(): boolean { + return hasDescription(this); + } + + description(): string | undefined { + return description(this); + } + + hasDefaultValue(): boolean { + return !!this._json.default; + } + + defaultValue(): string | undefined { + return this._json.default; + } + + hasAllowedValues(): boolean { + return !!this._json.enum; + } + + allowedValues(): Array { + return this._json.enum || []; + } + + examples(): Array { + return this._json.examples || []; + } + + extensions(): ExtensionsInterface { + return extensions(this); + } +} diff --git a/src/models/v3/server.ts b/src/models/v3/server.ts new file mode 100644 index 000000000..0086bd303 --- /dev/null +++ b/src/models/v3/server.ts @@ -0,0 +1,120 @@ +import { BaseModel } from '../base'; +import { Channels } from '../channels'; +import { Channel } from './channel'; +import { Messages } from '../messages'; +import { Operations } from '../operations'; +import { SecurityScheme } from './security-scheme'; +import { ServerVariables } from '../server-variables'; +import { ServerVariable } from './server-variable'; +import { SecurityRequirements } from '../security-requirements'; +import { SecurityRequirement } from './security-requirement'; + +import { bindings, hasDescription, description, extensions } from './mixins'; +import { tilde } from '../../utils'; + +import type { ChannelsInterface } from '../channels'; +import type { ChannelInterface } from '../channel'; +import type { OperationsInterface } from '../operations'; +import type { OperationInterface } from '../operation'; +import type { MessagesInterface } from '../messages'; +import type { MessageInterface } from '../message'; +import type { ServerInterface } from '../server'; +import type { ServerVariablesInterface } from '../server-variables'; +import type { ExtensionsInterface } from '../extensions'; +import type { BindingsInterface } from '../bindings'; + +import type { v3 } from '../../spec-types'; + +export class Server extends BaseModel implements ServerInterface { + id(): string { + return this._meta.id; + } + + url(): string { + return this._json.url; + } + + protocol(): string { + return this._json.protocol; + } + + hasProtocolVersion(): boolean { + return !!this._json.protocolVersion; + } + + protocolVersion(): string | undefined { + return this._json.protocolVersion; + } + + hasDescription(): boolean { + return hasDescription(this); + } + + description(): string | undefined { + return description(this); + } + + channels(): ChannelsInterface { + const channels: ChannelInterface[] = []; + Object.entries(this._meta.asyncapi?.parsed?.channels || {}).forEach(([channelAddress, channel]: [string, any]) => { + const allowedServers: string[] = channel.servers || []; + if (allowedServers.length === 0 || allowedServers.includes(this._meta.id)) { + channels.push(this.createModel(Channel, channel, { id: channelAddress, address: channelAddress, pointer: `/channels/${tilde(channelAddress)}` })); + } + }); + return new Channels(channels); + } + + operations(): OperationsInterface { + const operations: OperationInterface[] = []; + const operationsData: v3.OperationObject[] = []; + this.channels().forEach((channel: ChannelInterface) => { + channel.operations().forEach((op: OperationInterface) => { + if (!operationsData.includes(op.json())) { + operations.push(op); + operationsData.push(op.json()); + } + }); + }) + return new Operations(operations); + } + + messages(): MessagesInterface { + const messages: MessageInterface[] = []; + this.operations().forEach(operation => messages.push(...operation.messages().all())); + return new Messages(messages); + } + + variables(): ServerVariablesInterface { + return new ServerVariables( + Object.entries(this._json.variables || {}).map(([serverVariableName, serverVariable]) => { + return this.createModel(ServerVariable, serverVariable, { + id: serverVariableName, + pointer: `${this._meta.pointer}/variables/${serverVariableName}` + }); + }) + ); + } + + security(): SecurityRequirements[] { + const securitySchemes = (this._meta?.asyncapi?.parsed?.components?.securitySchemes || {}) as Record; + return (this._json.security || []).map((requirement, index) => { + const requirements: SecurityRequirement[] = []; + Object.entries(requirement).forEach(([security, scopes]) => { + const scheme = this.createModel(SecurityScheme, securitySchemes[security], { id: security, pointer: `/components/securitySchemes/${security}` }); + requirements.push( + this.createModel(SecurityRequirement, { scheme, scopes }, { id: security, pointer: `${this.meta().pointer}/security/${index}/${security}` }) + ); + }); + return new SecurityRequirements(requirements); + }); + } + + bindings(): BindingsInterface { + return bindings(this); + } + + extensions(): ExtensionsInterface { + return extensions(this); + } +} diff --git a/src/spec-types/v3.ts b/src/spec-types/v3.ts index d9c114224..7fb0ee76c 100644 --- a/src/spec-types/v3.ts +++ b/src/spec-types/v3.ts @@ -8,8 +8,126 @@ export interface AsyncAPIObject extends SpecificationExtensions { asyncapi: AsyncAPIVersion; id?: Identifier; defaultContentType?: DefaultContentType; + operations?: OperationsObject; + channels?: ChannelsObject; + components?: ComponentsObject; + servers?: ServerObject[]; } +export type ChannelsObject = Record; + +export interface ChannelObject extends SpecificationExtensions { + address?: string | null; + messages?: MessagesObject; + description?: string; + servers?: Array; + parameters?: ParametersObject; + bindings?: ChannelBindingsObject | ReferenceObject; + tags?: TagsObject; + externalDocs?: ExternalDocumentationObject; +} + +export interface ChannelBindingsObject extends SpecificationExtensions { + http?: Binding; + ws?: Binding; + kafka?: Binding; + anypointmq?: Binding; + amqp?: Binding; + amqp1?: Binding; + mqtt?: Binding; + mqtt5?: Binding; + nats?: Binding; + jms?: Binding; + sns?: Binding; + sqs?: Binding; + stomp?: Binding; + redis?: Binding; + mercure?: Binding; + ibmmq?: Binding; +} + +export type MessagesObject = Record; + +export interface MessageObject extends MessageTraitObject, SpecificationExtensions { + payload?: any; + traits?: Array; +} + +export interface MessageTraitObject extends SpecificationExtensions { + messageId?: string; + headers?: SchemaObject; + correlationId?: CorrelationIDObject | ReferenceObject; + schemaFormat?: string; + contentType?: string; + name?: string; + title?: string; + summary?: string; + description?: string; + tags?: TagsObject; + externalDocs?: ExternalDocumentationObject; + bindings?: MessageBindingsObject | ReferenceObject; + examples?: Array; +} + +export interface MessageExampleObject extends SpecificationExtensions { + name?: string; + summary?: string; + headers?: Record; + payload?: any; +} + +export interface MessageBindingsObject extends SpecificationExtensions { + http?: Binding; + ws?: Binding; + kafka?: Binding; + anypointmq?: Binding; + amqp?: Binding; + amqp1?: Binding; + mqtt?: Binding; + mqtt5?: Binding; + nats?: Binding; + jms?: Binding; + sns?: Binding; + sqs?: Binding; + stomp?: Binding; + redis?: Binding; + mercure?: Binding; + ibmmq?: Binding; +} + +export type ParametersObject = Record; + +export interface ParameterObject extends SpecificationExtensions { + description?: string; + schema?: SchemaObject; + location?: string; +} + +export type OperationsObject = Record; + +export interface OperationObject extends SpecificationExtensions { + action: 'send' | 'receive'; + channel: ChannelObject; + summary?: string; + description?: string; + security?: Array; + tags?: TagsObject; + externalDocs?: ExternalDocumentationObject; + bindings?: OperationBindingsObject | ReferenceObject; + traits?: OperationTraitObject[]; +}; + +export interface OperationTraitObject extends SpecificationExtensions { + action?: string; + channel?: ReferenceObject; + summary?: string; + description?: string; + security?: Array; + tags?: TagsObject; + externalDocs?: ExternalDocumentationObject; + bindings?: OperationBindingsObject | ReferenceObject; +}; + export type TagsObject = Array; export interface TagObject extends SpecificationExtensions { @@ -23,6 +141,193 @@ export interface ExternalDocumentationObject extends SpecificationExtensions { description?: string; } +export interface ComponentsObject extends SpecificationExtensions { + channels?: Record; + servers?: Record; + schemas?: Record; + messages?: Record; + securitySchemes?: Record; + parameters?: Record; + serverVariables?: Record; + correlationIds?: Record; + operationTraits?: Record; + messageTraits?: Record; + serverBindings?: Record; + channelBindings?: Record; + operationBindings?: Record; + messageBindings?: Record; +} + +export type ServersObject = Record; + +export interface ServerObject extends SpecificationExtensions { + url: string; + protocol: string; + protocolVersion?: string; + description?: string; + variables?: Record; + security?: Array; + bindings?: ServerBindingsObject | ReferenceObject; +} + +export interface ServerVariableObject extends SpecificationExtensions { + enum?: Array; + default?: string; + description?: string; + examples?: Array; +} + +export interface ServerBindingsObject extends SpecificationExtensions { + http?: Binding; + ws?: Binding; + kafka?: Binding; + anypointmq?: Binding; + amqp?: Binding; + amqp1?: Binding; + mqtt?: Binding; + mqtt5?: Binding; + nats?: Binding; + jms?: Binding; + sns?: Binding; + sqs?: Binding; + stomp?: Binding; + redis?: Binding; + mercure?: Binding; + ibmmq?: Binding; +} + +export interface SecuritySchemeObject extends SpecificationExtensions { + type: SecuritySchemeType; + description?: string; + name?: string; + in?: 'user' | 'password' | 'query' | 'header' | 'cookie'; + scheme?: string; + bearerFormat?: string; + flows?: OAuthFlowsObject; + openIdConnectUrl?: string; +} + +export type SecuritySchemeType = + | 'userPassword' + | 'apiKey' + | 'X509' + | 'symmetricEncryption' + | 'asymmetricEncryption' + | 'httpApiKey' + | 'http' + | 'oauth2' + | 'openIdConnect' + | 'plain' + | 'scramSha256' + | 'scramSha512' + | 'gssapi'; + +export type SecuritySchemaLocation = + | 'user' + | 'password' + | 'query' + | 'header' + | 'header' + | 'cookie'; + +export interface SecuritySchemeObjectBase extends SpecificationExtensions { + description?: string; +} + +export interface SecuritySchemeObjectUserPassword extends SecuritySchemeObjectBase, SpecificationExtensions { + type: 'userPassword'; +} + +export interface SecuritySchemeObjectApiKey extends SecuritySchemeObjectBase, SpecificationExtensions { + type: 'apiKey'; + in: 'user' | 'password'; +} + +export interface SecuritySchemeObjectX509 extends SecuritySchemeObjectBase, SpecificationExtensions { + type: 'X509'; +} + +export interface SecuritySchemeObjectSymetricEncryption extends SecuritySchemeObjectBase, SpecificationExtensions { + type: 'symmetricEncryption'; +} + +export interface SecuritySchemeObjectAsymetricEncryption extends SecuritySchemeObjectBase, SpecificationExtensions { + type: 'asymmetricEncryption'; +} + +export interface SecuritySchemeObjectHttpApiKey extends SecuritySchemeObjectBase, SpecificationExtensions { + type: 'httpApiKey'; + name: string; + in: 'query' | 'header' | 'cookie'; +} + +export interface SecuritySchemeObjectHttp extends SecuritySchemeObjectBase, SpecificationExtensions { + type: 'http'; + scheme: string; + bearerFormat?: string; +} + +export interface SecuritySchemeObjectOauth2 extends SecuritySchemeObjectBase, SpecificationExtensions { + type: 'oauth2'; + flows: OAuthFlowsObject; +} + +export interface SecuritySchemeObjectOpenIdConnect extends SecuritySchemeObjectBase, SpecificationExtensions { + type: 'openIdConnect'; + openIdConnectUrl: string; +} + +export interface SecuritySchemeObjectPlain extends SecuritySchemeObjectBase, SpecificationExtensions { + type: 'plain'; +} + +export interface SecuritySchemeObjectScramSha256 extends SecuritySchemeObjectBase, SpecificationExtensions { + type: 'scramSha256'; +} + +export interface SecuritySchemeObjectScramSha512 extends SecuritySchemeObjectBase, SpecificationExtensions { + type: 'scramSha512'; +} + +export interface SecuritySchemeObjectGssapi extends SecuritySchemeObjectBase, SpecificationExtensions { + type: 'gssapi'; +} + +export interface OAuthFlowsObject extends SpecificationExtensions { + implicit?: OAuthFlowObjectImplicit; + password?: OAuthFlowObjectPassword; + clientCredentials?: OAuthFlowObjectClientCredentials; + authorizationCode?: OAuthFlowObjectAuthorizationCode; +} + +export type OAuthFlowObject = + & OAuthFlowObjectImplicit + & OAuthFlowObjectPassword + & OAuthFlowObjectClientCredentials + & OAuthFlowObjectAuthorizationCode; + +export interface OAuthFlowObjectBase extends SpecificationExtensions { + refreshUrl?: string; + scopes: Record; +} + +export interface OAuthFlowObjectImplicit extends OAuthFlowObjectBase, SpecificationExtensions { + authorizationUrl: string; +} + +export interface OAuthFlowObjectPassword extends OAuthFlowObjectBase, SpecificationExtensions { + tokenUrl: string; +} + +export interface OAuthFlowObjectClientCredentials extends OAuthFlowObjectBase, SpecificationExtensions { + tokenUrl: string; +} + +export interface OAuthFlowObjectAuthorizationCode extends OAuthFlowObjectBase, SpecificationExtensions { + authorizationUrl: string; + tokenUrl: string; +} + export type SchemaObject = AsyncAPISchemaObject | ReferenceObject; export type AsyncAPISchemaObject = AsyncAPISchemaDefinition | boolean; @@ -110,4 +415,30 @@ export type SpecificationExtension = T; export interface ReferenceObject { $ref: string; +} + +export type SecurityRequirementObject = Record>; + +export interface OperationBindingsObject extends SpecificationExtensions { + http?: Binding; + ws?: Binding; + kafka?: Binding; + anypointmq?: Binding; + amqp?: Binding; + amqp1?: Binding; + mqtt?: Binding; + mqtt5?: Binding; + nats?: Binding; + jms?: Binding; + sns?: Binding; + sqs?: Binding; + stomp?: Binding; + redis?: Binding; + mercure?: Binding; + ibmmq?: Binding; +} + +export interface CorrelationIDObject extends SpecificationExtensions { + location: string; + description?: string; } \ No newline at end of file From 1af3ca8bcb2db3162d3701bd12d670b42f866a5b Mon Sep 17 00:00:00 2001 From: Fran Mendez Date: Thu, 13 Oct 2022 17:31:19 +0200 Subject: [PATCH 03/17] Fixing various things found during v3 development --- src/custom-operations/index.ts | 3 ++- src/document.ts | 3 ++- src/models/base.ts | 4 ++-- src/models/channel.ts | 2 +- src/models/operation-trait.ts | 7 +++---- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/custom-operations/index.ts b/src/custom-operations/index.ts index 5bf91a714..1577f3142 100644 --- a/src/custom-operations/index.ts +++ b/src/custom-operations/index.ts @@ -7,6 +7,7 @@ import type { Parser } from '../parser'; import type { ParseOptions } from '../parse'; import type { AsyncAPIDocumentInterface } from '../models'; import type { DetailedAsyncAPI } from '../types'; +import { v2 } from 'spec-types'; export async function customOperations(parser: Parser, document: AsyncAPIDocumentInterface, detailed: DetailedAsyncAPI, options: ParseOptions): Promise { switch (detailed.semver.major) { @@ -20,7 +21,7 @@ async function operationsV2(parser: Parser, document: AsyncAPIDocumentInterface, anonymousNaming(document); if (options.applyTraits) { - applyTraitsV2(detailed.parsed); + applyTraitsV2(detailed.parsed as v2.AsyncAPIObject); } if (options.parseSchemas) { await parseSchemasV2(parser, detailed); diff --git a/src/document.ts b/src/document.ts index 1ae04f9b3..b532f535c 100644 --- a/src/document.ts +++ b/src/document.ts @@ -9,11 +9,12 @@ import { import type { AsyncAPIDocumentInterface } from './models'; import type { DetailedAsyncAPI } from './types'; +import { v2 } from 'spec-types'; export function createAsyncAPIDocument(asyncapi: DetailedAsyncAPI): AsyncAPIDocumentInterface { switch (asyncapi.semver.major) { case 2: - return new AsyncAPIDocumentV2(asyncapi.parsed, { asyncapi, pointer: '/' }); + return new AsyncAPIDocumentV2(asyncapi.parsed as v2.AsyncAPIObject, { asyncapi, pointer: '/' }); // case 3: // return new AsyncAPIDocumentV3(asyncapi.parsed, { asyncapi, pointer: '/' }); default: diff --git a/src/models/base.ts b/src/models/base.ts index d82bfce5c..6a4062efc 100644 --- a/src/models/base.ts +++ b/src/models/base.ts @@ -3,7 +3,7 @@ import type { DetailedAsyncAPI } from '../types'; export interface ModelMetadata { asyncapi: DetailedAsyncAPI; - pointer: string; + pointer?: string; } export abstract class BaseModel = {}> { @@ -28,7 +28,7 @@ export abstract class BaseModel Date: Thu, 13 Oct 2022 17:31:35 +0200 Subject: [PATCH 04/17] Fixing v2 tests --- test/models/v2/asyncapi.spec.ts | 10 ++++----- test/models/v2/bindings.spec.ts | 2 +- test/models/v2/channel-parameters.spec.ts | 2 +- test/models/v2/channel.spec.ts | 8 +++---- test/models/v2/channels.spec.ts | 2 +- test/models/v2/components.spec.ts | 22 ++++++++++---------- test/models/v2/info.spec.ts | 2 +- test/models/v2/message-examples.spec.ts | 2 +- test/models/v2/message-trait.spec.ts | 2 +- test/models/v2/message-traits.spec.ts | 2 +- test/models/v2/message.spec.ts | 8 +++---- test/models/v2/messages.spec.ts | 2 +- test/models/v2/operation-trait.spec.ts | 22 +++++++------------- test/models/v2/operation-traits.spec.ts | 2 +- test/models/v2/operation.spec.ts | 16 +++++--------- test/models/v2/operations.spec.ts | 2 +- test/models/v2/security-requirements.spec.ts | 2 +- test/models/v2/server.spec.ts | 22 ++++++++++---------- test/models/v2/servers.spec.ts | 2 +- test/models/v2/tags.spec.ts | 2 +- 20 files changed, 61 insertions(+), 73 deletions(-) diff --git a/test/models/v2/asyncapi.spec.ts b/test/models/v2/asyncapi.spec.ts index 61f04e708..19e6f6bdf 100644 --- a/test/models/v2/asyncapi.spec.ts +++ b/test/models/v2/asyncapi.spec.ts @@ -1,11 +1,11 @@ import { AsyncAPIDocument } from '../../../src/models/v2/asyncapi'; -import { Channels } from '../../../src/models/v2/channels'; +import { Channels } from '../../../src/models/channels'; import { Components } from '../../../src/models/v2/components'; import { Info } from '../../../src/models/v2/info'; -import { Messages } from '../../../src/models/v2/messages'; -import { Operations } from '../../../src/models/v2/operations'; -import { SecuritySchemes } from '../../../src/models/v2/security-schemes'; -import { Servers } from '../../../src/models/v2/servers'; +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'; diff --git a/test/models/v2/bindings.spec.ts b/test/models/v2/bindings.spec.ts index 4075508a6..9ce1a7741 100644 --- a/test/models/v2/bindings.spec.ts +++ b/test/models/v2/bindings.spec.ts @@ -1,6 +1,6 @@ import { Bindings } from '../../../src/models/v2/bindings'; import { Binding } from '../../../src/models/v2/binding'; -import { Extensions } from '../../../src/models/v2/extensions'; +import { Extensions } from '../../../src/models/extensions'; import { Extension } from '../../../src/models/v2/extension'; const binding = { diff --git a/test/models/v2/channel-parameters.spec.ts b/test/models/v2/channel-parameters.spec.ts index d4417e2b5..f600c8b7c 100644 --- a/test/models/v2/channel-parameters.spec.ts +++ b/test/models/v2/channel-parameters.spec.ts @@ -1,4 +1,4 @@ -import { ChannelParameters } from '../../../src/models/v2/channel-parameters'; +import { ChannelParameters } from '../../../src/models/channel-parameters'; import { ChannelParameter } from '../../../src/models/v2/channel-parameter'; const channelParameter = { diff --git a/test/models/v2/channel.spec.ts b/test/models/v2/channel.spec.ts index 279969a8d..2d3ea0258 100644 --- a/test/models/v2/channel.spec.ts +++ b/test/models/v2/channel.spec.ts @@ -1,11 +1,11 @@ import { Channel } from '../../../src/models/v2/channel'; -import { ChannelParameters } from '../../../src/models/v2/channel-parameters'; +import { ChannelParameters } from '../../../src/models/channel-parameters'; import { ChannelParameter } from '../../../src/models/v2/channel-parameter'; -import { Operations } from '../../../src/models/v2/operations'; +import { Operations } from '../../../src/models/operations'; import { Operation } from '../../../src/models/v2/operation'; -import { Messages } from '../../../src/models/v2/messages'; +import { Messages } from '../../../src/models/messages'; import { Message } from '../../../src/models/v2/message'; -import { Servers } from '../../../src/models/v2/servers'; +import { Servers } from '../../../src/models/servers'; import { Server } from '../../../src/models/v2/server'; import { serializeInput, assertBindings, assertDescription, assertExtensions } from './utils'; diff --git a/test/models/v2/channels.spec.ts b/test/models/v2/channels.spec.ts index 709355fa2..8fa46e1f5 100644 --- a/test/models/v2/channels.spec.ts +++ b/test/models/v2/channels.spec.ts @@ -1,4 +1,4 @@ -import { Channels } from '../../../src/models/v2/channels'; +import { Channels } from '../../../src/models/channels'; import { Channel } from '../../../src/models/v2/channel'; const channel = { diff --git a/test/models/v2/components.spec.ts b/test/models/v2/components.spec.ts index bc1a82b35..1fc09b4be 100644 --- a/test/models/v2/components.spec.ts +++ b/test/models/v2/components.spec.ts @@ -11,20 +11,20 @@ import { Server } from '../../../src/models/v2/server'; import { ServerVariable } from '../../../src/models/v2/server-variable'; import { SecurityScheme } from '../../../src/models/v2/security-scheme'; import { BaseModel, ModelMetadata } from '../../../src/models'; -import { Servers } from '../../../src/models/v2/servers'; -import { Channels } from '../../../src/models/v2/channels'; -import { Messages } from '../../../src/models/v2/messages'; +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/v2/schemas'; -import { ChannelParameters } from '../../../src/models/v2/channel-parameters'; -import { ServerVariables } from '../../../src/models/v2/server-variables'; -import { OperationTraits } from '../../../src/models/v2/operation-traits'; -import { MessageTraits } from '../../../src/models/v2/message-traits'; -import { CorrelationIds } from '../../../src/models/v2/correlation-ids'; -import { SecuritySchemes } from '../../../src/models/v2/security-schemes'; +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/v2/operation'; -import { Operations } from '../../../src/models/v2/operations'; +import { Operations } from '../../../src/models/operations'; import { OperationAction } from '../../../src/models/operation'; import { serializeInput, assertExtensions } from './utils'; import type { v2 } from '../../../src/spec-types'; diff --git a/test/models/v2/info.spec.ts b/test/models/v2/info.spec.ts index 9c581b355..49c1d8c73 100644 --- a/test/models/v2/info.spec.ts +++ b/test/models/v2/info.spec.ts @@ -2,7 +2,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 { Tags } from '../../../src/models/v2/tags'; +import { Tags } from '../../../src/models/tags'; import { Tag } from '../../../src/models/v2/tag'; import { createDetailedAsyncAPI } from '../../../src/utils'; diff --git a/test/models/v2/message-examples.spec.ts b/test/models/v2/message-examples.spec.ts index 380d39e75..324b9dd71 100644 --- a/test/models/v2/message-examples.spec.ts +++ b/test/models/v2/message-examples.spec.ts @@ -1,4 +1,4 @@ -import { MessageExamples } from '../../../src/models/v2/message-examples'; +import { MessageExamples } from '../../../src/models/message-examples'; import { MessageExample } from '../../../src/models/v2/message-example'; const messageExample = { diff --git a/test/models/v2/message-trait.spec.ts b/test/models/v2/message-trait.spec.ts index e2b2bc0d6..e397b133c 100644 --- a/test/models/v2/message-trait.spec.ts +++ b/test/models/v2/message-trait.spec.ts @@ -1,5 +1,5 @@ import { CorrelationId } from '../../../src/models/v2/correlation-id'; -import { MessageExamples } from '../../../src/models/v2/message-examples'; +import { MessageExamples } from '../../../src/models/message-examples'; import { MessageExample } from '../../../src/models/v2/message-example'; import { MessageTrait } from '../../../src/models/v2/message-trait'; import { Schema } from '../../../src/models/v2/schema'; diff --git a/test/models/v2/message-traits.spec.ts b/test/models/v2/message-traits.spec.ts index 00f8539d6..7ab49a4d4 100644 --- a/test/models/v2/message-traits.spec.ts +++ b/test/models/v2/message-traits.spec.ts @@ -1,4 +1,4 @@ -import { MessageTraits } from '../../../src/models/v2/message-traits'; +import { MessageTraits } from '../../../src/models/message-traits'; import { MessageTrait } from '../../../src/models/v2/message-trait'; const messageTrait = { diff --git a/test/models/v2/message.spec.ts b/test/models/v2/message.spec.ts index 111031476..bf4bb9d95 100644 --- a/test/models/v2/message.spec.ts +++ b/test/models/v2/message.spec.ts @@ -1,12 +1,12 @@ -import { Channels } from '../../../src/models/v2/channels'; +import { Channels } from '../../../src/models/channels'; import { Channel } from '../../../src/models/v2/channel'; import { Message } from '../../../src/models/v2/message'; -import { MessageTraits } from '../../../src/models/v2/message-traits'; +import { MessageTraits } from '../../../src/models/message-traits'; import { MessageTrait } from '../../../src/models/v2/message-trait'; -import { Operations } from '../../../src/models/v2/operations'; +import { Operations } from '../../../src/models/operations'; import { Operation } from '../../../src/models/v2/operation'; import { Schema } from '../../../src/models/v2/schema'; -import { Servers } from '../../../src/models/v2/servers'; +import { Servers } from '../../../src/models/servers'; import { Server } from '../../../src/models/v2/server'; import { assertBindings, assertDescription, assertExtensions, assertExternalDocumentation, assertTags } from './utils'; diff --git a/test/models/v2/messages.spec.ts b/test/models/v2/messages.spec.ts index 28df7a65f..9cb2d1842 100644 --- a/test/models/v2/messages.spec.ts +++ b/test/models/v2/messages.spec.ts @@ -1,4 +1,4 @@ -import { Messages } from '../../../src/models/v2/messages'; +import { Messages } from '../../../src/models/messages'; import { Message } from '../../../src/models/v2/message'; const messages = { diff --git a/test/models/v2/operation-trait.spec.ts b/test/models/v2/operation-trait.spec.ts index 2c007ae4e..538c3d043 100644 --- a/test/models/v2/operation-trait.spec.ts +++ b/test/models/v2/operation-trait.spec.ts @@ -1,50 +1,44 @@ import { OperationTrait } from '../../../src/models/v2/operation-trait'; import { SecurityRequirement } from '../../../src/models/v2/security-requirement'; -import { SecurityRequirements } from '../../../src/models/v2/security-requirements'; +import { SecurityRequirements } from '../../../src/models/security-requirements'; import { SecurityScheme } from '../../../src/models/v2/security-scheme'; import { assertBindings, assertDescription, assertExtensions, assertExternalDocumentation, assertTags } from './utils'; describe('OperationTrait model', function() { describe('.id()', function() { - it('should return id of model', function() { - const doc = {}; - const d = new OperationTrait(doc, { asyncapi: {} as any, pointer: '', id: 'trait', action: 'publish' }); - expect(d.id()).toEqual('trait'); - }); - - it('should reuse operationId', function() { + it('should return operationId', function() { const doc = { operationId: '...' }; const d = new OperationTrait(doc); expect(d.id()).toEqual(doc.operationId); }); }); - describe('.hasOperationId()', function() { + describe('.hasId()', function() { it('should return true when there is a value', function() { const doc = { operationId: '...' }; const d = new OperationTrait(doc); - expect(d.hasOperationId()).toEqual(true); + expect(d.hasId()).toEqual(true); }); it('should return false when there is no value', function() { const doc = {}; const d = new OperationTrait(doc); - expect(d.hasOperationId()).toEqual(false); + expect(d.hasId()).toEqual(false); }); }); - describe('.operationId()', function() { + describe('.id()', function() { it('should return the value', function() { const doc = { operationId: '...' }; const d = new OperationTrait(doc); - expect(d.operationId()).toEqual(doc.operationId); + expect(d.id()).toEqual(doc.operationId); }); it('should return undefined when there is no value', function() { const doc = {}; const d = new OperationTrait(doc); - expect(d.operationId()).toBeUndefined(); + expect(d.id()).toBeUndefined(); }); }); diff --git a/test/models/v2/operation-traits.spec.ts b/test/models/v2/operation-traits.spec.ts index bf0e701d8..ebad3add9 100644 --- a/test/models/v2/operation-traits.spec.ts +++ b/test/models/v2/operation-traits.spec.ts @@ -1,4 +1,4 @@ -import { OperationTraits } from '../../../src/models/v2/operation-traits'; +import { OperationTraits } from '../../../src/models/operation-traits'; import { OperationTrait } from '../../../src/models/v2/operation-trait'; const operationTrait = { diff --git a/test/models/v2/operation.spec.ts b/test/models/v2/operation.spec.ts index 28b6d24cf..a01545413 100644 --- a/test/models/v2/operation.spec.ts +++ b/test/models/v2/operation.spec.ts @@ -1,24 +1,18 @@ -import { Channels } from '../../../src/models/v2/channels'; +import { Channels } from '../../../src/models/channels'; import { Channel } from '../../../src/models/v2/channel'; import { Operation } from '../../../src/models/v2/operation'; -import { OperationTraits } from '../../../src/models/v2/operation-traits'; +import { OperationTraits } from '../../../src/models/operation-traits'; import { OperationTrait } from '../../../src/models/v2/operation-trait'; -import { Messages } from '../../../src/models/v2/messages'; +import { Messages } from '../../../src/models/messages'; import { Message } from '../../../src/models/v2/message'; -import { Servers } from '../../../src/models/v2/servers'; +import { Servers } from '../../../src/models/servers'; import { Server } from '../../../src/models/v2/server'; import { assertBindings, assertDescription, assertExtensions, assertExternalDocumentation, assertTags } from './utils'; describe('Operation model', function() { describe('.id()', function() { - it('should return id of model', function() { - const doc = {}; - const d = new Operation(doc, { asyncapi: {} as any, pointer: '', id: 'operation', action: 'publish' }); - expect(d.id()).toEqual('operation'); - }); - - it('should reuse operationId', function() { + it('should return operationId', function() { const doc = { operationId: '...' }; const d = new Operation(doc); expect(d.id()).toEqual(doc.operationId); diff --git a/test/models/v2/operations.spec.ts b/test/models/v2/operations.spec.ts index 00a8460a2..3d342bbaf 100644 --- a/test/models/v2/operations.spec.ts +++ b/test/models/v2/operations.spec.ts @@ -1,4 +1,4 @@ -import { Operations } from '../../../src/models/v2/operations'; +import { Operations } from '../../../src/models/operations'; import { Operation } from '../../../src/models/v2/operation'; const operation = { diff --git a/test/models/v2/security-requirements.spec.ts b/test/models/v2/security-requirements.spec.ts index d684f981a..3cd270a27 100644 --- a/test/models/v2/security-requirements.spec.ts +++ b/test/models/v2/security-requirements.spec.ts @@ -1,4 +1,4 @@ -import { SecurityRequirements } from '../../../src/models/v2/security-requirements'; +import { SecurityRequirements } from '../../../src/models/security-requirements'; import { SecurityRequirement } from '../../../src/models/v2/security-requirement'; const requirementItem = new SecurityRequirement({} as any, { id: 'test' } as any); diff --git a/test/models/v2/server.spec.ts b/test/models/v2/server.spec.ts index eff3789dc..97e429ea4 100644 --- a/test/models/v2/server.spec.ts +++ b/test/models/v2/server.spec.ts @@ -1,17 +1,17 @@ -import { Channels } from '../../../src/models/v2/channels'; +import { Channels } from '../../../src/models/channels'; import { Channel } from '../../../src/models/v2/channel'; -import { Messages } from '../../../src/models/v2/messages'; +import { Messages } from '../../../src/models/messages'; import { Message } from '../../../src/models/v2/message'; -import { Operations } from '../../../src/models/v2/operations'; +import { Operations } from '../../../src/models/operations'; import { Operation } from '../../../src/models/v2/operation'; import { Server } from '../../../src/models/v2/server'; -import { ServerVariables } from '../../../src/models/v2/server-variables'; +import { ServerVariables } from '../../../src/models/server-variables'; import { SecurityScheme } from '../../../src/models/v2/security-scheme'; import { serializeInput, assertBindings, assertDescription, assertExtensions } from './utils'; import type { v2 } from '../../../src/spec-types'; -import { SecurityRequirements } from '../../../src/models/v2/security-requirements'; +import { SecurityRequirements } from '../../../src/models/security-requirements'; import { SecurityRequirement } from '../../../src/models/v2/security-requirement'; const doc = { @@ -109,7 +109,7 @@ describe('Server Model', function () { expect(d.operations()).toBeInstanceOf(Operations); expect(d.operations().all()).toHaveLength(1); expect(d.operations().all()[0]).toBeInstanceOf(Operation); - expect(d.operations().all()[0].operationId()).toEqual('1'); + expect(d.operations().all()[0].id()).toEqual('1'); }); it('should return collection of channels - multiple channels', function() { @@ -118,9 +118,9 @@ describe('Server Model', function () { expect(d.operations()).toBeInstanceOf(Operations); expect(d.operations().all()).toHaveLength(2); expect(d.operations().all()[0]).toBeInstanceOf(Operation); - expect(d.operations().all()[0].operationId()).toEqual('1'); + expect(d.operations().all()[0].id()).toEqual('1'); expect(d.operations().all()[1]).toBeInstanceOf(Operation); - expect(d.operations().all()[1].operationId()).toEqual('2'); + expect(d.operations().all()[1].id()).toEqual('2'); }); it('should return collection of channels - server available only in particular channel', function() { @@ -129,11 +129,11 @@ describe('Server Model', function () { expect(d.operations()).toBeInstanceOf(Operations); expect(d.operations().all()).toHaveLength(3); expect(d.operations().all()[0]).toBeInstanceOf(Operation); - expect(d.operations().all()[0].operationId()).toEqual('1'); + expect(d.operations().all()[0].id()).toEqual('1'); expect(d.operations().all()[1]).toBeInstanceOf(Operation); - expect(d.operations().all()[1].operationId()).toEqual('2'); + expect(d.operations().all()[1].id()).toEqual('2'); expect(d.operations().all()[2]).toBeInstanceOf(Operation); - expect(d.operations().all()[2].operationId()).toEqual('3'); + expect(d.operations().all()[2].id()).toEqual('3'); }); }); diff --git a/test/models/v2/servers.spec.ts b/test/models/v2/servers.spec.ts index 8d335d4cb..a4938a2b7 100644 --- a/test/models/v2/servers.spec.ts +++ b/test/models/v2/servers.spec.ts @@ -1,4 +1,4 @@ -import { Servers } from '../../../src/models/v2/servers'; +import { Servers } from '../../../src/models/servers'; import { Server } from '../../../src/models/v2/server'; const doc = { diff --git a/test/models/v2/tags.spec.ts b/test/models/v2/tags.spec.ts index ac8022995..d3c599b9b 100644 --- a/test/models/v2/tags.spec.ts +++ b/test/models/v2/tags.spec.ts @@ -1,4 +1,4 @@ -import { Tags } from '../../../src/models/v2/tags'; +import { Tags } from '../../../src/models/tags'; import { Tag } from '../../../src/models/v2/tag'; const tag = { From 5f3c1befba8dd8fa233ee5f4adcb7a184bf6f62c Mon Sep 17 00:00:00 2001 From: Fran Mendez Date: Thu, 13 Oct 2022 17:31:54 +0200 Subject: [PATCH 05/17] Adding npm run dev --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 9d77182d9..74c5ead08 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "README.md" ], "scripts": { + "dev": "tsc --watch", "build": "npm run build:esm && npm run build:cjs && npm run build:browser", "build:esm": "tsc", "build:cjs": "tsc --project ./tsconfig.cjs.json", From 46889bf8734e5f6318f4b09eb59b922c73278e30 Mon Sep 17 00:00:00 2001 From: Fran Mendez Date: Thu, 13 Oct 2022 17:59:50 +0200 Subject: [PATCH 06/17] Fix linting errors --- src/models/v3/asyncapi.ts | 2 +- src/models/v3/channel.ts | 5 ++--- src/models/v3/server.ts | 2 +- src/spec-types/v3.ts | 4 ++-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/models/v3/asyncapi.ts b/src/models/v3/asyncapi.ts index d6789556a..56e5ff4e9 100644 --- a/src/models/v3/asyncapi.ts +++ b/src/models/v3/asyncapi.ts @@ -36,7 +36,7 @@ export class AsyncAPIDocument extends BaseModel implements As operations() { const operations: OperationInterface[] = Object.entries(this._json.operations || {}).map(([operationId, operation]) => this.createModel(Operation, operation, { id: operationId, address: operationId, pointer: `/operations/${tilde(operationId)}` }) - ) + ); return new Operations(operations); } diff --git a/src/models/v3/channel.ts b/src/models/v3/channel.ts index 0d4be3396..04a7cc560 100644 --- a/src/models/v3/channel.ts +++ b/src/models/v3/channel.ts @@ -3,7 +3,6 @@ import { ChannelParameters } from '../channel-parameters'; import { ChannelParameter } from './channel-parameter'; import { Messages } from '../messages'; import { Operations } from '../operations'; -import { Operation } from './operation'; import { Servers } from '../servers'; import { Server } from './server'; @@ -16,7 +15,7 @@ import type { ExtensionsInterface } from '../extensions'; import type { MessagesInterface } from '../messages'; import type { MessageInterface } from '../message'; import type { OperationsInterface } from '../operations'; -import type { OperationAction, OperationInterface } from '../operation'; +import type { OperationInterface } from '../operation'; import type { ServersInterface } from '../servers'; import type { ServerInterface } from '../server'; @@ -44,7 +43,7 @@ export class Channel extends BaseModel new Server(server)); diff --git a/src/models/v3/server.ts b/src/models/v3/server.ts index 0086bd303..e06557814 100644 --- a/src/models/v3/server.ts +++ b/src/models/v3/server.ts @@ -75,7 +75,7 @@ export class Server extends BaseModel implement operationsData.push(op.json()); } }); - }) + }); return new Operations(operations); } diff --git a/src/spec-types/v3.ts b/src/spec-types/v3.ts index 7fb0ee76c..4a3a8bf38 100644 --- a/src/spec-types/v3.ts +++ b/src/spec-types/v3.ts @@ -115,7 +115,7 @@ export interface OperationObject extends SpecificationExtensions { externalDocs?: ExternalDocumentationObject; bindings?: OperationBindingsObject | ReferenceObject; traits?: OperationTraitObject[]; -}; +} export interface OperationTraitObject extends SpecificationExtensions { action?: string; @@ -126,7 +126,7 @@ export interface OperationTraitObject extends SpecificationExtensions { tags?: TagsObject; externalDocs?: ExternalDocumentationObject; bindings?: OperationBindingsObject | ReferenceObject; -}; +} export type TagsObject = Array; From 736133419db49c5946ef51c7d871249d3e644350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=20M=C3=A9ndez?= Date: Mon, 17 Oct 2022 12:32:35 +0200 Subject: [PATCH 07/17] Use import type instead of import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Maciej Urbańczyk --- src/custom-operations/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/custom-operations/index.ts b/src/custom-operations/index.ts index 1577f3142..b9efec2f7 100644 --- a/src/custom-operations/index.ts +++ b/src/custom-operations/index.ts @@ -7,7 +7,7 @@ import type { Parser } from '../parser'; import type { ParseOptions } from '../parse'; import type { AsyncAPIDocumentInterface } from '../models'; import type { DetailedAsyncAPI } from '../types'; -import { v2 } from 'spec-types'; +import type { v2 } from '../../spec-types'; export async function customOperations(parser: Parser, document: AsyncAPIDocumentInterface, detailed: DetailedAsyncAPI, options: ParseOptions): Promise { switch (detailed.semver.major) { From c58a021b7958a98a3418c7815b6bdf99cd131647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=20M=C3=A9ndez?= Date: Mon, 17 Oct 2022 12:36:45 +0200 Subject: [PATCH 08/17] Remove meta.address from v3 model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Maciej Urbańczyk --- src/models/v3/channel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/v3/channel.ts b/src/models/v3/channel.ts index 04a7cc560..1ab4a7b5c 100644 --- a/src/models/v3/channel.ts +++ b/src/models/v3/channel.ts @@ -21,7 +21,7 @@ import type { ServerInterface } from '../server'; import type { v3 } from '../../spec-types'; -export class Channel extends BaseModel implements ChannelInterface { +export class Channel extends BaseModel implements ChannelInterface { id(): string { return this._meta.id; } From aa6cf5143694ff83a7af638c710b7bb1e28a2e9e Mon Sep 17 00:00:00 2001 From: Fran Mendez Date: Mon, 17 Oct 2022 12:56:45 +0200 Subject: [PATCH 09/17] replace import with import type --- src/document.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/document.ts b/src/document.ts index b532f535c..7dac145bc 100644 --- a/src/document.ts +++ b/src/document.ts @@ -9,7 +9,7 @@ import { import type { AsyncAPIDocumentInterface } from './models'; import type { DetailedAsyncAPI } from './types'; -import { v2 } from 'spec-types'; +import type { v2 } from './spec-types'; export function createAsyncAPIDocument(asyncapi: DetailedAsyncAPI): AsyncAPIDocumentInterface { switch (asyncapi.semver.major) { From 80dd7ea49302d891b9a8bea50814f32bdf6f7d0b Mon Sep 17 00:00:00 2001 From: Fran Mendez Date: Mon, 17 Oct 2022 13:04:16 +0200 Subject: [PATCH 10/17] Fix some incomplete changes after review --- src/custom-operations/index.ts | 2 +- src/models/v3/channel.ts | 2 +- src/models/v3/operation.ts | 2 +- src/models/v3/server.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/custom-operations/index.ts b/src/custom-operations/index.ts index b9efec2f7..6f64035d2 100644 --- a/src/custom-operations/index.ts +++ b/src/custom-operations/index.ts @@ -7,7 +7,7 @@ import type { Parser } from '../parser'; import type { ParseOptions } from '../parse'; import type { AsyncAPIDocumentInterface } from '../models'; import type { DetailedAsyncAPI } from '../types'; -import type { v2 } from '../../spec-types'; +import type { v2 } from '../spec-types'; export async function customOperations(parser: Parser, document: AsyncAPIDocumentInterface, detailed: DetailedAsyncAPI, options: ParseOptions): Promise { switch (detailed.semver.major) { diff --git a/src/models/v3/channel.ts b/src/models/v3/channel.ts index 1ab4a7b5c..2341def3e 100644 --- a/src/models/v3/channel.ts +++ b/src/models/v3/channel.ts @@ -27,7 +27,7 @@ export class Channel extends BaseModel impleme } address(): string | null | undefined { - return this._meta.address; + return this._json.address; } hasDescription(): boolean { diff --git a/src/models/v3/operation.ts b/src/models/v3/operation.ts index 5c55b63e8..1dd259761 100644 --- a/src/models/v3/operation.ts +++ b/src/models/v3/operation.ts @@ -70,7 +70,7 @@ export class Operation extends BaseModel implements Operatio const channels: ChannelInterface[] = []; const pointer = this._json.channel.address ? tilde(`/channels/${this._json.channel.address}`) : undefined; channels.push( - this.createModel(Channel, this._json.channel, { id: this._json.channel['x-parser-id'], address: this._json.channel.address, pointer }) + this.createModel(Channel, this._json.channel, { id: this._json.channel['x-parser-id'], pointer }) ); return new Channels(channels); } diff --git a/src/models/v3/server.ts b/src/models/v3/server.ts index e06557814..33418a377 100644 --- a/src/models/v3/server.ts +++ b/src/models/v3/server.ts @@ -59,7 +59,7 @@ export class Server extends BaseModel implement Object.entries(this._meta.asyncapi?.parsed?.channels || {}).forEach(([channelAddress, channel]: [string, any]) => { const allowedServers: string[] = channel.servers || []; if (allowedServers.length === 0 || allowedServers.includes(this._meta.id)) { - channels.push(this.createModel(Channel, channel, { id: channelAddress, address: channelAddress, pointer: `/channels/${tilde(channelAddress)}` })); + channels.push(this.createModel(Channel, channel, { id: channelAddress, pointer: `/channels/${tilde(channelAddress)}` })); } }); return new Channels(channels); From 25e84bddc4f9caa1e39d4747e8385711d5368615 Mon Sep 17 00:00:00 2001 From: Matatjahu Date: Thu, 20 Oct 2022 13:05:15 +0200 Subject: [PATCH 11/17] fix models from spec perspective --- src/models/base.ts | 4 +- src/models/channel.ts | 4 +- src/models/operation-trait.ts | 8 +- src/models/operation.ts | 4 - src/models/server.ts | 4 +- src/models/v2/channel.ts | 17 +++- src/models/v2/operation-trait.ts | 20 ++-- src/models/v2/operation.ts | 6 +- src/models/v2/server.ts | 7 +- src/models/v3/asyncapi.ts | 29 ++++-- src/models/v3/channel.ts | 62 ++++++++---- src/models/v3/correlation-id.ts | 30 ++++++ src/models/v3/message-example.ts | 46 +++++++++ src/models/v3/message-trait.ts | 124 +++++++++++++++++++++++ src/models/v3/message.ts | 84 +++++++++++++++ src/models/v3/operation-trait.ts | 30 ++++-- src/models/v3/operation.ts | 119 +++------------------- src/models/v3/server.ts | 7 +- src/spec-types/v2.ts | 1 + src/spec-types/v3.ts | 169 ++++++++++++++++--------------- 20 files changed, 526 insertions(+), 249 deletions(-) create mode 100644 src/models/v3/correlation-id.ts create mode 100644 src/models/v3/message-example.ts create mode 100644 src/models/v3/message-trait.ts create mode 100644 src/models/v3/message.ts diff --git a/src/models/base.ts b/src/models/base.ts index 6a4062efc..d82bfce5c 100644 --- a/src/models/base.ts +++ b/src/models/base.ts @@ -3,7 +3,7 @@ import type { DetailedAsyncAPI } from '../types'; export interface ModelMetadata { asyncapi: DetailedAsyncAPI; - pointer?: string; + pointer: string; } export abstract class BaseModel = {}> { @@ -28,7 +28,7 @@ export abstract class BaseModel extends BaseModel implements OperationTraitInterface { id(): string | undefined { return this._json.operationId; } - action(): OperationAction { + action(): OperationAction | undefined { return this._meta.action; } @@ -47,6 +49,10 @@ export class OperationTrait; return (this._json.security || []).map((requirement, index) => { @@ -73,6 +75,10 @@ export class OperationTrait implements OperationInterface { + action(): OperationAction { + return this._meta.action; + } + servers(): ServersInterface { const servers: ServerInterface[] = []; const serversData: any[] = []; diff --git a/src/models/v2/server.ts b/src/models/v2/server.ts index 162527087..7aafe0eff 100644 --- a/src/models/v2/server.ts +++ b/src/models/v2/server.ts @@ -8,8 +8,9 @@ import { ServerVariables } from '../server-variables'; import { ServerVariable } from './server-variable'; import { SecurityRequirements } from '../security-requirements'; import { SecurityRequirement } from './security-requirement'; +import { TagsInterface } from '../tags'; -import { bindings, hasDescription, description, extensions } from './mixins'; +import { bindings, hasDescription, description, extensions, tags } from './mixins'; import { tilde } from '../../utils'; import type { ChannelsInterface } from '../channels'; @@ -104,6 +105,10 @@ export class Server extends BaseModel implement }); } + tags(): TagsInterface { + return tags(this); + } + bindings(): BindingsInterface { return bindings(this); } diff --git a/src/models/v3/asyncapi.ts b/src/models/v3/asyncapi.ts index 56e5ff4e9..81b3ea604 100644 --- a/src/models/v3/asyncapi.ts +++ b/src/models/v3/asyncapi.ts @@ -1,10 +1,15 @@ -import { tilde } from '../../utils'; import { BaseModel } from '../base'; -import { OperationInterface } from '../operation'; +import { Channels } from '../channels'; +import { Channel } from './channel'; import { Operations } from '../operations'; import { Operation } from './operation'; +import { extensions } from './mixins'; +import { tilde } from '../../utils'; + import type { AsyncAPIDocumentInterface } from '../asyncapi'; +import type { ChannelsInterface } from '../channels'; +import type { OperationsInterface } from '../operations'; import type { v3 } from '../../spec-types'; @@ -29,16 +34,20 @@ export class AsyncAPIDocument extends BaseModel implements As return null as any; } - channels() { - return null as any; + channels(): ChannelsInterface { + return new Channels( + Object.entries(this._json.channels || {}).map(([channelId, channel]) => + this.createModel(Channel, channel, { id: channelId, pointer: `/channels/${tilde(channelId)}` }) + ) + ); } - operations() { - const operations: OperationInterface[] = Object.entries(this._json.operations || {}).map(([operationId, operation]) => - this.createModel(Operation, operation, { id: operationId, address: operationId, pointer: `/operations/${tilde(operationId)}` }) + operations(): OperationsInterface { + return new Operations( + Object.entries(this._json.operations || {}).map(([operationId, operation]) => + this.createModel(Operation, operation, { id: operationId, pointer: `/operations/${tilde(operationId)}` }) + ) ); - - return new Operations(operations); } messages() { @@ -58,6 +67,6 @@ export class AsyncAPIDocument extends BaseModel implements As } extensions() { - return null as any; + return extensions(this); } } diff --git a/src/models/v3/channel.ts b/src/models/v3/channel.ts index 2341def3e..06a42d0f0 100644 --- a/src/models/v3/channel.ts +++ b/src/models/v3/channel.ts @@ -2,22 +2,25 @@ import { BaseModel } from '../base'; import { ChannelParameters } from '../channel-parameters'; import { ChannelParameter } from './channel-parameter'; import { Messages } from '../messages'; +import { Message } from './message'; import { Operations } from '../operations'; +import { Operation } from './operation'; import { Servers } from '../servers'; import { Server } from './server'; -import { bindings, hasDescription, description, extensions } from './mixins'; +import { bindings, hasDescription, description, extensions, hasExternalDocs, externalDocs, tags } from './mixins'; 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 { MessagesInterface } from '../messages'; -import type { MessageInterface } from '../message'; import type { OperationsInterface } from '../operations'; import type { OperationInterface } from '../operation'; import type { ServersInterface } from '../servers'; import type { ServerInterface } from '../server'; +import type { TagsInterface } from '../tags'; import type { v3 } from '../../spec-types'; @@ -38,35 +41,48 @@ export class Channel extends BaseModel impleme return description(this); } + hasExternalDocs(): boolean { + return hasExternalDocs(this); + } + + externalDocs(): ExternalDocumentationInterface | undefined { + return externalDocs(this); + } + servers(): ServersInterface { - let allowedServers: v3.ServerObject[]; - if (Array.isArray(this._json.servers)) { - allowedServers = this._json.servers || []; - } else { - const parsedAsyncAPI = this._meta.asyncapi.parsed as v3.AsyncAPIObject; - allowedServers = parsedAsyncAPI.servers || []; - } - const servers: ServerInterface[] = allowedServers.map((server: v3.ServerObject) => new Server(server)); + const servers: ServerInterface[] = []; + const allowedServers = this._json.servers || []; + Object.entries(this._meta.asyncapi?.parsed?.servers || {}).forEach(([serverName, server]) => { + if (allowedServers.length === 0) { + servers.push(this.createModel(Server, server, { id: serverName, pointer: `/servers/${serverName}` })); + } else { + const index = allowedServers.indexOf(server); + if (index !== -1) { + servers.push(this.createModel(Server, server, { id: serverName, pointer: this.jsonPath(`servers/${index}`) })); + } + } + }); return new Servers(servers); } operations(): OperationsInterface { const operations: OperationInterface[] = []; - // ['publish', 'subscribe'].forEach(operationAction => { - // const id = this._json[operationAction as 'publish' | 'subscribe'] && (this._json[operationAction as 'publish' | 'subscribe'] as v2.OperationObject).operationId || `${this.meta().id }_${ operationAction}`; - // if (this._json[operationAction as 'publish' | 'subscribe']) { - // operations.push( - // this.createModel(Operation, this._json[operationAction as 'publish' | 'subscribe'] as v2.OperationObject, { id, action: operationAction as OperationAction, pointer: `${this._meta.pointer}/${operationAction}` }), - // ); - // } - // }); + Object.entries(((this._meta.asyncapi?.parsed as v3.AsyncAPIObject)?.operations || {})).forEach(([operationId, operation]) => { + if (operation.channel === this._json) { + operations.push( + this.createModel(Operation, operation, { id: operationId, pointer: `/operations/${operationId}` }), + ); + } + }); return new Operations(operations); } messages(): MessagesInterface { - const messages: MessageInterface[] = []; - this.operations().forEach(operation => messages.push(...operation.messages().all())); - return new Messages(messages); + return new Messages( + Object.entries(this._json.messages || {}).map(([messageName, message]) => { + return this.createModel(Message, message, { id: messageName, pointer: this.jsonPath(`messages/${messageName}`) }) + }) + ); } parameters(): ChannelParametersInterface { @@ -80,6 +96,10 @@ export class Channel extends BaseModel impleme ); } + tags(): TagsInterface { + return tags(this); + } + bindings(): BindingsInterface { return bindings(this); } diff --git a/src/models/v3/correlation-id.ts b/src/models/v3/correlation-id.ts new file mode 100644 index 000000000..d447e4b4b --- /dev/null +++ b/src/models/v3/correlation-id.ts @@ -0,0 +1,30 @@ +import { BaseModel } from '../base'; + +import { hasDescription, description, extensions } from './mixins'; + +import type { CorrelationIdInterface } from '../correlation-id'; +import type { ExtensionsInterface } from '../extensions'; + +import type { v3 } from '../../spec-types'; + +export class CorrelationId extends BaseModel implements CorrelationIdInterface { + hasDescription(): boolean { + return hasDescription(this); + } + + description(): string | undefined { + return description(this); + } + + hasLocation(): boolean { + return !!this._json.location; + } + + location(): string | undefined { + return this._json.location; + } + + extensions(): ExtensionsInterface { + return extensions(this); + } +} diff --git a/src/models/v3/message-example.ts b/src/models/v3/message-example.ts new file mode 100644 index 000000000..591664cef --- /dev/null +++ b/src/models/v3/message-example.ts @@ -0,0 +1,46 @@ +import { BaseModel } from '../base'; + +import { extensions } from './mixins'; + +import type { ExtensionsInterface } from '../extensions'; +import type { MessageExampleInterface } from '../message-example'; + +import type { v3 } from '../../spec-types'; + +export class MessageExample extends BaseModel implements MessageExampleInterface { + hasName(): boolean { + return !!this._json.name; + } + + name(): string | undefined { + return this._json.name; + } + + hasSummary(): boolean { + return !!this._json.summary; + } + + summary(): string | undefined { + return this._json.summary; + } + + hasHeaders(): boolean { + return !!this._json.headers; + } + + headers(): Record | undefined { + return this._json.headers; + } + + hasPayload(): boolean { + return !!this._json.payload; + } + + payload(): Record | undefined { + return this._json.payload; + } + + extensions(): ExtensionsInterface { + return extensions(this); + } +} diff --git a/src/models/v3/message-trait.ts b/src/models/v3/message-trait.ts new file mode 100644 index 000000000..eee94be89 --- /dev/null +++ b/src/models/v3/message-trait.ts @@ -0,0 +1,124 @@ +import { BaseModel } from '../base'; +import { CorrelationId } from './correlation-id'; +import { MessageExamples } from '../message-examples'; +import { MessageExample } from './message-example'; +import { Schema } from './schema'; + +import { xParserMessageName } from '../../constants'; +import { getDefaultSchemaFormat } from '../../schema-parser'; +import { bindings, hasDescription, description, extensions, hasExternalDocs, externalDocs, tags } from './mixins'; + +import type { BindingsInterface } from '../bindings'; +import type { CorrelationIdInterface } from '../correlation-id'; +import type { ExtensionsInterface } from '../extensions'; +import type { ExternalDocumentationInterface } from '../external-docs'; +import type { MessageExamplesInterface } from '../message-examples'; +import type { MessageTraitInterface } from '../message-trait'; +import type { SchemaInterface } from '../schema'; +import type { TagsInterface } from '../tags'; + +import type { v3 } from '../../spec-types'; + +export class MessageTrait extends BaseModel implements MessageTraitInterface { + id(): string { + return this.messageId() || this._meta.id || this.extensions().get(xParserMessageName)?.value() as string; + } + + schemaFormat(): string { + return this._json.schemaFormat || getDefaultSchemaFormat(this._meta.asyncapi.semver.version); + } + + hasMessageId(): boolean { + return !!this._json.messageId; + } + + messageId(): string | undefined { + return this._json.messageId; + } + + hasCorrelationId(): boolean { + return !!this._json.correlationId; + } + + correlationId(): CorrelationIdInterface | undefined { + if (!this._json.correlationId) return undefined; + return this.createModel(CorrelationId, this._json.correlationId as v3.CorrelationIDObject, { pointer: `${this._meta.pointer}/correlationId` }); + } + + hasContentType(): boolean { + return !!this._json.contentType; + } + + contentType(): string | undefined { + return this._json.contentType || this._meta.asyncapi?.parsed.defaultContentType; + } + + hasHeaders(): boolean { + return !!this._json.headers; + } + + headers(): SchemaInterface | undefined { + if (!this._json.headers) return undefined; + return this.createModel(Schema, this._json.headers, { pointer: `${this._meta.pointer}/headers` }); + } + + hasName(): boolean { + return !!this._json.name; + } + + name(): string | undefined { + return this._json.name; + } + + hasTitle(): boolean { + return !!this._json.title; + } + + title(): string | undefined { + return this._json.title; + } + + hasSummary(): boolean { + return !!this._json.summary; + } + + summary(): string | undefined { + return this._json.summary; + } + + hasDescription(): boolean { + return hasDescription(this); + } + + description(): string | undefined { + return description(this); + } + + hasExternalDocs(): boolean { + return hasExternalDocs(this); + } + + externalDocs(): ExternalDocumentationInterface | undefined { + return externalDocs(this); + } + + examples(): MessageExamplesInterface { + return new MessageExamples( + (this._json.examples || []).map((example: any, index: number) => { + return this.createModel(MessageExample, example, { pointer: `${this._meta.pointer}/examples/${index}` }); + }) + ); + } + + tags(): TagsInterface { + return tags(this); + } + + bindings(): BindingsInterface { + return bindings(this); + } + + extensions(): ExtensionsInterface { + return extensions(this); + } +} diff --git a/src/models/v3/message.ts b/src/models/v3/message.ts new file mode 100644 index 000000000..df5e22c1e --- /dev/null +++ b/src/models/v3/message.ts @@ -0,0 +1,84 @@ +import { Channels } from '../channels'; +import { Channel } from './channel'; +import { Operations } from '../operations'; +import { MessageTraits } from '../message-traits'; +import { MessageTrait } from './message-trait'; +import { Servers } from '../servers'; +import { Schema } from './schema'; + +import type { ChannelsInterface } from '../channels'; +import type { ChannelInterface } from '../channel'; +import type { MessageInterface } from '../message'; +import type { MessageTraitsInterface } from '../message-traits'; +import type { OperationsInterface } from '../operations'; +import type { OperationInterface } from '../operation'; +import type { ServersInterface } from '../servers'; +import type { ServerInterface } from '../server'; +import type { SchemaInterface } from '../schema'; + +import type { v3 } from '../../spec-types'; + +export class Message extends MessageTrait implements MessageInterface { + hasPayload(): boolean { + return !!this._json.payload; + } + + payload(): SchemaInterface | undefined { + if (!this._json.payload) return undefined; + return this.createModel(Schema, this._json.payload, { pointer: `${this._meta.pointer}/payload` }); + } + + servers(): ServersInterface { + const servers: ServerInterface[] = []; + const serversData: any[] = []; + this.channels().forEach(channel => { + channel.servers().forEach(server => { + if (!serversData.includes(server.json())) { + serversData.push(server.json()); + servers.push(server); + } + }); + }); + return new Servers(servers); + } + + channels(): ChannelsInterface { + const channels: ChannelInterface[] = []; + Object.entries((this._meta.asyncapi?.parsed?.channels || {}) as v3.ChannelsObject).forEach(([channelName, channel]) => { + const hasMessage = Object.entries(channel.messages || {}).some(([_, message]) => message === this._json); + if (hasMessage) { + channels.push( + this.createModel(Channel, channel, { id: channelName, pointer: `/channels/${channelName}` }), + ); + } + }); + Object.entries((this._meta.asyncapi?.parsed as v3.AsyncAPIObject)?.operations || {}).forEach(([operationId, operation]) => { + const operationChannel = operation.channel as v3.ChannelObject | undefined; + if (!channels.some(channel => channel.json() === operationChannel)) { + const hasMessage = Object.entries(operationChannel?.messages || {}).some(([_, message]) => message === this._json); + if (hasMessage) { + channels.push( + this.createModel(Channel, operationChannel as v3.ChannelObject, { id: '', pointer: `/operations/${operationId}/channel` }), + ); + } + } + }); + return new Channels(channels); + } + + operations(): OperationsInterface { + const operations: OperationInterface[] = []; + this.channels().forEach(channel => { + operations.push(...channel.operations()); + }) + return new Operations(operations); + } + + traits(): MessageTraitsInterface { + return new MessageTraits( + (this._json.traits || []).map((trait: any, index: number) => { + return this.createModel(MessageTrait, trait, { id: '', pointer: `${this._meta.pointer}/traits/${index}` }); + }) + ); + } +} diff --git a/src/models/v3/operation-trait.ts b/src/models/v3/operation-trait.ts index 53f05e684..9a919bd8c 100644 --- a/src/models/v3/operation-trait.ts +++ b/src/models/v3/operation-trait.ts @@ -1,30 +1,33 @@ import { BaseModel } from '../base'; +import { Channels } from '../channels'; +import { Channel } from './channel'; import { SecurityScheme } from './security-scheme'; +import { SecurityRequirements } from '../security-requirements'; +import { SecurityRequirement } from './security-requirement'; import { bindings, hasDescription, description, extensions, hasExternalDocs, externalDocs, tags } from './mixins'; import type { BindingsInterface } from '../bindings'; import type { ExtensionsInterface } from '../extensions'; import type { ExternalDocumentationInterface } from '../external-docs'; +import type { ChannelsInterface } from '../channels'; import type { OperationAction } from '../operation'; import type { OperationTraitInterface } from '../operation-trait'; import type { TagsInterface } from '../tags'; import type { v3 } from '../../spec-types'; -import { SecurityRequirements } from '../security-requirements'; -import { SecurityRequirement } from './security-requirement'; -export class OperationTrait extends BaseModel implements OperationTraitInterface { +export class OperationTrait extends BaseModel implements OperationTraitInterface { id(): string | undefined { return this._meta.id; } action(): OperationAction | undefined { - return this._meta.action; + return this._json.action; } hasId(): boolean { - return this.id() === undefined; + return this.id() !== undefined; } hasSummary(): boolean { @@ -47,16 +50,25 @@ export class OperationTrait implements OperationInterface { - id(): string { - return this._json['x-parser-id']; - } +import type { MessagesInterface } from '../messages'; +import type { MessageInterface } from '../message'; +import type { OperationInterface, OperationAction } from '../operation'; +import type { OperationTraitsInterface } from '../operation-traits'; +import type { ServersInterface } from '../servers'; +import type { ServerInterface } from '../server'; - hasId(): boolean { - return true; - } +import type { v3 } from '../../spec-types'; +export class Operation extends OperationTrait implements OperationInterface { action(): OperationAction { return this._json.action; } - isSend(): boolean { - return this._json.action === 'send'; - } - - isReceive(): boolean { - return this._json.action === 'receive'; - } - servers(): ServersInterface { const servers: ServerInterface[] = []; - const serversData: v3.ServerObject[] = []; + const serversData: any[] = []; this.channels().forEach(channel => { channel.servers().forEach(server => { if (!serversData.includes(server.json())) { @@ -55,82 +30,20 @@ export class Operation extends BaseModel implements Operatio }); return new Servers(servers); } - + messages(): MessagesInterface { const messages: MessageInterface[] = []; this.channels().forEach(channel => { - channel.messages().forEach(message => { - messages.push(message); - }); + messages.push(...channel.messages()); }); return new Messages(messages); } - channels(): ChannelsInterface { - const channels: ChannelInterface[] = []; - const pointer = this._json.channel.address ? tilde(`/channels/${this._json.channel.address}`) : undefined; - channels.push( - this.createModel(Channel, this._json.channel, { id: this._json.channel['x-parser-id'], pointer }) - ); - return new Channels(channels); - } - - summary(): string | undefined { - return this._json.summary; - } - - hasSummary(): boolean { - return this._json.summary !== undefined; - } - - description(): string | undefined { - return this._json.description; - } - - hasDescription(): boolean { - return this._json.description !== undefined; - } - - security(): SecurityRequirements[] { - const securitySchemes = (this._meta?.asyncapi?.parsed?.components?.securitySchemes || {}) as Record; - return (this._json.security || []).map((requirement, index) => { - const requirements: SecurityRequirement[] = []; - Object.entries(requirement).forEach(([security, scopes]) => { - const scheme = this.createModel(SecurityScheme, securitySchemes[security], { id: security, pointer: `/components/securitySchemes/${security}` }); - requirements.push( - this.createModel(SecurityRequirement, { scheme, scopes }, { id: security, pointer: `${this.meta().pointer}/security/${index}/${security}` }) - ); - }); - return new SecurityRequirements(requirements); - }); - } - - tags(): TagsInterface { - return tags(this); - } - - bindings(): BindingsInterface { - return bindings(this); - } - - externalDocs(): ExternalDocumentationInterface | undefined { - if (!this.hasExternalDocs()) return; - return new ExternalDocumentation(this._json.externalDocs as v3.ExternalDocumentationObject); - } - - hasExternalDocs(): boolean { - return this._json.externalDocs === undefined; - } - traits(): OperationTraitsInterface { return new OperationTraits( - (this._json.traits || []).map((trait: v3.OperationTraitObject, index: number) => { - return this.createModel(OperationTrait, trait, { id: '', pointer: `${this._meta.pointer}/traits/${index}`, action: trait.action as OperationAction }); + (this._json.traits || []).map((trait: any, index: number) => { + return this.createModel(OperationTrait, trait, { id: '', pointer: `${this._meta.pointer}/traits/${index}` }); }) ); } - - extensions(): ExtensionsInterface { - return extensions(this); - } } diff --git a/src/models/v3/server.ts b/src/models/v3/server.ts index 33418a377..75620e2d2 100644 --- a/src/models/v3/server.ts +++ b/src/models/v3/server.ts @@ -9,7 +9,7 @@ import { ServerVariable } from './server-variable'; import { SecurityRequirements } from '../security-requirements'; import { SecurityRequirement } from './security-requirement'; -import { bindings, hasDescription, description, extensions } from './mixins'; +import { bindings, hasDescription, description, extensions, tags } from './mixins'; import { tilde } from '../../utils'; import type { ChannelsInterface } from '../channels'; @@ -22,6 +22,7 @@ import type { ServerInterface } from '../server'; import type { ServerVariablesInterface } from '../server-variables'; import type { ExtensionsInterface } from '../extensions'; import type { BindingsInterface } from '../bindings'; +import type { TagsInterface } from '../tags'; import type { v3 } from '../../spec-types'; @@ -110,6 +111,10 @@ export class Server extends BaseModel implement }); } + tags(): TagsInterface { + return tags(this); + } + bindings(): BindingsInterface { return bindings(this); } diff --git a/src/spec-types/v2.ts b/src/spec-types/v2.ts index 6551fc87e..6918ddca7 100644 --- a/src/spec-types/v2.ts +++ b/src/spec-types/v2.ts @@ -45,6 +45,7 @@ export interface ServerObject extends SpecificationExtensions { description?: string; variables?: Record; security?: Array; + tags?: TagsObject; bindings?: ServerBindingsObject | ReferenceObject; } diff --git a/src/spec-types/v3.ts b/src/spec-types/v3.ts index 4a3a8bf38..250046dd1 100644 --- a/src/spec-types/v3.ts +++ b/src/spec-types/v3.ts @@ -8,10 +8,49 @@ export interface AsyncAPIObject extends SpecificationExtensions { asyncapi: AsyncAPIVersion; id?: Identifier; defaultContentType?: DefaultContentType; - operations?: OperationsObject; + servers?: ServersObject; channels?: ChannelsObject; + operations?: OperationsObject; components?: ComponentsObject; - servers?: ServerObject[]; +} + +export type ServersObject = Record; + +export interface ServerObject extends SpecificationExtensions { + url: string; + protocol: string; + protocolVersion?: string; + description?: string; + variables?: Record; + security?: Array; + tags?: TagsObject; + bindings?: ServerBindingsObject | ReferenceObject; +} + +export interface ServerVariableObject extends SpecificationExtensions { + enum?: Array; + default?: string; + description?: string; + examples?: Array; +} + +export interface ServerBindingsObject extends SpecificationExtensions { + http?: Binding; + ws?: Binding; + kafka?: Binding; + anypointmq?: Binding; + amqp?: Binding; + amqp1?: Binding; + mqtt?: Binding; + mqtt5?: Binding; + nats?: Binding; + jms?: Binding; + sns?: Binding; + sqs?: Binding; + stomp?: Binding; + redis?: Binding; + mercure?: Binding; + ibmmq?: Binding; } export type ChannelsObject = Record; @@ -46,6 +85,50 @@ export interface ChannelBindingsObject extends SpecificationExtensions { ibmmq?: Binding; } +export type OperationsObject = Record; + +export interface OperationObject extends SpecificationExtensions { + action: 'send' | 'receive'; + channel: ChannelObject | ReferenceObject; + summary?: string; + description?: string; + security?: Array; + tags?: TagsObject; + externalDocs?: ExternalDocumentationObject; + bindings?: OperationBindingsObject | ReferenceObject; + traits?: OperationTraitObject[]; +} + +export interface OperationTraitObject extends SpecificationExtensions { + action?: 'send' | 'receive'; + channel?: ChannelObject | ReferenceObject; + summary?: string; + description?: string; + security?: Array; + tags?: TagsObject; + externalDocs?: ExternalDocumentationObject; + bindings?: OperationBindingsObject | ReferenceObject; +} + +export interface OperationBindingsObject extends SpecificationExtensions { + http?: Binding; + ws?: Binding; + kafka?: Binding; + anypointmq?: Binding; + amqp?: Binding; + amqp1?: Binding; + mqtt?: Binding; + mqtt5?: Binding; + nats?: Binding; + jms?: Binding; + sns?: Binding; + sqs?: Binding; + stomp?: Binding; + redis?: Binding; + mercure?: Binding; + ibmmq?: Binding; +} + export type MessagesObject = Record; export interface MessageObject extends MessageTraitObject, SpecificationExtensions { @@ -103,31 +186,6 @@ export interface ParameterObject extends SpecificationExtensions { location?: string; } -export type OperationsObject = Record; - -export interface OperationObject extends SpecificationExtensions { - action: 'send' | 'receive'; - channel: ChannelObject; - summary?: string; - description?: string; - security?: Array; - tags?: TagsObject; - externalDocs?: ExternalDocumentationObject; - bindings?: OperationBindingsObject | ReferenceObject; - traits?: OperationTraitObject[]; -} - -export interface OperationTraitObject extends SpecificationExtensions { - action?: string; - channel?: ReferenceObject; - summary?: string; - description?: string; - security?: Array; - tags?: TagsObject; - externalDocs?: ExternalDocumentationObject; - bindings?: OperationBindingsObject | ReferenceObject; -} - export type TagsObject = Array; export interface TagObject extends SpecificationExtensions { @@ -158,44 +216,6 @@ export interface ComponentsObject extends SpecificationExtensions { messageBindings?: Record; } -export type ServersObject = Record; - -export interface ServerObject extends SpecificationExtensions { - url: string; - protocol: string; - protocolVersion?: string; - description?: string; - variables?: Record; - security?: Array; - bindings?: ServerBindingsObject | ReferenceObject; -} - -export interface ServerVariableObject extends SpecificationExtensions { - enum?: Array; - default?: string; - description?: string; - examples?: Array; -} - -export interface ServerBindingsObject extends SpecificationExtensions { - http?: Binding; - ws?: Binding; - kafka?: Binding; - anypointmq?: Binding; - amqp?: Binding; - amqp1?: Binding; - mqtt?: Binding; - mqtt5?: Binding; - nats?: Binding; - jms?: Binding; - sns?: Binding; - sqs?: Binding; - stomp?: Binding; - redis?: Binding; - mercure?: Binding; - ibmmq?: Binding; -} - export interface SecuritySchemeObject extends SpecificationExtensions { type: SecuritySchemeType; description?: string; @@ -419,25 +439,6 @@ export interface ReferenceObject { export type SecurityRequirementObject = Record>; -export interface OperationBindingsObject extends SpecificationExtensions { - http?: Binding; - ws?: Binding; - kafka?: Binding; - anypointmq?: Binding; - amqp?: Binding; - amqp1?: Binding; - mqtt?: Binding; - mqtt5?: Binding; - nats?: Binding; - jms?: Binding; - sns?: Binding; - sqs?: Binding; - stomp?: Binding; - redis?: Binding; - mercure?: Binding; - ibmmq?: Binding; -} - export interface CorrelationIDObject extends SpecificationExtensions { location: string; description?: string; From d900b06909ab2687b5be7b773004f5d9a6ff05cd Mon Sep 17 00:00:00 2001 From: Matatjahu Date: Fri, 21 Oct 2022 10:09:38 +0200 Subject: [PATCH 12/17] add unit tests --- src/custom-operations/anonymous-naming.ts | 8 +- src/models/schema.ts | 2 +- src/models/v2/schema.ts | 6 +- src/models/v3/bindings.ts | 2 +- src/models/v3/extensions.ts | 11 - src/models/v3/index.ts | 34 +- src/models/v3/mixins.ts | 4 +- src/models/v3/schema.ts | 6 +- src/models/v3/tags.ts | 10 - test/models/v2/message.spec.ts | 10 +- test/models/v2/oauth-flow.spec.ts | 6 + test/models/v2/oauth-flows.spec.ts | 6 + test/models/v2/schema.spec.ts | 23 +- test/models/v2/server-variable.spec.ts | 19 +- test/models/v3/bindings.spec.ts | 64 ++ test/models/v3/channel-parameter.spec.ts | 77 ++ test/models/v3/correlation-id.spec.ts | 40 ++ test/models/v3/external-docs.spec.ts | 20 + test/models/v3/message-example.spec.ts | 121 ++++ test/models/v3/message-trait.spec.ts | 264 +++++++ test/models/v3/message.spec.ts | 190 +++++ test/models/v3/oauth-flow.spec.ts | 39 ++ test/models/v3/oauth-flows.spec.ts | 42 ++ test/models/v3/schema.spec.ts | 815 ++++++++++++++++++++++ test/models/v3/server-variable.spec.ts | 34 + test/models/v3/tag.spec.ts | 19 + test/models/v3/utils.ts | 150 ++++ 27 files changed, 1964 insertions(+), 58 deletions(-) delete mode 100644 src/models/v3/extensions.ts delete mode 100644 src/models/v3/tags.ts create mode 100644 test/models/v3/bindings.spec.ts create mode 100644 test/models/v3/channel-parameter.spec.ts create mode 100644 test/models/v3/correlation-id.spec.ts create mode 100644 test/models/v3/external-docs.spec.ts create mode 100644 test/models/v3/message-example.spec.ts create mode 100644 test/models/v3/message-trait.spec.ts create mode 100644 test/models/v3/message.spec.ts create mode 100644 test/models/v3/oauth-flow.spec.ts create mode 100644 test/models/v3/oauth-flows.spec.ts create mode 100644 test/models/v3/schema.spec.ts create mode 100644 test/models/v3/server-variable.spec.ts create mode 100644 test/models/v3/tag.spec.ts create mode 100644 test/models/v3/utils.ts diff --git a/src/custom-operations/anonymous-naming.ts b/src/custom-operations/anonymous-naming.ts index 42c1b3655..fa94171ab 100644 --- a/src/custom-operations/anonymous-naming.ts +++ b/src/custom-operations/anonymous-naming.ts @@ -37,7 +37,7 @@ function assignNameToAnonymousMessages(document: AsyncAPIDocumentInterface) { function assignUidToComponentParameterSchemas(document: AsyncAPIDocumentInterface) { document.components().channelParameters().forEach(parameter => { const schema = parameter.schema(); - if (schema && !schema.uid()) { + if (schema && !schema.id()) { setExtension(xParserSchemaId, parameter.id(), schema); } }); @@ -47,7 +47,7 @@ function assignUidToChannelParameterSchemas(document: AsyncAPIDocumentInterface) document.channels().forEach(channel => { channel.parameters().forEach(parameter => { const schema = parameter.schema(); - if (schema && !schema.uid()) { + if (schema && !schema.id()) { setExtension(xParserSchemaId, parameter.id(), schema); } }); @@ -56,14 +56,14 @@ function assignUidToChannelParameterSchemas(document: AsyncAPIDocumentInterface) function assignUidToComponentSchemas(document: AsyncAPIDocumentInterface) { document.components().schemas().forEach(schema => { - setExtension(xParserSchemaId, schema.uid(), schema); + setExtension(xParserSchemaId, schema.id(), schema); }); } function assignUidToAnonymousSchemas(doc: AsyncAPIDocumentInterface) { let anonymousSchemaCounter = 0; function callback(schema: SchemaInterface) { - if (!schema.uid()) { + if (!schema.id()) { setExtension(xParserSchemaId, ``, schema); } } diff --git a/src/models/schema.ts b/src/models/schema.ts index bcbef4bf9..06f814b2c 100644 --- a/src/models/schema.ts +++ b/src/models/schema.ts @@ -3,7 +3,7 @@ import type { ExtensionsMixinInterface, ExternalDocumentationMixinInterface } fr import type { v2 } from '../spec-types'; export interface SchemaInterface extends BaseModel, ExtensionsMixinInterface, ExternalDocumentationMixinInterface { - uid(): string; + id(): string; $comment(): string | undefined; $id(): string | undefined; $schema(): string; diff --git a/src/models/v2/schema.ts b/src/models/v2/schema.ts index a025f5619..49c5ac1d8 100644 --- a/src/models/v2/schema.ts +++ b/src/models/v2/schema.ts @@ -14,14 +14,14 @@ import type { v2 } from '../../spec-types'; export class Schema extends BaseModel implements SchemaInterface { constructor( _json: v2.AsyncAPISchemaObject, - _meta: ModelMetadata & { id: string, parent?: Schema } = {} as any, + _meta: ModelMetadata & { id?: string, parent?: Schema } = {} as any, ) { _json = retrievePossibleRef(_json, _meta.pointer as string, _meta.asyncapi?.parsed); super(_json, _meta); } - uid(): string { - return this._meta.id || this.extensions().get(xParserSchemaId)?.value() as string; + id(): string { + return this.$id() || this._meta.id || this.json(xParserSchemaId as any) as string; } $comment(): string | undefined { diff --git a/src/models/v3/bindings.ts b/src/models/v3/bindings.ts index c17208d60..bce2fc4ba 100644 --- a/src/models/v3/bindings.ts +++ b/src/models/v3/bindings.ts @@ -1,5 +1,5 @@ import { Collection } from '../collection'; -import { Extensions } from './extensions'; +import { Extensions } from '../extensions'; import { Extension } from './extension'; import { createModel } from '../utils'; diff --git a/src/models/v3/extensions.ts b/src/models/v3/extensions.ts deleted file mode 100644 index e529b35d3..000000000 --- a/src/models/v3/extensions.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Collection } from '../collection'; - -import type { ExtensionsInterface } from '../extensions'; -import type { ExtensionInterface } from '../extension'; - -export class Extensions extends Collection implements ExtensionsInterface { - override get(id: string): ExtensionInterface | undefined { - id = id.startsWith('x-') ? id : `x-${id}`; - return this.collections.find(ext => ext.id() === id); - } -} diff --git a/src/models/v3/index.ts b/src/models/v3/index.ts index 05cd9ec64..4da27ddc5 100644 --- a/src/models/v3/index.ts +++ b/src/models/v3/index.ts @@ -1 +1,33 @@ -export { AsyncAPIDocument as AsyncAPIDocumentV3 } from './asyncapi'; \ No newline at end of file +export { AsyncAPIDocument as AsyncAPIDocumentV3 } from './asyncapi'; +export { Binding as BindingV3 } from './binding'; +export { Bindings as BindingsV3 } from './bindings'; +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 { 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 { MessageExample as MessageExampleV3 } from './message-example'; +export { MessageExamples as MessageExamplesV3 } from '../message-examples'; +export { MessageTrait as MessageTraitV3 } from './message-trait'; +export { MessageTraits as MessageTraitsV3 } from '../message-traits'; +export { Message as MessageV3 } from './message'; +export { Messages as MessagesV3 } from '../messages'; +export { OAuthFlow as OAuthFlowV3 } from './oauth-flow'; +export { OAuthFlows as OAuthFlowsV3 } from './oauth-flows'; +export { OperationTrait as OperationTraitV3 } from './operation-trait'; +export { OperationTraits as OperationTraitsV3 } from '../operation-traits'; +export { Operation as OperationV3 } from './operation'; +export { Operations as OperationsV3 } from '../operations'; +export { Schema as SchemaV3 } from './schema'; +export { Schemas as SchemasV3 } from '../schemas'; +export { SecurityScheme as SecuritySchemeV3 } from './security-scheme'; +export { SecuritySchemes as SecuritySchemesV3 } from '../security-schemes'; +export { ServerVariable as ServerVariableV3 } from './server-variable'; +export { ServerVariables as ServerVariablesV3 } from '../server-variables'; +export { Server as ServerV3 } from './server'; +export { Servers as ServersV3 } from '../servers'; +export { Tag as TagV3 } from './tag'; +export { Tags as TagsV3 } from '../tags'; diff --git a/src/models/v3/mixins.ts b/src/models/v3/mixins.ts index 405d1cd6b..605cafed2 100644 --- a/src/models/v3/mixins.ts +++ b/src/models/v3/mixins.ts @@ -1,9 +1,9 @@ import { Bindings } from './bindings'; import { Binding } from './binding'; -import { Extensions } from './extensions'; +import { Extensions } from '../extensions'; import { Extension } from './extension'; import { ExternalDocumentation } from './external-docs'; -import { Tags } from './tags'; +import { Tags } from '../tags'; import { Tag } from './tag'; import { createModel } from '../utils'; diff --git a/src/models/v3/schema.ts b/src/models/v3/schema.ts index 1441cf782..889f254d7 100644 --- a/src/models/v3/schema.ts +++ b/src/models/v3/schema.ts @@ -14,14 +14,14 @@ import type { v3 } from '../../spec-types'; export class Schema extends BaseModel implements SchemaInterface { constructor( _json: v3.AsyncAPISchemaObject, - _meta: ModelMetadata & { id: string, parent?: Schema } = {} as any, + _meta: ModelMetadata & { id?: string, parent?: Schema } = {} as any, ) { _json = retrievePossibleRef(_json, _meta.pointer as string, _meta.asyncapi?.parsed); super(_json, _meta); } - uid(): string { - return this._meta.id || this.extensions().get(xParserSchemaId)?.value() as string; + id(): string { + return this.$id() || this._meta.id || this.json(xParserSchemaId as any) as string; } $comment(): string | undefined { diff --git a/src/models/v3/tags.ts b/src/models/v3/tags.ts deleted file mode 100644 index 6048620ff..000000000 --- a/src/models/v3/tags.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Collection } from '../collection'; - -import type { TagsInterface } from '../tags'; -import type { TagInterface } from '../tag'; - -export class Tags extends Collection implements TagsInterface { - override get(name: string): TagInterface | undefined { - return this.collections.find(tag => tag.name() === name); - } -} diff --git a/test/models/v2/message.spec.ts b/test/models/v2/message.spec.ts index bf4bb9d95..c1ca1885c 100644 --- a/test/models/v2/message.spec.ts +++ b/test/models/v2/message.spec.ts @@ -181,10 +181,10 @@ describe('Message model', function() { }); describe('mixins', function() { - assertBindings(MessageTrait); - assertDescription(MessageTrait); - assertExtensions(MessageTrait); - assertExternalDocumentation(MessageTrait); - assertTags(MessageTrait); + assertBindings(Message); + assertDescription(Message); + assertExtensions(Message); + assertExternalDocumentation(Message); + assertTags(Message); }); }); diff --git a/test/models/v2/oauth-flow.spec.ts b/test/models/v2/oauth-flow.spec.ts index 113375d37..15ffc588c 100644 --- a/test/models/v2/oauth-flow.spec.ts +++ b/test/models/v2/oauth-flow.spec.ts @@ -1,5 +1,7 @@ import { OAuthFlow } from '../../../src/models/v2/oauth-flow'; +import { assertExtensions } from './utils'; + import type { v2 } from '../../../src/spec-types'; const flowObject = { @@ -30,4 +32,8 @@ describe('OAuth Flow', function() { expect(flow.scopes()!['write:pets']).toMatch(flowObject.scopes['write:pets']); }); }); + + describe('mixins', function() { + assertExtensions(OAuthFlow); + }); }); diff --git a/test/models/v2/oauth-flows.spec.ts b/test/models/v2/oauth-flows.spec.ts index b11b2ef81..f7e5b56d9 100644 --- a/test/models/v2/oauth-flows.spec.ts +++ b/test/models/v2/oauth-flows.spec.ts @@ -1,6 +1,8 @@ import { OAuthFlows } from '../../../src/models/v2/oauth-flows'; import { OAuthFlow } from '../../../src/models/v2/oauth-flow'; +import { assertExtensions } from './utils'; + const oAuthFlowsObject = { implicit: { authorizationUrl: 'https://example.com/api/oauth/dialog', @@ -33,4 +35,8 @@ describe('OAuth Flows', function() { expect(flows.implicit() instanceof OAuthFlow).toBeTruthy(); }); }); + + describe('mixins', function() { + assertExtensions(OAuthFlows); + }); }); \ No newline at end of file diff --git a/test/models/v2/schema.spec.ts b/test/models/v2/schema.spec.ts index e29abfa23..680bfd893 100644 --- a/test/models/v2/schema.spec.ts +++ b/test/models/v2/schema.spec.ts @@ -1,15 +1,28 @@ import { Schema } from '../../../src/models/v2/schema'; import { assertExtensions, assertExternalDocumentation } from './utils'; +import { xParserSchemaId } from '../../../src/constants'; import type { v2 } from '../../../src/spec-types'; -describe('Channel model', function() { +describe('Schema model', function() { describe('.id()', function() { - it('should return id of model', function() { - const doc = {}; - const d = new Schema(doc, { asyncapi: {} as any, pointer: '', id: 'schema' }); - expect(d.uid()).toEqual('schema'); + it('should return $id of schema', function() { + const doc = { $id: '$id', [xParserSchemaId]: xParserSchemaId }; + const d = new Schema(doc, { asyncapi: {} as any, pointer: '', id: 'id' }); + expect(d.id()).toEqual('$id'); + }); + + it('should return meta id of schema as fallback', function() { + const doc = { [xParserSchemaId]: xParserSchemaId }; + const d = new Schema(doc, { asyncapi: {} as any, pointer: '', id: 'id' }); + expect(d.id()).toEqual('id'); + }); + + it(`should return ${xParserSchemaId} of schema as fallback`, function() { + const doc = { [xParserSchemaId]: xParserSchemaId }; + const d = new Schema(doc, { asyncapi: {} as any, pointer: '' }); + expect(d.id()).toEqual(xParserSchemaId); }); }); diff --git a/test/models/v2/server-variable.spec.ts b/test/models/v2/server-variable.spec.ts index f88659b7f..2f8888349 100644 --- a/test/models/v2/server-variable.spec.ts +++ b/test/models/v2/server-variable.spec.ts @@ -1,5 +1,7 @@ import { ServerVariable } from '../../../src/models/v2/server-variable'; +import { assertDescription, assertExtensions } from './utils'; + const doc = { description: 'Secure connection (TLS) is available through port 8883.', default: '1883', @@ -13,18 +15,6 @@ describe('Server Variable ', function() { expect(sv.id()).toMatch('doc'); }); - describe('.hasDefaultValue()', function() { - it('should return true if default value is passed', function() { - expect(sv.hasDefaultValue()).toBeTruthy(); - }); - }); - - describe('.defaultValue()', function() { - it('should return default value', function() { - expect(sv.defaultValue()).toMatch(doc.default); - }); - }); - describe('.hasAllowedValue()', function() { it('should return true when enum is passed', function() { expect(sv.hasAllowedValues()).toBeTruthy(); @@ -36,4 +26,9 @@ describe('Server Variable ', function() { expect(sv.allowedValues()).toEqual(doc.enum); }); }); + + describe('mixins', function() { + assertDescription(ServerVariable); + assertExtensions(ServerVariable); + }); }); diff --git a/test/models/v3/bindings.spec.ts b/test/models/v3/bindings.spec.ts new file mode 100644 index 000000000..39789d345 --- /dev/null +++ b/test/models/v3/bindings.spec.ts @@ -0,0 +1,64 @@ +import { Bindings } from '../../../src/models/v3/bindings'; +import { Binding } from '../../../src/models/v3/binding'; +import { Extensions } from '../../../src/models/extensions'; +import { Extension } from '../../../src/models/v3/extension'; + +const binding = { + http: {} +}; +const bindingItem = new Binding(binding, { asyncapi: {} as any, pointer: '', protocol: 'http' }); + +describe('Bindings model', function () { + describe('.isEmpty()', function () { + it('should return true if collection is empty', function () { + const bindings = new Bindings([]); + expect(bindings.isEmpty()).toEqual(true); + }); + + it('should return false if collection is not empty', function () { + const bindings = new Bindings([bindingItem]); + expect(bindings.isEmpty()).toEqual(false); + }); + }); + + describe('.get(id)', function () { + it('should return a specific Message Trait if it is present', function () { + const bindings = new Bindings([bindingItem]); + expect(bindings.get('http')).toBeTruthy(); + }); + + it('should return undefined if specific Message Trait is missing', function () { + const bindings = new Bindings([]); + expect(bindings.get('anotherProtocol')).toBeUndefined(); + }); + }); + + describe('.has(id)', function () { + it('should return true if the said id is available', function () { + const bindings = new Bindings([bindingItem]); + expect(bindings.has('http')).toEqual(true); + }); + + it('should return false if the Message Trait id is missing', function () { + const bindings = new Bindings([bindingItem]); + expect(bindings.has('anotherProtocol')).toEqual(false); + }); + }); + + describe('.extensions()', function () { + it('should return empty collection of extensions', function () { + const bindings = new Bindings([], {}); + expect(bindings.extensions()).toBeInstanceOf(Extensions); + expect(bindings.extensions().isEmpty()).toEqual(true); + }); + + it('should return collection of extensions', function () { + const bindings = new Bindings([], { asyncapi: {} as any, originalData: { 'x-someExtension': { someKey: 'someValue' } as any, 'x-anotherOne': { someKey: 123 } as any } }); + expect(bindings.extensions()).toBeInstanceOf(Extensions); + expect(bindings.extensions().get('x-someExtension')).toBeInstanceOf(Extension); + expect(bindings.extensions().get('x-someExtension')?.value()).toEqual({ someKey: 'someValue' }); + expect(bindings.extensions().get('x-anotherOne')).toBeInstanceOf(Extension); + expect(bindings.extensions().get('x-anotherOne')?.value()).toEqual({ someKey: 123 }); + }); + }); +}); diff --git a/test/models/v3/channel-parameter.spec.ts b/test/models/v3/channel-parameter.spec.ts new file mode 100644 index 000000000..66c6c76a1 --- /dev/null +++ b/test/models/v3/channel-parameter.spec.ts @@ -0,0 +1,77 @@ +import { ChannelParameter } from '../../../src/models/v3/channel-parameter'; +import { Schema } from '../../../src/models/v3/schema'; + +import { serializeInput, assertDescription, assertExtensions } from './utils'; + +import type { v3 } from '../../../src/spec-types'; + +describe('ChannelParameter model', function() { + describe('.id()', function() { + it('should return id of model', function() { + const doc = serializeInput({}); + const d = new ChannelParameter(doc, { asyncapi: {} as any, pointer: '', id: 'parameter' }); + expect(d.id()).toEqual('parameter'); + }); + }); + + describe('.hasLocation()', function() { + it('should return true when there is a value', function() { + const doc = serializeInput({ location: '...' }); + const d = new ChannelParameter(doc); + expect(d.hasLocation()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = serializeInput({}); + const d = new ChannelParameter(doc); + expect(d.hasLocation()).toEqual(false); + }); + }); + + describe('.location()', function() { + it('should return the value', function() { + const doc = serializeInput({ location: '...' }); + const d = new ChannelParameter(doc); + expect(d.location()).toEqual(doc.location); + }); + + it('should return undefined when there is no value', function() { + const doc = serializeInput({}); + const d = new ChannelParameter(doc); + expect(d.location()).toBeUndefined(); + }); + }); + + describe('.hasSchema()', function() { + it('should return true when there is a value', function() { + const doc = serializeInput({ schema: {} }); + const d = new ChannelParameter(doc); + expect(d.hasSchema()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = serializeInput({}); + const d = new ChannelParameter(doc); + expect(d.hasSchema()).toEqual(false); + }); + }); + + describe('.schema()', function() { + it('should return the value', function() { + const doc = serializeInput({ schema: {} }); + const d = new ChannelParameter(doc); + expect(d.schema()).toBeInstanceOf(Schema); + }); + + it('should return undefined when there is no value', function() { + const doc = serializeInput({}); + const d = new ChannelParameter(doc); + expect(d.schema()).toBeUndefined(); + }); + }); + + describe('mixins', function() { + assertDescription(ChannelParameter); + assertExtensions(ChannelParameter); + }); +}); diff --git a/test/models/v3/correlation-id.spec.ts b/test/models/v3/correlation-id.spec.ts new file mode 100644 index 000000000..8536f640e --- /dev/null +++ b/test/models/v3/correlation-id.spec.ts @@ -0,0 +1,40 @@ +import { CorrelationId } from '../../../src/models/v3/correlation-id'; + +import { serializeInput, assertDescription, assertExtensions } from './utils'; + +import type { v3 } from '../../../src/spec-types'; + +describe('CorrelationId model', function() { + describe('.hasLocation()', function() { + it('should return true when there is a value', function() { + const doc = serializeInput({ location: '...' }); + const d = new CorrelationId(doc); + expect(d.hasLocation()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = serializeInput({}); + const d = new CorrelationId(doc); + expect(d.hasLocation()).toEqual(false); + }); + }); + + describe('.location()', function() { + it('should return the value', function() { + const doc = serializeInput({ location: '...' }); + const d = new CorrelationId(doc); + expect(d.location()).toEqual(doc.location); + }); + + it('should return undefined when there is no value', function() { + const doc = serializeInput({}); + const d = new CorrelationId(doc); + expect(d.location()).toBeUndefined(); + }); + }); + + describe('mixins', function() { + assertDescription(CorrelationId); + assertExtensions(CorrelationId); + }); +}); diff --git a/test/models/v3/external-docs.spec.ts b/test/models/v3/external-docs.spec.ts new file mode 100644 index 000000000..c4b2025cf --- /dev/null +++ b/test/models/v3/external-docs.spec.ts @@ -0,0 +1,20 @@ +import { ExternalDocumentation } from '../../../src/models/v3/external-docs'; + +import { serializeInput, assertDescription, assertExtensions } from './utils'; + +import type { v3 } from '../../../src/spec-types'; + +describe('ExternalDocumentation model', function() { + describe('.url()', function() { + it('should return the value', function() { + const doc = serializeInput({ url: 'somewhere' }); + const d = new ExternalDocumentation(doc); + expect(d.url()).toEqual(doc.url); + }); + }); + + describe('mixins', function() { + assertDescription(ExternalDocumentation); + assertExtensions(ExternalDocumentation); + }); +}); diff --git a/test/models/v3/message-example.spec.ts b/test/models/v3/message-example.spec.ts new file mode 100644 index 000000000..8b7f653cd --- /dev/null +++ b/test/models/v3/message-example.spec.ts @@ -0,0 +1,121 @@ +import { MessageExample } from '../../../src/models/v3/message-example'; + +import { assertExtensions } from './utils'; + +describe('MessageExample model', function() { + describe('.hasName()', function() { + it('should return true when there is a value', function() { + const doc = { name: '...' }; + const d = new MessageExample(doc); + expect(d.hasName()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = {}; + const d = new MessageExample(doc); + expect(d.hasName()).toEqual(false); + }); + }); + + describe('.name()', function() { + it('should return the value', function() { + const doc = { name: '...' }; + const d = new MessageExample(doc); + expect(d.name()).toEqual(doc.name); + }); + + it('should return undefined when there is no value', function() { + const doc = {}; + const d = new MessageExample(doc); + expect(d.name()).toBeUndefined(); + }); + }); + + describe('.hasSummary()', function() { + it('should return true when there is a value', function() { + const doc = { summary: '...' }; + const d = new MessageExample(doc); + expect(d.hasSummary()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = {}; + const d = new MessageExample(doc); + expect(d.hasSummary()).toEqual(false); + }); + }); + + describe('.summary()', function() { + it('should return the value', function() { + const doc = { summary: '...' }; + const d = new MessageExample(doc); + expect(d.summary()).toEqual(doc.summary); + }); + + it('should return undefined when there is no value', function() { + const doc = {}; + const d = new MessageExample(doc); + expect(d.summary()).toBeUndefined(); + }); + }); + + describe('.hasHeaders()', function() { + it('should return true when there is a value', function() { + const doc = { headers: {} }; + const d = new MessageExample(doc); + expect(d.hasHeaders()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = {}; + const d = new MessageExample(doc); + expect(d.hasSummary()).toEqual(false); + }); + }); + + describe('.headers()', function() { + it('should return the value', function() { + const doc = { headers: {} }; + const d = new MessageExample(doc); + expect(d.headers()).toEqual(doc.headers); + }); + + it('should return undefined when there is no value', function() { + const doc = {}; + const d = new MessageExample(doc); + expect(d.headers()).toBeUndefined(); + }); + }); + + describe('.hasPayload()', function() { + it('should return true when there is a value', function() { + const doc = { payload: {} }; + const d = new MessageExample(doc); + expect(d.hasPayload()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = {}; + const d = new MessageExample(doc); + expect(d.hasPayload()).toEqual(false); + }); + }); + + describe('.payload()', function() { + it('should return the value', function() { + const doc = { payload: {} }; + const d = new MessageExample(doc); + expect(d.payload()).toEqual(doc.payload); + }); + + it('should return undefined when there is no value', function() { + const doc = {}; + const d = new MessageExample(doc); + expect(d.payload()).toBeUndefined(); + }); + }); + + describe('mixins', function() { + assertExtensions(MessageExample); + }); +}); diff --git a/test/models/v3/message-trait.spec.ts b/test/models/v3/message-trait.spec.ts new file mode 100644 index 000000000..3c83731e6 --- /dev/null +++ b/test/models/v3/message-trait.spec.ts @@ -0,0 +1,264 @@ +import { CorrelationId } from '../../../src/models/v3/correlation-id'; +import { MessageExamples } from '../../../src/models/message-examples'; +import { MessageExample } from '../../../src/models/v3/message-example'; +import { MessageTrait } from '../../../src/models/v3/message-trait'; +import { Schema } from '../../../src/models/v3/schema'; + +import { assertBindings, assertDescription, assertExtensions, assertExternalDocumentation, assertTags } from './utils'; + +describe('MessageTrait model', function() { + describe('.id()', function() { + it('should return id of model', function() { + const doc = {}; + const d = new MessageTrait(doc, { asyncapi: {} as any, pointer: '', id: 'trait' }); + expect(d.id()).toEqual('trait'); + }); + + it('should reuse messageId', function() { + const doc = { messageId: '...' }; + const d = new MessageTrait(doc); + expect(d.id()).toEqual(doc.messageId); + }); + }); + + describe('.schemaFormat()', function() { + it('should return defined schemaFormat', function() { + const doc = { schemaFormat: 'customSchemaFormat' }; + const d = new MessageTrait(doc, { asyncapi: {} as any, pointer: '', id: 'message' }); + expect(d.schemaFormat()).toEqual('customSchemaFormat'); + }); + + it('should return default schemaFormat if schemaFormat field is absent', function() { + const doc = {}; + const d = new MessageTrait(doc, { asyncapi: { semver: { version: '2.0.0' } } as any, pointer: '', id: 'message' }); + expect(d.schemaFormat()).toEqual('application/vnd.aai.asyncapi;version=2.0.0'); + }); + }); + + describe('.hasMessageId()', function() { + it('should return true when there is a value', function() { + const doc = { messageId: '...' }; + const d = new MessageTrait(doc); + expect(d.hasMessageId()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = {}; + const d = new MessageTrait(doc); + expect(d.hasMessageId()).toEqual(false); + }); + }); + + describe('.messageId()', function() { + it('should return the value', function() { + const doc = { messageId: '...' }; + const d = new MessageTrait(doc); + expect(d.messageId()).toEqual(doc.messageId); + }); + + it('should return undefined when there is no value', function() { + const doc = {}; + const d = new MessageTrait(doc); + expect(d.messageId()).toBeUndefined(); + }); + }); + + describe('.hasCorrelationId()', function() { + it('should return true when there is a value', function() { + const doc = { correlationId: { location: '...' } }; + const d = new MessageTrait(doc); + expect(d.hasCorrelationId()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = {}; + const d = new MessageTrait(doc); + expect(d.hasCorrelationId()).toEqual(false); + }); + }); + + describe('.correlationId()', function() { + it('should return the value', function() { + const doc = { correlationId: { location: '...' } }; + const d = new MessageTrait(doc); + expect(d.correlationId()).toBeInstanceOf(CorrelationId); + }); + + it('should return undefined when there is no value', function() { + const doc = {}; + const d = new MessageTrait(doc); + expect(d.correlationId()).toBeUndefined(); + }); + }); + + describe('.hasContentType()', function() { + it('should return true when there is a value', function() { + const doc = { contentType: '...' }; + const d = new MessageTrait(doc); + expect(d.hasContentType()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = {}; + const d = new MessageTrait(doc); + expect(d.hasContentType()).toEqual(false); + }); + }); + + describe('.contentType()', function() { + it('should return the value', function() { + const doc = { contentType: '...' }; + const d = new MessageTrait(doc); + expect(d.contentType()).toEqual(doc.contentType); + }); + + it('should return undefined when there is no value', function() { + const doc = {}; + const d = new MessageTrait(doc); + expect(d.contentType()).toBeUndefined(); + }); + + it('should reuse defaultContentType value', function() { + const doc = {}; + const d = new MessageTrait(doc, { asyncapi: { parsed: { defaultContentType: '...' } } } as any); + expect(d.contentType()).toEqual('...'); + }); + }); + + describe('.hasHeaders()', function() { + it('should return true when there is a value', function() { + const doc = { headers: {} }; + const d = new MessageTrait(doc); + expect(d.hasHeaders()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = {}; + const d = new MessageTrait(doc); + expect(d.hasHeaders()).toEqual(false); + }); + }); + + describe('.headers()', function() { + it('should return the value', function() { + const doc = { headers: {} }; + const d = new MessageTrait(doc); + expect(d.headers()).toBeInstanceOf(Schema); + }); + + it('should return undefined when there is no value', function() { + const doc = {}; + const d = new MessageTrait(doc); + expect(d.headers()).toBeUndefined(); + }); + }); + + describe('.hasName()', function() { + it('should return true when there is a value', function() { + const doc = { name: '...' }; + const d = new MessageTrait(doc); + expect(d.hasName()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = {}; + const d = new MessageTrait(doc); + expect(d.hasName()).toEqual(false); + }); + }); + + describe('.name()', function() { + it('should return the value', function() { + const doc = { name: '...' }; + const d = new MessageTrait(doc); + expect(d.name()).toEqual(doc.name); + }); + + it('should return undefined when there is no value', function() { + const doc = {}; + const d = new MessageTrait(doc); + expect(d.name()).toBeUndefined(); + }); + }); + + describe('.hasTitle()', function() { + it('should return true when there is a value', function() { + const doc = { title: '...' }; + const d = new MessageTrait(doc); + expect(d.hasTitle()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = {}; + const d = new MessageTrait(doc); + expect(d.hasTitle()).toEqual(false); + }); + }); + + describe('.title()', function() { + it('should return the value', function() { + const doc = { title: '...' }; + const d = new MessageTrait(doc); + expect(d.title()).toEqual(doc.title); + }); + + it('should return undefined when there is no value', function() { + const doc = {}; + const d = new MessageTrait(doc); + expect(d.title()).toBeUndefined(); + }); + }); + + describe('.hasSummary()', function() { + it('should return true when there is a value', function() { + const doc = { summary: '...' }; + const d = new MessageTrait(doc); + expect(d.hasSummary()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = {}; + const d = new MessageTrait(doc); + expect(d.hasSummary()).toEqual(false); + }); + }); + + describe('.summary()', function() { + it('should return the value', function() { + const doc = { summary: '...' }; + const d = new MessageTrait(doc); + expect(d.summary()).toEqual(doc.summary); + }); + + it('should return undefined when there is no value', function() { + const doc = {}; + const d = new MessageTrait(doc); + expect(d.summary()).toBeUndefined(); + }); + }); + + describe('.examples()', function() { + it('should return collection of examples', function() { + const doc = { examples: [{ name: '...' }] }; + const d = new MessageTrait(doc); + expect(d.examples()).toBeInstanceOf(MessageExamples); + expect(d.examples().all()).toHaveLength(1); + expect(d.examples().all()[0]).toBeInstanceOf(MessageExample); + }); + + it('should return collection of examples when value is undefined', function() { + const doc = {}; + const d = new MessageTrait(doc); + expect(d.examples()).toBeInstanceOf(MessageExamples); + expect(d.examples().all()).toHaveLength(0); + }); + }); + + describe('mixins', function() { + assertBindings(MessageTrait); + assertDescription(MessageTrait); + assertExtensions(MessageTrait); + assertExternalDocumentation(MessageTrait); + assertTags(MessageTrait); + }); +}); diff --git a/test/models/v3/message.spec.ts b/test/models/v3/message.spec.ts new file mode 100644 index 000000000..4b4c8d78c --- /dev/null +++ b/test/models/v3/message.spec.ts @@ -0,0 +1,190 @@ +import { Channels } from '../../../src/models/channels'; +import { Channel } from '../../../src/models/v3/channel'; +import { Message } from '../../../src/models/v3/message'; +import { MessageTraits } from '../../../src/models/message-traits'; +import { MessageTrait } from '../../../src/models/v3/message-trait'; +import { Operations } from '../../../src/models/operations'; +import { Operation } from '../../../src/models/v3/operation'; +import { Schema } from '../../../src/models/v3/schema'; +import { Servers } from '../../../src/models/servers'; +import { Server } from '../../../src/models/v3/server'; + +import { assertBindings, assertDescription, assertExtensions, assertExternalDocumentation, assertTags } from './utils'; + +describe('Message model', function() { + describe('.id()', function() { + it('should return id of model', function() { + const doc = {}; + const d = new Message(doc, { asyncapi: {} as any, pointer: '', id: 'message' }); + expect(d.id()).toEqual('message'); + }); + + it('should reuse messageId', function() { + const doc = { messageId: '...' }; + const d = new Message(doc); + expect(d.id()).toEqual(doc.messageId); + }); + }); + + describe('.schemaFormat()', function() { + it('should return defined schemaFormat', function() { + const doc = { schemaFormat: 'customSchemaFormat' }; + const d = new Message(doc, { asyncapi: {} as any, pointer: '', id: 'message' }); + expect(d.schemaFormat()).toEqual('customSchemaFormat'); + }); + + it('should return default schemaFormat if schemaFormat field is absent', function() { + const doc = {}; + const d = new Message(doc, { asyncapi: { semver: { version: '2.0.0' } } as any, pointer: '', id: 'message' }); + expect(d.schemaFormat()).toEqual('application/vnd.aai.asyncapi;version=2.0.0'); + }); + }); + + describe('.hasPayload()', function() { + it('should return true when there is a value', function() { + const doc = { payload: {} }; + const d = new Message(doc); + expect(d.hasPayload()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = {}; + const d = new Message(doc); + expect(d.hasPayload()).toEqual(false); + }); + }); + + describe('.payload()', function() { + it('should return the value', function() { + const doc = { payload: {} }; + const d = new Message(doc); + expect(d.payload()).toBeInstanceOf(Schema); + }); + + it('should return undefined when there is no value', function() { + const doc = {}; + const d = new Message(doc); + expect(d.payload()).toBeUndefined(); + }); + }); + + // describe('.servers()', function() { + // it('should return collection of servers - available on all servers', function() { + // const doc = {}; + // const d = new Message(doc, { asyncapi: { parsed: { servers: { production: {}, development: {} }, channels: { 'user/signup': { publish: { message: doc } } } } } as any, pointer: '', id: 'message' }); + // expect(d.servers()).toBeInstanceOf(Servers); + // expect(d.servers().all()).toHaveLength(2); + // expect(d.servers().all()[0]).toBeInstanceOf(Server); + // expect(d.servers().all()[0].id()).toEqual('production'); + // expect(d.servers().all()[1]).toBeInstanceOf(Server); + // expect(d.servers().all()[1].id()).toEqual('development'); + // }); + + // it('should return collection of servers - available on selected servers', function() { + // const doc = {}; + // const d = new Message(doc, { asyncapi: { parsed: { servers: { production: {}, development: {} }, channels: { 'user/signup': { publish: { message: doc }, servers: ['production'] } } } } as any, pointer: '', id: 'message' }); + // expect(d.servers()).toBeInstanceOf(Servers); + // expect(d.servers().all()).toHaveLength(1); + // expect(d.servers().all()[0]).toBeInstanceOf(Server); + // expect(d.servers().all()[0].id()).toEqual('production'); + // }); + + // it('should return collection of servers - do not duplicate servers', function() { + // const doc = {}; + // const d = new Message(doc, { asyncapi: { parsed: { servers: { production: {}, development: {} }, channels: { 'user/signup': { publish: { message: doc }, subscribe: { message: doc }, servers: ['production'] } } } } as any, pointer: '', id: 'message' }); + // expect(d.servers()).toBeInstanceOf(Servers); + // expect(d.servers().all()).toHaveLength(1); + // expect(d.servers().all()[0]).toBeInstanceOf(Server); + // expect(d.servers().all()[0].id()).toEqual('production'); + // }); + // }); + + // describe('.channels()', function() { + // it('should return collection of channels - single channel', function() { + // const doc = {}; + // const d = new Message(doc, { asyncapi: { parsed: { channels: { 'user/signup': { publish: { message: doc } } } } } as any, pointer: '', id: 'message' }); + // expect(d.channels()).toBeInstanceOf(Channels); + // expect(d.channels().all()).toHaveLength(1); + // expect(d.channels().all()[0]).toBeInstanceOf(Channel); + // expect(d.channels().all()[0].address()).toEqual('user/signup'); + // }); + + // it('should return collection of channels - multiple channels', function() { + // const doc = {}; + // const d = new Message(doc, { asyncapi: { parsed: { channels: { 'user/signup': { publish: { message: doc } }, 'user/logout': { subscribe: { message: doc } } } } } as any, pointer: '', id: 'message' }); + // expect(d.channels()).toBeInstanceOf(Channels); + // expect(d.channels().all()).toHaveLength(2); + // expect(d.channels().all()[0]).toBeInstanceOf(Channel); + // expect(d.channels().all()[0].address()).toEqual('user/signup'); + // expect(d.channels().all()[1]).toBeInstanceOf(Channel); + // expect(d.channels().all()[1].address()).toEqual('user/logout'); + // }); + + // it('should return collection of channels - do not duplicate channels', function() { + // const doc = {}; + // const d = new Message(doc, { asyncapi: { parsed: { channels: { 'user/signup': { publish: { message: doc }, subscribe: { message: doc } } } } } as any, pointer: '', id: 'message' }); + // expect(d.channels()).toBeInstanceOf(Channels); + // expect(d.channels().all()).toHaveLength(1); + // expect(d.channels().all()[0]).toBeInstanceOf(Channel); + // expect(d.channels().all()[0].address()).toEqual('user/signup'); + // }); + // }); + + // describe('.operations()', function() { + // it('should return collection of operations - single operation', function() { + // const doc = {}; + // const d = new Message(doc, { asyncapi: { parsed: { channels: { 'user/signup': { publish: { message: doc } } } } } as any, pointer: '', id: 'message' }); + // expect(d.operations()).toBeInstanceOf(Operations); + // expect(d.operations().all()).toHaveLength(1); + // expect(d.operations().all()[0]).toBeInstanceOf(Operation); + // expect(d.operations().all()[0].action()).toEqual('publish'); + // }); + + // it('should return collection of operations - multiple operations', function() { + // const doc = {}; + // const d = new Message(doc, { asyncapi: { parsed: { channels: { 'user/signup': { publish: { message: doc }, subscribe: { message: doc } } } } } as any, pointer: '', id: 'message' }); + // expect(d.operations()).toBeInstanceOf(Operations); + // expect(d.operations().all()).toHaveLength(2); + // expect(d.operations().all()[0]).toBeInstanceOf(Operation); + // expect(d.operations().all()[0].action()).toEqual('subscribe'); + // expect(d.operations().all()[1]).toBeInstanceOf(Operation); + // expect(d.operations().all()[1].action()).toEqual('publish'); + // }); + + // it('should return collection of operations - multiple operations on different channels', function() { + // const doc = {}; + // const d = new Message(doc, { asyncapi: { parsed: { channels: { 'user/signup': { publish: { message: doc } }, 'user/logout': { subscribe: { message: doc } } } } } as any, pointer: '', id: 'message' }); + // expect(d.operations()).toBeInstanceOf(Operations); + // expect(d.operations().all()).toHaveLength(2); + // expect(d.operations().all()[0]).toBeInstanceOf(Operation); + // expect(d.operations().all()[0].action()).toEqual('publish'); + // expect(d.operations().all()[1]).toBeInstanceOf(Operation); + // expect(d.operations().all()[1].action()).toEqual('subscribe'); + // }); + // }); + + describe('.traits()', function() { + it('should return collection of traits', function() { + const doc = { traits: [{ messageId: '...' }] }; + const d = new Message(doc); + expect(d.traits()).toBeInstanceOf(MessageTraits); + expect(d.traits().all()).toHaveLength(1); + expect(d.traits().all()[0]).toBeInstanceOf(MessageTrait); + }); + + it('should return collection of traits when value is undefined', function() { + const doc = {}; + const d = new Message(doc); + expect(d.traits()).toBeInstanceOf(MessageTraits); + expect(d.traits().all()).toHaveLength(0); + }); + }); + + describe('mixins', function() { + assertBindings(Message); + assertDescription(Message); + assertExtensions(Message); + assertExternalDocumentation(Message); + assertTags(Message); + }); +}); diff --git a/test/models/v3/oauth-flow.spec.ts b/test/models/v3/oauth-flow.spec.ts new file mode 100644 index 000000000..882cc0105 --- /dev/null +++ b/test/models/v3/oauth-flow.spec.ts @@ -0,0 +1,39 @@ +import { OAuthFlow } from '../../../src/models/v3/oauth-flow'; + +import { assertExtensions } from './utils'; + +import type { v3 } from '../../../src/spec-types'; + +const flowObject = { + authorizationUrl: 'https://example.com/api/oauth/dialog', + scopes: { + 'write:pets': 'modify pets in your account', + 'read:pets': 'read your pets' + } +}; + +const flow = new OAuthFlow(flowObject); +const emptyObject = new OAuthFlow({} as v3.OAuthFlowObject); + +describe('OAuth Flow', function() { + describe('.authorizationUrl()', function() { + it('should reutrn undefined if no authorizationUrl present', function() { + expect(emptyObject.authorizationUrl()).toBeUndefined(); + }); + + it('should return authrozationUrl ', function() { + expect(flow.authorizationUrl()).toMatch(flowObject.authorizationUrl); + }); + }); + + describe('.scopes()', function() { + it('should return scopes if present', function() { + expect(emptyObject.scopes()).toBeUndefined(); + expect(flow.scopes()!['write:pets']).toMatch(flowObject.scopes['write:pets']); + }); + }); + + describe('mixins', function() { + assertExtensions(OAuthFlow); + }); +}); diff --git a/test/models/v3/oauth-flows.spec.ts b/test/models/v3/oauth-flows.spec.ts new file mode 100644 index 000000000..5dbd11e23 --- /dev/null +++ b/test/models/v3/oauth-flows.spec.ts @@ -0,0 +1,42 @@ +import { OAuthFlows } from '../../../src/models/v3/oauth-flows'; +import { OAuthFlow } from '../../../src/models/v3/oauth-flow'; + +import { assertExtensions } from './utils'; + +const oAuthFlowsObject = { + implicit: { + authorizationUrl: 'https://example.com/api/oauth/dialog', + scopes: { + 'write:pets': 'modify pets in your account', + 'read:pets': 'read your pets' + } + }, + authorizationCode: { + authorizationUrl: 'https://example.com/api/oauth/dialog', + tokenUrl: 'https://example.com/api/oauth/token', + scopes: { + 'write:pets': 'modify pets in your account', + 'read:pets': 'read your pets' + } + } +}; + +const flows = new OAuthFlows(oAuthFlowsObject); + +describe('OAuth Flows', function() { + describe('.hasImplicit()', function() { + it('should return true', function() { + expect(flows.hasImplicit()).toBeTruthy(); + }); + }); + + describe('.implicit()', function() { + it('should return OAuthflow object', function() { + expect(flows.implicit() instanceof OAuthFlow).toBeTruthy(); + }); + }); + + describe('mixins', function() { + assertExtensions(OAuthFlows); + }); +}); \ No newline at end of file diff --git a/test/models/v3/schema.spec.ts b/test/models/v3/schema.spec.ts new file mode 100644 index 000000000..59a24050c --- /dev/null +++ b/test/models/v3/schema.spec.ts @@ -0,0 +1,815 @@ +import { Schema } from '../../../src/models/v3/schema'; + +import { assertExtensions, assertExternalDocumentation } from './utils'; +import { xParserSchemaId } from '../../../src/constants'; + +import type { v3 } from '../../../src/spec-types'; + +describe('Schema model', function() { + describe('.id()', function() { + it('should return $id of schema', function() { + const doc = { $id: '$id', [xParserSchemaId]: xParserSchemaId }; + const d = new Schema(doc, { asyncapi: {} as any, pointer: '', id: 'id' }); + expect(d.id()).toEqual('$id'); + }); + + it('should return meta id of schema as fallback', function() { + const doc = { [xParserSchemaId]: xParserSchemaId }; + const d = new Schema(doc, { asyncapi: {} as any, pointer: '', id: 'id' }); + expect(d.id()).toEqual('id'); + }); + + it(`should return ${xParserSchemaId} of schema as fallback`, function() { + const doc = { [xParserSchemaId]: xParserSchemaId }; + const d = new Schema(doc, { asyncapi: {} as any, pointer: '' }); + expect(d.id()).toEqual(xParserSchemaId); + }); + }); + + describe('.$comment()', function() { + it('should return the value', function() { + const doc = { $comment: '...' }; + const d = new Schema(doc); + expect(d.$comment()).toEqual(doc.$comment); + }); + + it('should return undefined when there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.$comment()).toBeUndefined(); + }); + }); + + describe('.$id()', function() { + it('should return the value', function() { + const doc = { $id: '...' }; + const d = new Schema(doc); + expect(d.$id()).toEqual(doc.$id); + }); + + it('should return undefined when there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.$id()).toBeUndefined(); + }); + }); + + describe('.$schema()', function() { + it('should return the value', function() { + const doc = { $schema: '...' }; + const d = new Schema(doc); + expect(d.$schema()).toEqual(doc.$schema); + }); + + it('should return fallback value when there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.$schema()).toEqual('http://json-schema.org/draft-07/schema#'); + }); + }); + + describe('.additionalItems()', function() { + it('should return the value schema object', function() { + const doc = { additionalItems: {} }; + const d = new Schema(doc); + expect(d.additionalItems()).toBeInstanceOf(Schema); + }); + + it('should return the true when there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.additionalItems()).toEqual(true); + }); + + it('should return the false where there is false value', function() { + const doc = { additionalItems: false }; + const d = new Schema(doc); + expect(d.additionalItems()).toEqual(false); + }); + + it('should return the false where there is true value', function() { + const doc = { additionalItems: true }; + const d = new Schema(doc); + expect(d.additionalItems()).toEqual(true); + }); + }); + + describe('.additionalProperties()', function() { + it('should return the value schema object', function() { + const doc = { additionalProperties: {} }; + const d = new Schema(doc); + expect(d.additionalProperties()).toBeInstanceOf(Schema); + }); + + it('should return the true when there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.additionalProperties()).toEqual(true); + }); + + it('should return the false where there is false value', function() { + const doc = { additionalProperties: false }; + const d = new Schema(doc); + expect(d.additionalProperties()).toEqual(false); + }); + + it('should return the false where there is true value', function() { + const doc = { additionalProperties: true }; + const d = new Schema(doc); + expect(d.additionalProperties()).toEqual(true); + }); + }); + + describe('.allOf()', function() { + it('should return collection of schemas', function() { + const doc = { allOf: [{}, {}] }; + const d = new Schema(doc); + expect(Array.isArray(d.allOf())).toEqual(true); + expect(d.allOf()).toHaveLength(2); + expect((d.allOf() as any)[0]).toBeInstanceOf(Schema); + expect((d.allOf() as any)[1]).toBeInstanceOf(Schema); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.allOf()).toBeUndefined(); + }); + }); + + describe('.anyOf()', function() { + it('should return collection of schemas', function() { + const doc = { anyOf: [{}, {}] }; + const d = new Schema(doc); + expect(Array.isArray(d.anyOf())).toEqual(true); + expect(d.anyOf()).toHaveLength(2); + expect((d.anyOf() as any)[0]).toBeInstanceOf(Schema); + expect((d.anyOf() as any)[1]).toBeInstanceOf(Schema); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.anyOf()).toBeUndefined(); + }); + }); + + describe('.const()', function() { + it('should return value', function() { + const doc = { const: '...' }; + const d = new Schema(doc); + expect(d.const()).toEqual(doc.const); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.const()).toBeUndefined(); + }); + }); + + describe('.contains()', function() { + it('should return value', function() { + const doc = { contains: {} }; + const d = new Schema(doc); + expect(d.contains()).toBeInstanceOf(Schema); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.contains()).toBeUndefined(); + }); + }); + + describe('.contentEncoding()', function() { + it('should return value', function() { + const doc = { contentEncoding: '...' }; + const d = new Schema(doc); + expect(d.contentEncoding()).toEqual(doc.contentEncoding); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.contentEncoding()).toBeUndefined(); + }); + }); + + describe('.contentMediaType()', function() { + it('should return value', function() { + const doc = { contentMediaType: '...' }; + const d = new Schema(doc); + expect(d.contentMediaType()).toEqual(doc.contentMediaType); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.contentMediaType()).toBeUndefined(); + }); + }); + + describe('.default()', function() { + it('should return value', function() { + const doc = { default: '...' }; + const d = new Schema(doc); + expect(d.default()).toEqual(doc.default); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.default()).toBeUndefined(); + }); + }); + + describe('.definitions()', function() { + it('should return map of definitions', function() { + const doc = { definitions: { def: {} } }; + const d = new Schema(doc); + expect(typeof d.definitions() === 'object').toEqual(true); + expect((d.definitions() as any)['def']).toBeInstanceOf(Schema); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.default()).toBeUndefined(); + }); + }); + + describe('.description()', function() { + it('should return value', function() { + const doc = { description: '...' }; + const d = new Schema(doc); + expect(d.description()).toEqual(doc.description); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.description()).toBeUndefined(); + }); + }); + + describe('.dependencies()', function() { + it('should return map of dependencies (schema case)', function() { + const doc = { dependencies: { dep: {} } }; + const d = new Schema(doc); + expect(typeof d.dependencies() === 'object').toEqual(true); + expect((d.dependencies() as any)['dep']).toBeInstanceOf(Schema); + }); + + it('should return map of dependencies (array case)', function() { + const doc = { dependencies: { array: [] } }; + const d = new Schema(doc); + expect(typeof d.dependencies() === 'object').toEqual(true); + expect(Array.isArray((d.dependencies() as any)['array'])).toEqual(true); + }); + + it('should return map of dependencies (schema and array case)', function() { + const doc = { dependencies: { dep: {}, array: [] } }; + const d = new Schema(doc); + expect(typeof d.dependencies() === 'object').toEqual(true); + expect((d.dependencies() as any)['dep']).toBeInstanceOf(Schema); + expect(Array.isArray((d.dependencies() as any)['array'])).toEqual(true); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.dependencies()).toBeUndefined(); + }); + }); + + describe('.deprecated()', function() { + it('should return value', function() { + const doc = { deprecated: true }; + const d = new Schema(doc); + expect(d.deprecated()).toEqual(doc.deprecated); + }); + + it('should return false where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.deprecated()).toEqual(false); + }); + }); + + describe('.discriminator()', function() { + it('should return value', function() { + const doc = { discriminator: '...' }; + const d = new Schema(doc); + expect(d.discriminator()).toEqual(doc.discriminator); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.discriminator()).toBeUndefined(); + }); + }); + + describe('.else()', function() { + it('should return value', function() { + const doc = { else: {} }; + const d = new Schema(doc); + expect(d.else()).toBeInstanceOf(Schema); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.else()).toBeUndefined(); + }); + }); + + describe('.enum()', function() { + it('should return value', function() { + const doc = { enum: ['example'] }; + const d = new Schema(doc); + expect(Array.isArray(d.enum())).toEqual(true); + expect((d.enum() as any)[0]).toEqual('example'); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.else()).toBeUndefined(); + }); + }); + + describe('.examples()', function() { + it('should return value', function() { + const doc = { examples: ['example'] }; + const d = new Schema(doc); + expect(Array.isArray(d.examples())).toEqual(true); + expect((d.examples() as any)[0]).toEqual('example'); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.examples()).toBeUndefined(); + }); + }); + + describe('.exclusiveMaximum()', function() { + it('should return value', function() { + const doc = { exclusiveMaximum: 2137 }; + const d = new Schema(doc); + expect(d.exclusiveMaximum()).toEqual(doc.exclusiveMaximum); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.exclusiveMaximum()).toBeUndefined(); + }); + }); + + describe('.exclusiveMinimum()', function() { + it('should return value', function() { + const doc = { exclusiveMinimum: 2137 }; + const d = new Schema(doc); + expect(d.exclusiveMinimum()).toEqual(doc.exclusiveMinimum); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.exclusiveMinimum()).toBeUndefined(); + }); + }); + + describe('.format()', function() { + it('should return value', function() { + const doc = { format: '...' }; + const d = new Schema(doc); + expect(d.format()).toEqual(doc.format); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.format()).toBeUndefined(); + }); + }); + + describe('.isBooleanSchema()', function() { + it('should return true where value is true boolean', function() { + const d = new Schema(true as any); + expect(d.isBooleanSchema()).toEqual(true); + }); + + it('should return true where value is false boolean', function() { + const d = new Schema(false as any); + expect(d.isBooleanSchema()).toEqual(true); + }); + + it('should return false where value is object', function() { + const d = new Schema({}); + expect(d.isBooleanSchema()).toEqual(false); + }); + }); + + describe('.if()', function() { + it('should return value', function() { + const doc = { if: {} }; + const d = new Schema(doc); + expect(d.if()).toBeInstanceOf(Schema); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.if()).toBeUndefined(); + }); + }); + + describe('.isCircular()', function() { + it('should return a true when schema has circular reference', function() { + const doc: v3.AsyncAPISchemaObject = { + properties: { + nonCircular: { + type: 'string', + }, + circular: {}, + } + }; + doc.properties!.circular = doc; + const d = new Schema(doc); + expect(d.isCircular()).toEqual(false); + expect((d.properties() as any)['nonCircular'].isCircular()).toEqual(false); + expect((d.properties() as any)['circular'].isCircular()).toEqual(true); + }); + }); + + describe('.items()', function() { + it('should return schema instance', function() { + const doc = { items: {} }; + const d = new Schema(doc); + expect(d.items()).toBeInstanceOf(Schema); + }); + + it('should return collection of schemas', function() { + const doc = { items: [{}, {}] }; + const d = new Schema(doc); + expect(Array.isArray(d.items())).toEqual(true); + expect(d.items()).toHaveLength(2); + expect((d.items() as any)[0]).toBeInstanceOf(Schema); + expect((d.items() as any)[1]).toBeInstanceOf(Schema); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.items()).toBeUndefined(); + }); + }); + + describe('.maximum()', function() { + it('should return value', function() { + const doc = { maximum: 2137 }; + const d = new Schema(doc); + expect(d.maximum()).toEqual(doc.maximum); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.maximum()).toBeUndefined(); + }); + }); + + describe('.maxItems()', function() { + it('should return value', function() { + const doc = { maxItems: 2137 }; + const d = new Schema(doc); + expect(d.maxItems()).toEqual(doc.maxItems); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.maxItems()).toBeUndefined(); + }); + }); + + describe('.maxLength()', function() { + it('should return value', function() { + const doc = { maxLength: 2137 }; + const d = new Schema(doc); + expect(d.maxLength()).toEqual(doc.maxLength); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.maxLength()).toBeUndefined(); + }); + }); + + describe('.maxProperties()', function() { + it('should return value', function() { + const doc = { maxProperties: 2137 }; + const d = new Schema(doc); + expect(d.maxProperties()).toEqual(doc.maxProperties); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.maxProperties()).toBeUndefined(); + }); + }); + + describe('.minimum()', function() { + it('should return value', function() { + const doc = { minimum: 2137 }; + const d = new Schema(doc); + expect(d.minimum()).toEqual(doc.minimum); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.minimum()).toBeUndefined(); + }); + }); + + describe('.minItems()', function() { + it('should return value', function() { + const doc = { minItems: 2137 }; + const d = new Schema(doc); + expect(d.minItems()).toEqual(doc.minItems); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.minItems()).toBeUndefined(); + }); + }); + + describe('.minLength()', function() { + it('should return value', function() { + const doc = { minLength: 2137 }; + const d = new Schema(doc); + expect(d.minLength()).toEqual(doc.minLength); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.minLength()).toBeUndefined(); + }); + }); + + describe('.minProperties()', function() { + it('should return value', function() { + const doc = { minProperties: 2137 }; + const d = new Schema(doc); + expect(d.minProperties()).toEqual(doc.minProperties); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.minProperties()).toBeUndefined(); + }); + }); + + describe('.multipleOf()', function() { + it('should return value', function() { + const doc = { multipleOf: 2137 }; + const d = new Schema(doc); + expect(d.multipleOf()).toEqual(doc.multipleOf); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.multipleOf()).toBeUndefined(); + }); + }); + + describe('.not()', function() { + it('should return value', function() { + const doc = { not: {} }; + const d = new Schema(doc); + expect(d.not()).toBeInstanceOf(Schema); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.not()).toBeUndefined(); + }); + }); + + describe('.oneOf()', function() { + it('should return collection of schemas', function() { + const doc = { oneOf: [{}, {}] }; + const d = new Schema(doc); + expect(Array.isArray(d.oneOf())).toEqual(true); + expect(d.oneOf()).toHaveLength(2); + expect((d.oneOf() as any)[0]).toBeInstanceOf(Schema); + expect((d.oneOf() as any)[1]).toBeInstanceOf(Schema); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.oneOf()).toBeUndefined(); + }); + }); + + describe('.pattern()', function() { + it('should return value', function() { + const doc = { pattern: '...' }; + const d = new Schema(doc); + expect(d.pattern()).toEqual(doc.pattern); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.pattern()).toBeUndefined(); + }); + }); + + describe('.patternProperties()', function() { + it('should return map of patternProperties', function() { + const doc = { patternProperties: { prop: {} } }; + const d = new Schema(doc); + expect(typeof d.patternProperties() === 'object').toEqual(true); + expect((d.patternProperties() as any)['prop']).toBeInstanceOf(Schema); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.patternProperties()).toBeUndefined(); + }); + }); + + describe('.properties()', function() { + it('should return map of properties', function() { + const doc = { properties: { prop: {} } }; + const d = new Schema(doc); + expect(typeof d.properties() === 'object').toEqual(true); + expect((d.properties() as any)['prop']).toBeInstanceOf(Schema); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.properties()).toBeUndefined(); + }); + }); + + describe('.property()', function() { + it('should return property', function() { + const doc = { properties: { prop: {} } }; + const d = new Schema(doc); + expect(d.property('prop')).toBeInstanceOf(Schema); + }); + + it('should return undefined where there is no property', function() { + const doc = { properties: { another: {} } }; + const d = new Schema(doc); + expect(d.property('prop')).toBeUndefined(); + }); + + it('should return undefined where there is no properties', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.property('prop')).toBeUndefined(); + }); + }); + + describe('.propertyNames()', function() { + it('should return value', function() { + const doc = { propertyNames: {} }; + const d = new Schema(doc); + expect(d.propertyNames()).toBeInstanceOf(Schema); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.propertyNames()).toBeUndefined(); + }); + }); + + describe('.readOnly()', function() { + it('should return value', function() { + const doc = { readOnly: true }; + const d = new Schema(doc); + expect(d.readOnly()).toEqual(doc.readOnly); + }); + + it('should return false where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.readOnly()).toEqual(false); + }); + }); + + describe('.required()', function() { + it('should return array of required properties', function() { + const doc = { required: ['prop1', 'prop2'] }; + const d = new Schema(doc); + expect(d.required()).toEqual(doc.required); + }); + + it('should return false where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.required()).toBeUndefined(); + }); + }); + + describe('.then()', function() { + it('should return value', function() { + const doc = { then: {} }; + const d = new Schema(doc); + expect(d.then()).toBeInstanceOf(Schema); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.then()).toBeUndefined(); + }); + }); + + describe('.title()', function() { + it('should return value', function() { + const doc = { title: '...' }; + const d = new Schema(doc); + expect(d.title()).toEqual(doc.title); + }); + + it('should return undefined where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.title()).toBeUndefined(); + }); + }); + + describe('.type()', function() { + it('should return single type', function() { + const doc: v3.AsyncAPISchemaObject = { type: 'object' }; + const d = new Schema(doc); + expect(d.type()).toEqual(doc.type); + }); + + it('should return array of type', function() { + const doc: v3.AsyncAPISchemaObject = { type: ['object', 'array'] }; + const d = new Schema(doc); + expect(d.type()).toEqual(doc.type); + }); + + it('should return false where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.type()).toBeUndefined(); + }); + }); + + describe('.uniqueItems()', function() { + it('should return value', function() { + const doc = { uniqueItems: true }; + const d = new Schema(doc); + expect(d.uniqueItems()).toEqual(doc.uniqueItems); + }); + + it('should return false where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.uniqueItems()).toEqual(false); + }); + }); + + describe('.writeOnly()', function() { + it('should return value', function() { + const doc = { writeOnly: true }; + const d = new Schema(doc); + expect(d.writeOnly()).toEqual(doc.writeOnly); + }); + + it('should return false where there is no value', function() { + const doc = {}; + const d = new Schema(doc); + expect(d.writeOnly()).toEqual(false); + }); + }); + + describe('mixins', function() { + assertExtensions(Schema); + assertExternalDocumentation(Schema); + }); +}); diff --git a/test/models/v3/server-variable.spec.ts b/test/models/v3/server-variable.spec.ts new file mode 100644 index 000000000..0a4c0e98b --- /dev/null +++ b/test/models/v3/server-variable.spec.ts @@ -0,0 +1,34 @@ +import { ServerVariable } from '../../../src/models/v3/server-variable'; + +import { assertDescription, assertExtensions } from './utils'; + +const doc = { + description: 'Secure connection (TLS) is available through port 8883.', + default: '1883', + enum: ['1883', '8883'] +}; + +const sv = new ServerVariable(doc, { asyncapi: {} as any, pointer: '', id: 'doc' }); + +describe('Server Variable ', function() { + describe('.id()', function() { + expect(sv.id()).toMatch('doc'); + }); + + describe('.hasAllowedValue()', function() { + it('should return true when enum is passed', function() { + expect(sv.hasAllowedValues()).toBeTruthy(); + }); + }); + + describe('.allowedValue()', function() { + it('should return enum object', function() { + expect(sv.allowedValues()).toEqual(doc.enum); + }); + }); + + describe('mixins', function() { + assertDescription(ServerVariable); + assertExtensions(ServerVariable); + }); +}); diff --git a/test/models/v3/tag.spec.ts b/test/models/v3/tag.spec.ts new file mode 100644 index 000000000..04086f139 --- /dev/null +++ b/test/models/v3/tag.spec.ts @@ -0,0 +1,19 @@ +import { Tag } from '../../../src/models/v3/tag'; + +import { assertDescription, assertExtensions, assertExternalDocumentation } from './utils'; + +describe('Tag model', function() { + describe('.name()', function() { + it('should return the value', function() { + const doc = { name: 'LeChuck' }; + const d = new Tag(doc); + expect(d.name()).toEqual(doc.name); + }); + }); + + describe('mixins inheritance', function() { + assertDescription(Tag); + assertExtensions(Tag); + assertExternalDocumentation(Tag); + }); +}); diff --git a/test/models/v3/utils.ts b/test/models/v3/utils.ts new file mode 100644 index 000000000..ee1f2750d --- /dev/null +++ b/test/models/v3/utils.ts @@ -0,0 +1,150 @@ +import { BindingsV3, ExtensionsV3, ExternalDocumentationV3, TagsV3 } from '../../../src/models/v3'; + +import type { Constructor } from '../../../src/models/utils'; +import type { BindingsMixinInterface, DescriptionMixinInterface, ExtensionsMixinInterface, ExternalDocumentationMixinInterface, TagsMixinInterface } from '../../../src/models/mixins'; + +type DeepPartial = T extends object ? { [P in keyof T]?: DeepPartial } : T; + +export function serializeInput(data: DeepPartial): T { + return data as T; +} + +export function assertBindings(model: Constructor) { + describe('.bindings()', () => { + const doc1 = { bindings: { amqp: { test: 'test1' } } }; + const doc2 = { bindings: {} }; + const doc3 = {}; + const d1 = new model(doc1); + const d2 = new model(doc2); + const d3 = new model(doc3); + + it('should return a collection of bindings', () => { + expect(d1.bindings()).toBeInstanceOf(BindingsV3); + expect(d1.bindings().length).toEqual(1); + }); + + it('should return an empty object', () => { + expect(d2.bindings()).toBeInstanceOf(BindingsV3); + expect(d2.bindings().length).toEqual(0); + expect(d3.bindings()).toBeInstanceOf(BindingsV3); + expect(d3.bindings().length).toEqual(0); + }); + }); +} + +export function assertDescription(model: Constructor) { + describe('.hasDescription()', () => { + const doc1 = { description: 'Testing' }; + const doc2 = { description: '' }; + const doc3 = {}; + const d1 = new model(doc1); + const d2 = new model(doc2); + const d3 = new model(doc3); + + it('should return a boolean indicating if the object has description', () => { + expect(d1.hasDescription()).toEqual(true); + expect(d2.hasDescription()).toEqual(false); + expect(d3.hasDescription()).toEqual(false); + }); + }); + + describe('.description()', () => { + const doc1 = { description: 'Testing' }; + const doc2 = { description: '' }; + const doc3 = {}; + const d1 = new model(doc1); + const d2 = new model(doc2); + const d3 = new model(doc3); + + it('should return a value', () => { + expect(d1.description()).toEqual(doc1.description); + expect(d2.description()).toEqual(''); + }); + + it('should return an undefined', () => { + expect(d3.description()).toEqual(undefined); + }); + }); +} + +export function assertExtensions(model: Constructor) { + describe('.extensions()', () => { + const doc1 = { 'x-test': 'testing', test: 'testing' }; + const doc2 = { test: 'testing' }; + const doc3 = {}; + const d1 = new model(doc1); + const d2 = new model(doc2); + const d3 = new model(doc3); + + it('should return a collection with extensions', () => { + expect(d1.extensions()).toBeInstanceOf(ExtensionsV3); + expect(d1.extensions().length).toEqual(1); + }); + + it('should return a empty object', () => { + expect(d2.extensions()).toBeInstanceOf(ExtensionsV3); + expect(d2.extensions().length).toEqual(0); + expect(d3.extensions()).toBeInstanceOf(ExtensionsV3); + expect(d3.extensions().length).toEqual(0); + }); + }); +} + +export function assertExternalDocumentation(model: Constructor) { + describe('.hasExternalDocs()', () => { + const doc1 = { externalDocs: { url: 'test.com' } }; + const doc2 = { externalDocs: {} }; + const doc3 = {}; + const d1 = new model(doc1); + const d2 = new model(doc2); + const d3 = new model(doc3); + + it('should return a boolean indicating if the object has externalDocs', () => { + expect(d1.hasExternalDocs()).toEqual(true); + expect(d2.hasExternalDocs()).toEqual(false); + expect(d3.hasExternalDocs()).toEqual(false); + }); + }); + + describe('.externalDocs()', () => { + const doc1 = { externalDocs: { url: 'test.com' } }; + const doc2 = { externalDocs: {} }; + const doc3 = {}; + const d1 = new model(doc1); + const d2 = new model(doc2); + const d3 = new model(doc3); + + it('should return a externalDocs object', () => { + expect(d1.externalDocs()).toBeInstanceOf(ExternalDocumentationV3); + expect(d1.externalDocs()!.json()).toEqual(doc1.externalDocs); + }); + + it('should return a undefined', () => { + expect(d2.externalDocs()).toEqual(undefined); + expect(d3.externalDocs()).toEqual(undefined); + }); + }); +} + +export function assertTags(model: Constructor) { + describe('tags', () => { + const doc1 = { tags: [{ name: 'test1' }, { name: 'test2' }] }; + const doc2 = { tags: [] }; + const doc3 = {}; + const d1 = new model(doc1); + const d2 = new model(doc2); + const d3 = new model(doc3); + + it('should return an array of tag objects', () => { + expect(d1.tags()).toBeInstanceOf(TagsV3); + expect(d1.tags().length).toEqual(2); + }); + + it('should return an empty array', () => { + expect(d2.tags()).toBeInstanceOf(TagsV3); + expect(d2.tags().length).toEqual(0); + expect(d3.tags()).toBeInstanceOf(TagsV3); + expect(d3.tags().length).toEqual(0); + }); + }); +} \ No newline at end of file From 715b97d361e2774794ea3f4b1eb285e270119355 Mon Sep 17 00:00:00 2001 From: Matatjahu Date: Fri, 21 Oct 2022 10:46:23 +0200 Subject: [PATCH 13/17] add more tests --- src/models/v2/operation-trait.ts | 8 +- src/models/v3/operation-trait.ts | 29 ++-- src/models/v3/operation.ts | 12 ++ test/models/v2/operation-trait.spec.ts | 48 ++++-- test/models/v2/operation.spec.ts | 36 ----- test/models/v2/server.spec.ts | 4 +- test/models/v3/channel.spec.ts | 144 +++++++++++++++++ test/models/v3/operation-trait.spec.ts | 129 +++++++++++++++ test/models/v3/operation.spec.ts | 133 +++++++++++++++ test/models/v3/server.spec.ts | 214 +++++++++++++++++++++++++ 10 files changed, 691 insertions(+), 66 deletions(-) create mode 100644 test/models/v3/channel.spec.ts create mode 100644 test/models/v3/operation-trait.spec.ts create mode 100644 test/models/v3/operation.spec.ts create mode 100644 test/models/v3/server.spec.ts diff --git a/src/models/v2/operation-trait.ts b/src/models/v2/operation-trait.ts index 810f5dc70..7b9d78a5a 100644 --- a/src/models/v2/operation-trait.ts +++ b/src/models/v2/operation-trait.ts @@ -21,14 +21,14 @@ export class OperationTrait implements Ope return new Servers(servers); } + channels(): ChannelsInterface { + if (this._json.channel) { + return new Channels([ + this.createModel(Channel, this._json.channel as v3.ChannelObject, { id: '', pointer: this.jsonPath('channel') }) + ]); + } + return new Channels([]); + } + messages(): MessagesInterface { const messages: MessageInterface[] = []; this.channels().forEach(channel => { diff --git a/test/models/v2/operation-trait.spec.ts b/test/models/v2/operation-trait.spec.ts index 538c3d043..363bd0fb2 100644 --- a/test/models/v2/operation-trait.spec.ts +++ b/test/models/v2/operation-trait.spec.ts @@ -7,11 +7,17 @@ import { assertBindings, assertDescription, assertExtensions, assertExternalDocu describe('OperationTrait model', function() { describe('.id()', function() { - it('should return operationId', function() { + it('should return the value', function() { const doc = { operationId: '...' }; const d = new OperationTrait(doc); expect(d.id()).toEqual(doc.operationId); }); + + it('should return undefined when there is no value', function() { + const doc = {}; + const d = new OperationTrait(doc); + expect(d.id()).toBeUndefined(); + }); }); describe('.hasId()', function() { @@ -28,17 +34,39 @@ describe('OperationTrait model', function() { }); }); - describe('.id()', function() { - it('should return the value', function() { - const doc = { operationId: '...' }; - const d = new OperationTrait(doc); - expect(d.id()).toEqual(doc.operationId); + describe('.action()', function() { + it('should return kind/action of operation', function() { + const doc = {}; + const d = new OperationTrait(doc, { asyncapi: {} as any, pointer: '', id: 'trait', action: 'publish' }); + expect(d.action()).toEqual('publish'); }); - - it('should return undefined when there is no value', function() { + }); + + describe('.isSend()', function() { + it('should return true when operation is subscribe', function() { const doc = {}; - const d = new OperationTrait(doc); - expect(d.id()).toBeUndefined(); + const d = new OperationTrait(doc, { asyncapi: {} as any, pointer: '', id: 'trait', action: 'subscribe' }); + expect(d.isSend()).toBeTruthy(); + }); + + it('should return false when operation is publish', function() { + const doc = {}; + const d = new OperationTrait(doc, { asyncapi: {} as any, pointer: '', id: 'trait', action: 'publish' }); + expect(d.isSend()).toBeFalsy(); + }); + }); + + describe('.isReceive()', function() { + it('should return true when operation is publish', function() { + const doc = {}; + const d = new OperationTrait(doc, { asyncapi: {} as any, pointer: '', id: 'trait', action: 'publish' }); + expect(d.isReceive()).toBeTruthy(); + }); + + it('should return false when operation is subscribe', function() { + const doc = {}; + const d = new OperationTrait(doc, { asyncapi: {} as any, pointer: '', id: 'trait', action: 'subscribe' }); + expect(d.isReceive()).toBeFalsy(); }); }); diff --git a/test/models/v2/operation.spec.ts b/test/models/v2/operation.spec.ts index a01545413..abfdc0ede 100644 --- a/test/models/v2/operation.spec.ts +++ b/test/models/v2/operation.spec.ts @@ -83,42 +83,6 @@ describe('Operation model', function() { }); }); - describe('.action()', function() { - it('should return kind/action of operation', function() { - const doc = {}; - const d = new OperationTrait(doc, { asyncapi: {} as any, pointer: '', id: 'trait', action: 'publish' }); - expect(d.action()).toEqual('publish'); - }); - }); - - describe('.isSend()', function() { - it('should return true when operation is subscribe', function() { - const doc = {}; - const d = new OperationTrait(doc, { asyncapi: {} as any, pointer: '', id: 'trait', action: 'subscribe' }); - expect(d.isSend()).toBeTruthy(); - }); - - it('should return false when operation is publish', function() { - const doc = {}; - const d = new OperationTrait(doc, { asyncapi: {} as any, pointer: '', id: 'trait', action: 'publish' }); - expect(d.isSend()).toBeFalsy(); - }); - }); - - describe('.isReceive()', function() { - it('should return true when operation is publish', function() { - const doc = {}; - const d = new OperationTrait(doc, { asyncapi: {} as any, pointer: '', id: 'trait', action: 'publish' }); - expect(d.isReceive()).toBeTruthy(); - }); - - it('should return false when operation is subscribe', function() { - const doc = {}; - const d = new OperationTrait(doc, { asyncapi: {} as any, pointer: '', id: 'trait', action: 'subscribe' }); - expect(d.isReceive()).toBeFalsy(); - }); - }); - describe('.messages()', function() { it('should return collection of messages - single message', function() { const doc = { message: { messageId: '...' } }; diff --git a/test/models/v2/server.spec.ts b/test/models/v2/server.spec.ts index 97e429ea4..0da5db926 100644 --- a/test/models/v2/server.spec.ts +++ b/test/models/v2/server.spec.ts @@ -7,12 +7,12 @@ import { Operation } from '../../../src/models/v2/operation'; import { Server } from '../../../src/models/v2/server'; import { ServerVariables } from '../../../src/models/server-variables'; import { SecurityScheme } from '../../../src/models/v2/security-scheme'; +import { SecurityRequirements } from '../../../src/models/security-requirements'; +import { SecurityRequirement } from '../../../src/models/v2/security-requirement'; import { serializeInput, assertBindings, assertDescription, assertExtensions } from './utils'; import type { v2 } from '../../../src/spec-types'; -import { SecurityRequirements } from '../../../src/models/security-requirements'; -import { SecurityRequirement } from '../../../src/models/v2/security-requirement'; const doc = { development: { diff --git a/test/models/v3/channel.spec.ts b/test/models/v3/channel.spec.ts new file mode 100644 index 000000000..75b2cc6be --- /dev/null +++ b/test/models/v3/channel.spec.ts @@ -0,0 +1,144 @@ +import { Channel } from '../../../src/models/v3/channel'; +import { ChannelParameters } from '../../../src/models/channel-parameters'; +import { ChannelParameter } from '../../../src/models/v3/channel-parameter'; +import { Operations } from '../../../src/models/operations'; +import { Operation } from '../../../src/models/v3/operation'; +import { Messages } from '../../../src/models/messages'; +import { Message } from '../../../src/models/v3/message'; +import { Servers } from '../../../src/models/servers'; +import { Server } from '../../../src/models/v3/server'; + +import { serializeInput, assertBindings, assertDescription, assertExtensions } from './utils'; + +import type { v3 } from '../../../src/spec-types'; + +describe('Channel model', function() { + describe('.id()', function() { + it('should return id of model', function() { + const doc = serializeInput({}); + const d = new Channel(doc, { asyncapi: {} as any, pointer: '', id: 'channel' }); + expect(d.id()).toEqual('channel'); + }); + }); + + describe('.address()', function() { + it('should return the value', function() { + const doc = serializeInput({ address: 'user/signup' }); + const d = new Channel(doc, { asyncapi: {} as any, pointer: '', id: 'channel' }); + expect(d.address()).toEqual('user/signup'); + }); + }); + + // describe('.servers()', function() { + // it('should return collection of servers - available on all servers', function() { + // const doc = serializeInput({}); + // const d = new Channel(doc, { asyncapi: { parsed: { servers: { someServer1: {}, someServer2: {}, } } } as any, pointer: '', id: 'channel' }); + // expect(d.servers()).toBeInstanceOf(Servers); + // expect(d.servers().all()).toHaveLength(2); + // expect(d.servers().all()[0]).toBeInstanceOf(Server); + // expect(d.servers().all()[0].id()).toEqual('someServer1'); + // expect(d.servers().all()[1]).toBeInstanceOf(Server); + // expect(d.servers().all()[1].id()).toEqual('someServer2'); + // }); + + // it('should return collection of servers - available on all servers (empty servers array)', function() { + // const doc = serializeInput({ servers: [] }); + // const d = new Channel(doc, { asyncapi: { parsed: { servers: { someServer1: {}, someServer2: {}, } } } as any, pointer: '', id: 'channel' }); + // expect(d.servers()).toBeInstanceOf(Servers); + // expect(d.servers().all()).toHaveLength(2); + // expect(d.servers().all()[0]).toBeInstanceOf(Server); + // expect(d.servers().all()[0].id()).toEqual('someServer1'); + // expect(d.servers().all()[1]).toBeInstanceOf(Server); + // expect(d.servers().all()[1].id()).toEqual('someServer2'); + // }); + + // it('should return collection of servers - available only on particular ones', function() { + // const someServer2 = {} + // const doc = serializeInput({ servers: [someServer2] }); + // const d = new Channel(doc, { asyncapi: { parsed: { servers: { someServer1: {}, someServer2, } } } as any, pointer: '', id: 'channel' }); + // expect(d.servers()).toBeInstanceOf(Servers); + // expect(d.servers().all()).toHaveLength(1); + // expect(d.servers().all()[0]).toBeInstanceOf(Server); + // expect(d.servers().all()[0].id()).toEqual('someServer2'); + // }); + // }); + + // describe('.operations()', function() { + // it('should return collection of operations - publish operation', function() { + // const doc = serializeInput({ publish: {} }); + // const d = new Channel(doc); + // expect(d.operations()).toBeInstanceOf(Operations); + // expect(d.operations().all()).toHaveLength(1); + // expect(d.operations().all()[0]).toBeInstanceOf(Operation); + // expect(d.operations().all()[0].action()).toEqual('publish'); + // }); + + // it('should return collection of operations - subscribe operation', function() { + // const doc = serializeInput({ subscribe: {} }); + // const d = new Channel(doc); + // expect(d.operations()).toBeInstanceOf(Operations); + // expect(d.operations().all()).toHaveLength(1); + // expect(d.operations().all()[0]).toBeInstanceOf(Operation); + // expect(d.operations().all()[0].action()).toEqual('subscribe'); + // }); + + // it('should return collection of operations - both operations', function() { + // const doc = serializeInput({ publish: {}, subscribe: {} }); + // const d = new Channel(doc); + // expect(d.operations()).toBeInstanceOf(Operations); + // expect(d.operations().all()).toHaveLength(2); + // expect(d.operations().all()[0]).toBeInstanceOf(Operation); + // expect(d.operations().all()[0].action()).toEqual('publish'); + // expect(d.operations().all()[1]).toBeInstanceOf(Operation); + // expect(d.operations().all()[1].action()).toEqual('subscribe'); + // }); + // }); + + // describe('.messages()', function() { + // it('should return collection of messages - single message', function() { + // const doc = serializeInput({ publish: { message: { messageId: '...' } } }); + // const d = new Channel(doc); + // expect(d.messages()).toBeInstanceOf(Messages); + // expect(d.messages().all()).toHaveLength(1); + // expect(d.messages().all()[0]).toBeInstanceOf(Message); + // expect(d.messages().all()[0].messageId()).toEqual((doc as any).publish.message.messageId); + // }); + + // it('should return collection of messages - oneOf message', function() { + // const doc = serializeInput({ subscribe: { message: { oneOf: [{ messageId: '1' }, { messageId: '2' }] } } }); + // const d = new Channel(doc); + // expect(d.messages()).toBeInstanceOf(Messages); + // expect(d.messages().all()).toHaveLength(2); + // expect(d.messages().all()[0]).toBeInstanceOf(Message); + // expect(d.messages().all()[0].messageId()).toEqual((doc as any).subscribe.message.oneOf[0].messageId); + // expect(d.messages().all()[1]).toBeInstanceOf(Message); + // expect(d.messages().all()[1].messageId()).toEqual((doc as any).subscribe.message.oneOf[1].messageId); + // }); + + // it('should return collection of messages - single message and oneOf', function() { + // const doc = serializeInput({ publish: { message: {} }, subscribe: { message: { oneOf: [{ messageId: '1' }, { messageId: '2' }] } } }); + // const d = new Channel(doc); + // expect(d.messages()).toBeInstanceOf(Messages); + // expect(d.messages().all()).toHaveLength(3); + // }); + // }); + + describe('.parameters()', function() { + it('should return collection of channel parameters', function() { + const doc = serializeInput({ parameters: { parameter1: {}, parameter2: {} } }); + const d = new Channel(doc); + expect(d.parameters()).toBeInstanceOf(ChannelParameters); + expect(d.parameters().all()).toHaveLength(2); + expect(d.parameters().all()[0]).toBeInstanceOf(ChannelParameter); + expect(d.parameters().all()[0].id()).toEqual('parameter1'); + expect(d.parameters().all()[1]).toBeInstanceOf(ChannelParameter); + expect(d.parameters().all()[1].id()).toEqual('parameter2'); + }); + }); + + describe('mixins', function() { + assertBindings(Channel); + assertDescription(Channel); + assertExtensions(Channel); + }); +}); diff --git a/test/models/v3/operation-trait.spec.ts b/test/models/v3/operation-trait.spec.ts new file mode 100644 index 000000000..6601ee495 --- /dev/null +++ b/test/models/v3/operation-trait.spec.ts @@ -0,0 +1,129 @@ +import { OperationTrait } from '../../../src/models/v3/operation-trait'; +import { SecurityRequirement } from '../../../src/models/v3/security-requirement'; +import { SecurityRequirements } from '../../../src/models/security-requirements'; +import { SecurityScheme } from '../../../src/models/v3/security-scheme'; + +import { assertBindings, assertDescription, assertExtensions, assertExternalDocumentation, assertTags } from './utils'; + +describe('OperationTrait model', function() { + describe('.id()', function() { + it('should return the value', function() { + const d = new OperationTrait({}, { id: '...' } as any); + expect(d.id()).toEqual('...'); + }); + + it('should return undefined when there is no value', function() { + const doc = {}; + const d = new OperationTrait(doc); + expect(d.id()).toBeUndefined(); + }); + }); + + describe('.hasId()', function() { + it('should return true when there is a value', function() { + const d = new OperationTrait({}, { id: '...' } as any); + expect(d.hasId()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = {}; + const d = new OperationTrait(doc); + expect(d.hasId()).toEqual(false); + }); + }); + + describe('.action()', function() { + it('should return kind/action of operation', function() { + const d = new OperationTrait({ action: 'send', channel: {} }, { asyncapi: {} as any, pointer: '', id: 'trait' }); + expect(d.action()).toEqual('send'); + }); + }); + + describe('.isSend()', function() { + it('should return true when operation has send action', function() { + const d = new OperationTrait({ action: 'send', channel: {} }, { asyncapi: {} as any, pointer: '', id: 'trait' }); + expect(d.isSend()).toBeTruthy(); + }); + + it('should return false when operation has receive action', function() { + const doc = {}; + const d = new OperationTrait({ action: 'receive', channel: {} }, { asyncapi: {} as any, pointer: '', id: 'trait' }); + expect(d.isSend()).toBeFalsy(); + }); + }); + + describe('.isReceive()', function() { + it('should return true when operation has receive action', function() { + const doc = {}; + const d = new OperationTrait({ action: 'receive', channel: {} }, { asyncapi: {} as any, pointer: '', id: 'trait' }); + expect(d.isReceive()).toBeTruthy(); + }); + + it('should return false when operation has send action', function() { + const doc = {}; + const d = new OperationTrait({ action: 'send', channel: {} }, { asyncapi: {} as any, pointer: '', id: 'trait' }); + expect(d.isReceive()).toBeFalsy(); + }); + }); + + describe('.hasSummary()', function() { + it('should return true when there is a value', function() { + const doc = { summary: '...' }; + const d = new OperationTrait(doc); + expect(d.hasSummary()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = {}; + const d = new OperationTrait(doc); + expect(d.hasSummary()).toEqual(false); + }); + }); + + describe('.summary()', function() { + it('should return the value', function() { + const doc = { summary: '...' }; + const d = new OperationTrait(doc); + expect(d.summary()).toEqual(doc.summary); + }); + + it('should return undefined when there is no value', function() { + const doc = {}; + const d = new OperationTrait(doc); + expect(d.summary()).toBeUndefined(); + }); + }); + + describe('.security()', function() { + it('should return collection of security requirements', function() { + const doc = { security: [{ requirement: [] }] }; + const d = new OperationTrait(doc); + + const security = d.security(); + expect(Array.isArray(security)).toEqual(true); + expect(security).toHaveLength(1); + expect(security[0]).toBeInstanceOf(SecurityRequirements); + + const requirement = security[0].get('requirement') as SecurityRequirement; + expect(requirement).toBeInstanceOf(SecurityRequirement); + expect(requirement.meta().id).toEqual('requirement'); + expect(requirement.scheme()).toBeInstanceOf(SecurityScheme); + expect(requirement.scopes()).toEqual([]); + }); + + it('should return collection of security requirements when value is undefined', function() { + const doc = {}; + const d = new OperationTrait(doc); + expect(Array.isArray(d.security())).toEqual(true); + expect(d.security()).toHaveLength(0); + }); + }); + + describe('mixins', function() { + assertBindings(OperationTrait); + assertDescription(OperationTrait); + assertExtensions(OperationTrait); + assertExternalDocumentation(OperationTrait); + assertTags(OperationTrait); + }); +}); diff --git a/test/models/v3/operation.spec.ts b/test/models/v3/operation.spec.ts new file mode 100644 index 000000000..a5fee648d --- /dev/null +++ b/test/models/v3/operation.spec.ts @@ -0,0 +1,133 @@ +import { Channels } from '../../../src/models/channels'; +import { Channel } from '../../../src/models/v3/channel'; +import { Operation } from '../../../src/models/v3/operation'; +import { OperationTraits } from '../../../src/models/operation-traits'; +import { OperationTrait } from '../../../src/models/v3/operation-trait'; +import { Messages } from '../../../src/models/messages'; +import { Message } from '../../../src/models/v3/message'; +import { Servers } from '../../../src/models/servers'; +import { Server } from '../../../src/models/v3/server'; + +import { assertBindings, assertDescription, assertExtensions, assertExternalDocumentation, assertTags } from './utils'; + +describe('Operation model', function() { + describe('.action()', function() { + it('should return kind/action of operation', function() { + const d = new Operation({ action: 'send', channel: {} }, { asyncapi: {} as any, pointer: '', id: 'trait' }); + expect(d.action()).toEqual('send'); + }); + }); + + // describe('.servers()', function() { + // it('should return collection of servers - channel available on all servers', function() { + // const doc = {}; + // const d = new Operation({ action: 'send', channel: {} }, { asyncapi: { parsed: { servers: { production: {}, development: {} }, channels: { 'user/signup': { publish: doc } } } } as any, pointer: '', id: 'operation'}); + // expect(d.servers()).toBeInstanceOf(Servers); + // expect(d.servers().all()).toHaveLength(2); + // expect(d.servers().all()[0]).toBeInstanceOf(Server); + // expect(d.servers().all()[0].id()).toEqual('production'); + // expect(d.servers().all()[1]).toBeInstanceOf(Server); + // expect(d.servers().all()[1].id()).toEqual('development'); + // }); + + // it('should return collection of servers - channel available on selected servers', function() { + // const doc = {}; + // const d = new Operation({ action: 'send', channel: {} }, { asyncapi: { parsed: { servers: { production: {}, development: {} }, channels: { 'user/signup': { publish: doc, servers: ['production'] } } } } as any, pointer: '', id: 'operation'}); + // expect(d.servers()).toBeInstanceOf(Servers); + // expect(d.servers().all()).toHaveLength(1); + // expect(d.servers().all()[0]).toBeInstanceOf(Server); + // expect(d.servers().all()[0].id()).toEqual('production'); + // }); + + // it('should return collection of servers - do not duplicate servers', function() { + // const doc = {}; + // const d = new Operation({ action: 'send', channel: {} }, { asyncapi: { parsed: { servers: { production: {}, development: {} }, channels: { 'user/signup': { publish: doc, servers: ['production'] }, 'user/logout': { subscribe: doc } } } } as any, pointer: '', id: 'operation' }); + // expect(d.servers()).toBeInstanceOf(Servers); + // expect(d.servers().all()).toHaveLength(2); + // expect(d.servers().all()[0]).toBeInstanceOf(Server); + // expect(d.servers().all()[0].id()).toEqual('production'); + // expect(d.servers().all()[1]).toBeInstanceOf(Server); + // expect(d.servers().all()[1].id()).toEqual('development'); + // }); + // }); + + // describe('.channels()', function() { + // it('should return collection of channels - single channel', function() { + // const doc = {}; + // const d = new Operation(doc, { asyncapi: { parsed: { channels: { 'user/signup': { publish: doc } } } } as any, pointer: '', id: 'operation', action: 'publish' }); + // expect(d.channels()).toBeInstanceOf(Channels); + // expect(d.channels().all()).toHaveLength(1); + // expect(d.channels().all()[0]).toBeInstanceOf(Channel); + // expect(d.channels().all()[0].address()).toEqual('user/signup'); + // }); + + // it('should return collection of channels - multiple channels', function() { + // const doc = {}; + // const d = new Operation(doc, { asyncapi: { parsed: { channels: { 'user/signup': { publish: doc }, 'user/logout': { subscribe: doc } } } } as any, pointer: '', id: 'operation', action: 'publish' }); + // expect(d.channels()).toBeInstanceOf(Channels); + // expect(d.channels().all()).toHaveLength(2); + // expect(d.channels().all()[0]).toBeInstanceOf(Channel); + // expect(d.channels().all()[0].address()).toEqual('user/signup'); + // expect(d.channels().all()[1]).toBeInstanceOf(Channel); + // expect(d.channels().all()[1].address()).toEqual('user/logout'); + // }); + + // it('should return collection of channels - do not duplicate channels', function() { + // const doc = {}; + // const d = new Operation(doc, { asyncapi: { parsed: { channels: { 'user/signup': { publish: doc, subscribe: doc } } } } as any, pointer: '', id: 'operation', action: 'publish' }); + // expect(d.channels()).toBeInstanceOf(Channels); + // expect(d.channels().all()).toHaveLength(1); + // expect(d.channels().all()[0]).toBeInstanceOf(Channel); + // expect(d.channels().all()[0].address()).toEqual('user/signup'); + // }); + // }); + + // describe('.messages()', function() { + // it('should return collection of messages - single message', function() { + // const doc = { message: { messageId: '...' } }; + // const d = new Operation(doc); + // expect(d.messages()).toBeInstanceOf(Messages); + // expect(d.messages().all()).toHaveLength(1); + // expect(d.messages().all()[0]).toBeInstanceOf(Message); + // }); + + // it('should return collection of messages - oneOf messages', function() { + // const doc = { message: { oneOf: [{ messageId: '...' }, { messageId: '...' }] } }; + // const d = new Operation(doc); + // expect(d.messages()).toBeInstanceOf(Messages); + // expect(d.messages().all()).toHaveLength(2); + // expect(d.messages().all()[0]).toBeInstanceOf(Message); + // expect(d.messages().all()[1]).toBeInstanceOf(Message); + // }); + + // it('should return undefined when there is no value', function() { + // const doc = {}; + // const d = new Operation(doc); + // expect(d.messages()).toBeInstanceOf(Messages); + // expect(d.messages().all()).toHaveLength(0); + // }); + // }); + + describe('.traits()', function() { + it('should return collection of traits', function() { + const d = new Operation({ action: 'send', channel: {}, traits: [{ action: 'receive' }] }); + expect(d.traits()).toBeInstanceOf(OperationTraits); + expect(d.traits().all()).toHaveLength(1); + expect(d.traits().all()[0]).toBeInstanceOf(OperationTrait); + }); + + it('should return collection of traits when value is undefined', function() { + const d = new Operation({ action: 'send', channel: {}, traits: [] }); + expect(d.traits()).toBeInstanceOf(OperationTraits); + expect(d.traits().all()).toHaveLength(0); + }); + }); + + describe('mixins', function() { + assertBindings(Operation); + assertDescription(Operation); + assertExtensions(Operation); + assertExternalDocumentation(Operation); + assertTags(Operation); + }); +}); diff --git a/test/models/v3/server.spec.ts b/test/models/v3/server.spec.ts new file mode 100644 index 000000000..f32820016 --- /dev/null +++ b/test/models/v3/server.spec.ts @@ -0,0 +1,214 @@ +import { Channels } from '../../../src/models/channels'; +import { Channel } from '../../../src/models/v3/channel'; +import { Messages } from '../../../src/models/messages'; +import { Message } from '../../../src/models/v3/message'; +import { Operations } from '../../../src/models/operations'; +import { Operation } from '../../../src/models/v3/operation'; +import { Server } from '../../../src/models/v3/server'; +import { ServerVariables } from '../../../src/models/server-variables'; +import { SecurityScheme } from '../../../src/models/v3/security-scheme'; +import { SecurityRequirements } from '../../../src/models/security-requirements'; +import { SecurityRequirement } from '../../../src/models/v3/security-requirement'; + +import { serializeInput, assertBindings, assertDescription, assertExtensions } from './utils'; + +import type { v3 } from '../../../src/spec-types'; + +const doc = { + development: { + protocol: 'mqtt', + protocolVersion: '1.0.0', + url: 'development.gigantic-server.com', + variables: { + username: { + default: 'demo', + description: 'This value is assigned by the service provider, in this example `gigantic-server.com`' + } + } + } +}; +const docItem = new Server(doc.development, { asyncapi: {} as any, pointer: '', id: 'development' }); +const emptyItem = new Server(serializeInput({}), { asyncapi: {} as any, pointer: '', id: '' }); + +describe('Server Model', function () { + describe('.id()', function () { + it('should return name if present', function () { + expect(docItem.id()).toMatch('development'); + }); + }); + + describe('protocol()', function () { + it('should return protocol ', function () { + expect(docItem.protocol()).toMatch(doc.development.protocol); + }); + }); + + describe('.hasProtocolVersion()', function () { + it('should return true if protocolVersion is not missing', function () { + expect(docItem.hasProtocolVersion()).toBeTruthy(); + }); + + it('should be false when protocolVersion is missing', function () { + expect(emptyItem.hasProtocolVersion()).toBeFalsy(); + }); + }); + + describe('.protocolVersion()', function () { + it('should return value', function () { + expect(docItem.protocolVersion()).toMatch(doc.development.protocolVersion); + }); + + it('should return undefined when protocolVersion is missing', function () { + expect(emptyItem.protocolVersion()).toBeUndefined(); + }); + }); + + describe('.url()', function () { + it('should return value', function () { + expect(docItem.url()).toMatch(doc.development.url); + }); + }); + + // describe('.channels()', function() { + // it('should return collection of channels - single channel', function() { + // const doc = serializeInput({}); + // const d = new Server(doc, { asyncapi: { parsed: { channels: { 'user/signup': {} } } } as any, pointer: '', id: 'production' }); + // expect(d.channels()).toBeInstanceOf(Channels); + // expect(d.channels().all()).toHaveLength(1); + // expect(d.channels().all()[0]).toBeInstanceOf(Channel); + // expect(d.channels().all()[0].address()).toEqual('user/signup'); + // }); + + // it('should return collection of channels - multiple channels', function() { + // const doc = serializeInput({}); + // const d = new Server(doc, { asyncapi: { parsed: { channels: { 'user/signup': {}, 'user/logout': {} } } } as any, pointer: '', id: 'production' }); + // expect(d.channels()).toBeInstanceOf(Channels); + // expect(d.channels().all()).toHaveLength(2); + // expect(d.channels().all()[0]).toBeInstanceOf(Channel); + // expect(d.channels().all()[0].address()).toEqual('user/signup'); + // expect(d.channels().all()[1]).toBeInstanceOf(Channel); + // expect(d.channels().all()[1].address()).toEqual('user/logout'); + // }); + + // it('should return collection of channels - server available only in particular channel', function() { + // const doc = serializeInput({}); + // const d = new Server(doc, { asyncapi: { parsed: { channels: { 'user/signup': { servers: ['production'] }, 'user/logout': { servers: ['development'] }, 'user/create': {} } } } as any, pointer: '', id: 'production', }); + // expect(d.channels()).toBeInstanceOf(Channels); + // expect(d.channels().all()).toHaveLength(2); + // expect(d.channels().all()[0]).toBeInstanceOf(Channel); + // expect(d.channels().all()[0].address()).toEqual('user/signup'); + // expect(d.channels().all()[1]).toBeInstanceOf(Channel); + // expect(d.channels().all()[1].address()).toEqual('user/create'); + // }); + // }); + + // describe('.operations()', function() { + // it('should return collection of operations - single channel', function() { + // const doc = serializeInput({}); + // const d = new Server(doc, { asyncapi: { parsed: { channels: { 'user/signup': { publish: { operationId: '1' } } } } } as any, pointer: '', id: 'production' }); + // expect(d.operations()).toBeInstanceOf(Operations); + // expect(d.operations().all()).toHaveLength(1); + // expect(d.operations().all()[0]).toBeInstanceOf(Operation); + // expect(d.operations().all()[0].id()).toEqual('1'); + // }); + + // it('should return collection of channels - multiple channels', function() { + // const doc = serializeInput({}); + // const d = new Server(doc, { asyncapi: { parsed: { channels: { 'user/signup': { publish: { operationId: '1' } }, 'user/logout': { subscribe: { operationId: '2' } } } } } as any, pointer: '', id: 'production' }); + // expect(d.operations()).toBeInstanceOf(Operations); + // expect(d.operations().all()).toHaveLength(2); + // expect(d.operations().all()[0]).toBeInstanceOf(Operation); + // expect(d.operations().all()[0].id()).toEqual('1'); + // expect(d.operations().all()[1]).toBeInstanceOf(Operation); + // expect(d.operations().all()[1].id()).toEqual('2'); + // }); + + // it('should return collection of channels - server available only in particular channel', function() { + // const doc = serializeInput({}); + // const d = new Server(doc, { asyncapi: { parsed: { channels: { 'user/signup': { servers: ['production'], publish: { operationId: '1' } }, 'user/logout': { servers: ['development'] }, 'user/create': { subscribe: { operationId: '3' }, publish: { operationId: '2' } } } } } as any, pointer: '', id: 'production', }); + // expect(d.operations()).toBeInstanceOf(Operations); + // expect(d.operations().all()).toHaveLength(3); + // expect(d.operations().all()[0]).toBeInstanceOf(Operation); + // expect(d.operations().all()[0].id()).toEqual('1'); + // expect(d.operations().all()[1]).toBeInstanceOf(Operation); + // expect(d.operations().all()[1].id()).toEqual('2'); + // expect(d.operations().all()[2]).toBeInstanceOf(Operation); + // expect(d.operations().all()[2].id()).toEqual('3'); + // }); + // }); + + // describe('.messages()', function() { + // it('should return collection of messages - single channel', function() { + // const doc = serializeInput({}); + // const d = new Server(doc, { asyncapi: { parsed: { channels: { 'user/signup': { publish: { message: { messageId: '1' } } } } } } as any, pointer: '', id: 'production' }); + // expect(d.messages()).toBeInstanceOf(Messages); + // expect(d.messages().all()).toHaveLength(1); + // expect(d.messages().all()[0]).toBeInstanceOf(Message); + // expect(d.messages().all()[0].messageId()).toEqual('1'); + // }); + + // it('should return collection of messages - multiple channels', function() { + // const doc = serializeInput({}); + // const d = new Server(doc, { asyncapi: { parsed: { channels: { 'user/signup': { publish: { message: { messageId: '1' } } }, 'user/logout': { subscribe: { message: { oneOf: [{ messageId: '2' }, { messageId: '3' }] } } } } } } as any, pointer: '', id: 'production' }); + // expect(d.messages()).toBeInstanceOf(Messages); + // expect(d.messages().all()).toHaveLength(3); + // expect(d.messages().all()[0]).toBeInstanceOf(Message); + // expect(d.messages().all()[0].messageId()).toEqual('1'); + // expect(d.messages().all()[1]).toBeInstanceOf(Message); + // expect(d.messages().all()[1].messageId()).toEqual('2'); + // expect(d.messages().all()[2]).toBeInstanceOf(Message); + // expect(d.messages().all()[2].messageId()).toEqual('3'); + // }); + + // it('should return collection of messages - server available only in particular channel', function() { + // const doc = serializeInput({}); + // const d = new Server(doc, { asyncapi: { parsed: { channels: { 'user/signup': { servers: ['production'], publish: { message: { messageId: '1' } } }, 'user/logout': { servers: ['development'] }, 'user/create': { subscribe: { message: { messageId: '3' } }, publish: { message: { messageId: '2' } } } } } } as any, pointer: '', id: 'production', }); + // expect(d.messages()).toBeInstanceOf(Messages); + // expect(d.messages().all()).toHaveLength(3); + // expect(d.messages().all()[0]).toBeInstanceOf(Message); + // expect(d.messages().all()[0].messageId()).toEqual('1'); + // expect(d.messages().all()[1]).toBeInstanceOf(Message); + // expect(d.messages().all()[1].messageId()).toEqual('2'); + // expect(d.messages().all()[2]).toBeInstanceOf(Message); + // expect(d.messages().all()[2].messageId()).toEqual('3'); + // }); + // }); + + describe('.variables()', function () { + it('should return ServerVariables object', function () { + expect(docItem.variables()).toBeInstanceOf(ServerVariables); + }); + }); + + describe('.security()', function() { + it('should return SecurityRequirements', function() { + const doc = serializeInput({ security: [{ requirement: [] }] }); + const d = new Server(doc, {pointer: '/servers/test'} as any); + + const security = d.security(); + expect(Array.isArray(security)).toEqual(true); + expect(security).toHaveLength(1); + expect(security[0]).toBeInstanceOf(SecurityRequirements); + + const requirement = security[0].get('requirement') as SecurityRequirement; + expect(requirement).toBeInstanceOf(SecurityRequirement); + expect(requirement.scheme()).toBeInstanceOf(SecurityScheme); + expect(requirement.scopes()).toEqual([]); + expect(requirement.meta().id).toEqual('requirement'); + expect(requirement.meta().pointer).toEqual('/servers/test/security/0/requirement'); + }); + + it('should return SecurityRequirements when value is undefined', function() { + const doc = serializeInput({}); + const d = new Server(doc); + expect(Array.isArray(d.security())).toEqual(true); + expect(d.security()).toHaveLength(0); + }); + }); + + describe('mixins inheritance', function () { + assertBindings(Server); + assertDescription(Server); + assertExtensions(Server); + }); +}); From 9d9c514bdbde67e961b818baa2983e9a330db6ab Mon Sep 17 00:00:00 2001 From: Matatjahu Date: Fri, 21 Oct 2022 12:16:28 +0200 Subject: [PATCH 14/17] add more tests --- src/models/v3/server.ts | 37 ++++-- test/models/v3/channel.spec.ts | 167 +++++++++++++------------- test/models/v3/operation.spec.ts | 134 ++++++++------------- test/models/v3/server.spec.ts | 200 +++++++++++++++---------------- 4 files changed, 254 insertions(+), 284 deletions(-) diff --git a/src/models/v3/server.ts b/src/models/v3/server.ts index 75620e2d2..46b42b1ac 100644 --- a/src/models/v3/server.ts +++ b/src/models/v3/server.ts @@ -57,10 +57,19 @@ export class Server extends BaseModel implement channels(): ChannelsInterface { const channels: ChannelInterface[] = []; - Object.entries(this._meta.asyncapi?.parsed?.channels || {}).forEach(([channelAddress, channel]: [string, any]) => { - const allowedServers: string[] = channel.servers || []; - if (allowedServers.length === 0 || allowedServers.includes(this._meta.id)) { - channels.push(this.createModel(Channel, channel, { id: channelAddress, pointer: `/channels/${tilde(channelAddress)}` })); + Object.entries((this._meta.asyncapi?.parsed as v3.AsyncAPIObject)?.channels || {}).forEach(([channelName, channel]) => { + const allowedServers: v3.ServerObject[] = channel.servers || []; + if (allowedServers.length === 0 || allowedServers.includes(this._json)) { + channels.push(this.createModel(Channel, channel, { id: channelName, pointer: `/channels/${tilde(channelName)}` })); + } + }); + Object.entries((this._meta.asyncapi?.parsed as v3.AsyncAPIObject)?.operations || {}).forEach(([operationId, operation]) => { + const operationChannel = operation.channel as v3.ChannelObject | undefined; + if (!channels.some(channel => channel.json() === operationChannel)) { + const allowedServers: v3.ServerObject[] = operationChannel!.servers || []; + if (allowedServers.length === 0 || allowedServers.includes(this._json)) { + channels.push(this.createModel(Channel, operationChannel!, { id: '', pointer: `/operations/${tilde(operationId)}/channel` })); + } } }); return new Channels(channels); @@ -69,11 +78,11 @@ export class Server extends BaseModel implement operations(): OperationsInterface { const operations: OperationInterface[] = []; const operationsData: v3.OperationObject[] = []; - this.channels().forEach((channel: ChannelInterface) => { - channel.operations().forEach((op: OperationInterface) => { - if (!operationsData.includes(op.json())) { - operations.push(op); - operationsData.push(op.json()); + this.channels().forEach(channel => { + channel.operations().forEach(operation => { + if (!operationsData.includes(operation.json())) { + operations.push(operation); + operationsData.push(operation.json()); } }); }); @@ -82,7 +91,15 @@ export class Server extends BaseModel implement messages(): MessagesInterface { const messages: MessageInterface[] = []; - this.operations().forEach(operation => messages.push(...operation.messages().all())); + const messagedData: v3.MessageObject[] = []; + this.channels().forEach(channel => { + channel.messages().forEach(message => { + if (!messagedData.includes(message.json())) { + messages.push(message); + messagedData.push(message.json()); + } + }); + }); return new Messages(messages); } diff --git a/test/models/v3/channel.spec.ts b/test/models/v3/channel.spec.ts index 75b2cc6be..f75fb9844 100644 --- a/test/models/v3/channel.spec.ts +++ b/test/models/v3/channel.spec.ts @@ -29,99 +29,94 @@ describe('Channel model', function() { }); }); - // describe('.servers()', function() { - // it('should return collection of servers - available on all servers', function() { - // const doc = serializeInput({}); - // const d = new Channel(doc, { asyncapi: { parsed: { servers: { someServer1: {}, someServer2: {}, } } } as any, pointer: '', id: 'channel' }); - // expect(d.servers()).toBeInstanceOf(Servers); - // expect(d.servers().all()).toHaveLength(2); - // expect(d.servers().all()[0]).toBeInstanceOf(Server); - // expect(d.servers().all()[0].id()).toEqual('someServer1'); - // expect(d.servers().all()[1]).toBeInstanceOf(Server); - // expect(d.servers().all()[1].id()).toEqual('someServer2'); - // }); + describe('.servers()', function() { + it('should return collection of servers - available on all servers', function() { + const doc = serializeInput({}); + const d = new Channel(doc, { asyncapi: { parsed: { servers: { someServer1: {}, someServer2: {}, } } } as any, pointer: '', id: 'channel' }); + expect(d.servers()).toBeInstanceOf(Servers); + expect(d.servers().all()).toHaveLength(2); + expect(d.servers().all()[0]).toBeInstanceOf(Server); + expect(d.servers().all()[0].id()).toEqual('someServer1'); + expect(d.servers().all()[1]).toBeInstanceOf(Server); + expect(d.servers().all()[1].id()).toEqual('someServer2'); + }); - // it('should return collection of servers - available on all servers (empty servers array)', function() { - // const doc = serializeInput({ servers: [] }); - // const d = new Channel(doc, { asyncapi: { parsed: { servers: { someServer1: {}, someServer2: {}, } } } as any, pointer: '', id: 'channel' }); - // expect(d.servers()).toBeInstanceOf(Servers); - // expect(d.servers().all()).toHaveLength(2); - // expect(d.servers().all()[0]).toBeInstanceOf(Server); - // expect(d.servers().all()[0].id()).toEqual('someServer1'); - // expect(d.servers().all()[1]).toBeInstanceOf(Server); - // expect(d.servers().all()[1].id()).toEqual('someServer2'); - // }); + it('should return collection of servers - available on all servers (empty servers array)', function() { + const doc = serializeInput({ servers: [] }); + const d = new Channel(doc, { asyncapi: { parsed: { servers: { someServer1: {}, someServer2: {}, } } } as any, pointer: '', id: 'channel' }); + expect(d.servers()).toBeInstanceOf(Servers); + expect(d.servers().all()).toHaveLength(2); + expect(d.servers().all()[0]).toBeInstanceOf(Server); + expect(d.servers().all()[0].id()).toEqual('someServer1'); + expect(d.servers().all()[1]).toBeInstanceOf(Server); + expect(d.servers().all()[1].id()).toEqual('someServer2'); + }); - // it('should return collection of servers - available only on particular ones', function() { - // const someServer2 = {} - // const doc = serializeInput({ servers: [someServer2] }); - // const d = new Channel(doc, { asyncapi: { parsed: { servers: { someServer1: {}, someServer2, } } } as any, pointer: '', id: 'channel' }); - // expect(d.servers()).toBeInstanceOf(Servers); - // expect(d.servers().all()).toHaveLength(1); - // expect(d.servers().all()[0]).toBeInstanceOf(Server); - // expect(d.servers().all()[0].id()).toEqual('someServer2'); - // }); - // }); + it('should return collection of servers - available only on particular ones', function() { + const someServer2 = {} + const doc = serializeInput({ servers: [someServer2] }); + const d = new Channel(doc, { asyncapi: { parsed: { servers: { someServer1: {}, someServer2, } } } as any, pointer: '', id: 'channel' }); + expect(d.servers()).toBeInstanceOf(Servers); + expect(d.servers().all()).toHaveLength(1); + expect(d.servers().all()[0]).toBeInstanceOf(Server); + expect(d.servers().all()[0].id()).toEqual('someServer2'); + }); + }); - // describe('.operations()', function() { - // it('should return collection of operations - publish operation', function() { - // const doc = serializeInput({ publish: {} }); - // const d = new Channel(doc); - // expect(d.operations()).toBeInstanceOf(Operations); - // expect(d.operations().all()).toHaveLength(1); - // expect(d.operations().all()[0]).toBeInstanceOf(Operation); - // expect(d.operations().all()[0].action()).toEqual('publish'); - // }); + describe('.operations()', function() { + it('should return collection of operations - send operation', function() { + const channel = {}; + const d = new Channel(channel, { asyncapi: { parsed: { operations: { someOperation: { action: 'send', channel } } } } } as any); + expect(d.operations()).toBeInstanceOf(Operations); + expect(d.operations().all()).toHaveLength(1); + expect(d.operations().all()[0]).toBeInstanceOf(Operation); + expect(d.operations().all()[0].action()).toEqual('send'); + }); - // it('should return collection of operations - subscribe operation', function() { - // const doc = serializeInput({ subscribe: {} }); - // const d = new Channel(doc); - // expect(d.operations()).toBeInstanceOf(Operations); - // expect(d.operations().all()).toHaveLength(1); - // expect(d.operations().all()[0]).toBeInstanceOf(Operation); - // expect(d.operations().all()[0].action()).toEqual('subscribe'); - // }); + it('should return collection of operations - receive operation', function() { + const channel = {}; + const d = new Channel(channel, { asyncapi: { parsed: { operations: { someOperation: { action: 'receive', channel } } } } } as any); + expect(d.operations()).toBeInstanceOf(Operations); + expect(d.operations().all()).toHaveLength(1); + expect(d.operations().all()[0]).toBeInstanceOf(Operation); + expect(d.operations().all()[0].action()).toEqual('receive'); + }); - // it('should return collection of operations - both operations', function() { - // const doc = serializeInput({ publish: {}, subscribe: {} }); - // const d = new Channel(doc); - // expect(d.operations()).toBeInstanceOf(Operations); - // expect(d.operations().all()).toHaveLength(2); - // expect(d.operations().all()[0]).toBeInstanceOf(Operation); - // expect(d.operations().all()[0].action()).toEqual('publish'); - // expect(d.operations().all()[1]).toBeInstanceOf(Operation); - // expect(d.operations().all()[1].action()).toEqual('subscribe'); - // }); - // }); + it('should return collection of operations - more than one operations', function() { + const channel = {}; + const d = new Channel(channel, { asyncapi: { parsed: { operations: { someOperation1: { action: 'send', channel }, someOperation2: { action: 'receive', channel }, someOperation3: { action: 'send', channel } } } } } as any); + expect(d.operations()).toBeInstanceOf(Operations); + expect(d.operations().all()).toHaveLength(3); + expect(d.operations().all()[0]).toBeInstanceOf(Operation); + expect(d.operations().all()[0].action()).toEqual('send'); + expect(d.operations().all()[1]).toBeInstanceOf(Operation); + expect(d.operations().all()[1].action()).toEqual('receive'); + expect(d.operations().all()[2]).toBeInstanceOf(Operation); + expect(d.operations().all()[2].action()).toEqual('send'); + }); + }); - // describe('.messages()', function() { - // it('should return collection of messages - single message', function() { - // const doc = serializeInput({ publish: { message: { messageId: '...' } } }); - // const d = new Channel(doc); - // expect(d.messages()).toBeInstanceOf(Messages); - // expect(d.messages().all()).toHaveLength(1); - // expect(d.messages().all()[0]).toBeInstanceOf(Message); - // expect(d.messages().all()[0].messageId()).toEqual((doc as any).publish.message.messageId); - // }); + describe('.messages()', function() { + it('should return collection of messages - single message', function() { + const doc = serializeInput({ messages: { someMessage: { messageId: 'messageId' } } }); + const d = new Channel(doc); + expect(d.messages()).toBeInstanceOf(Messages); + expect(d.messages().all()).toHaveLength(1); + expect(d.messages().all()[0]).toBeInstanceOf(Message); + expect(d.messages().all()[0].messageId()).toEqual(doc.messages?.['someMessage']?.messageId); + }); - // it('should return collection of messages - oneOf message', function() { - // const doc = serializeInput({ subscribe: { message: { oneOf: [{ messageId: '1' }, { messageId: '2' }] } } }); - // const d = new Channel(doc); - // expect(d.messages()).toBeInstanceOf(Messages); - // expect(d.messages().all()).toHaveLength(2); - // expect(d.messages().all()[0]).toBeInstanceOf(Message); - // expect(d.messages().all()[0].messageId()).toEqual((doc as any).subscribe.message.oneOf[0].messageId); - // expect(d.messages().all()[1]).toBeInstanceOf(Message); - // expect(d.messages().all()[1].messageId()).toEqual((doc as any).subscribe.message.oneOf[1].messageId); - // }); - - // it('should return collection of messages - single message and oneOf', function() { - // const doc = serializeInput({ publish: { message: {} }, subscribe: { message: { oneOf: [{ messageId: '1' }, { messageId: '2' }] } } }); - // const d = new Channel(doc); - // expect(d.messages()).toBeInstanceOf(Messages); - // expect(d.messages().all()).toHaveLength(3); - // }); - // }); + it('should return collection of messages - more than one messages', function() { + const doc = serializeInput({ messages: { someMessage1: { messageId: 'messageId1' }, someMessage2: { messageId: 'messageId2' } } }); + const d = new Channel(doc); + expect(d.messages()).toBeInstanceOf(Messages); + expect(d.messages().all()).toHaveLength(2); + expect(d.messages().all()[0]).toBeInstanceOf(Message); + expect(d.messages().all()[0].messageId()).toEqual(doc.messages?.['someMessage1']?.messageId); + expect(d.messages().all()[1]).toBeInstanceOf(Message); + expect(d.messages().all()[1].messageId()).toEqual(doc.messages?.['someMessage2']?.messageId); + }); + }); describe('.parameters()', function() { it('should return collection of channel parameters', function() { diff --git a/test/models/v3/operation.spec.ts b/test/models/v3/operation.spec.ts index a5fee648d..887ada4a7 100644 --- a/test/models/v3/operation.spec.ts +++ b/test/models/v3/operation.spec.ts @@ -18,95 +18,61 @@ describe('Operation model', function() { }); }); - // describe('.servers()', function() { - // it('should return collection of servers - channel available on all servers', function() { - // const doc = {}; - // const d = new Operation({ action: 'send', channel: {} }, { asyncapi: { parsed: { servers: { production: {}, development: {} }, channels: { 'user/signup': { publish: doc } } } } as any, pointer: '', id: 'operation'}); - // expect(d.servers()).toBeInstanceOf(Servers); - // expect(d.servers().all()).toHaveLength(2); - // expect(d.servers().all()[0]).toBeInstanceOf(Server); - // expect(d.servers().all()[0].id()).toEqual('production'); - // expect(d.servers().all()[1]).toBeInstanceOf(Server); - // expect(d.servers().all()[1].id()).toEqual('development'); - // }); - - // it('should return collection of servers - channel available on selected servers', function() { - // const doc = {}; - // const d = new Operation({ action: 'send', channel: {} }, { asyncapi: { parsed: { servers: { production: {}, development: {} }, channels: { 'user/signup': { publish: doc, servers: ['production'] } } } } as any, pointer: '', id: 'operation'}); - // expect(d.servers()).toBeInstanceOf(Servers); - // expect(d.servers().all()).toHaveLength(1); - // expect(d.servers().all()[0]).toBeInstanceOf(Server); - // expect(d.servers().all()[0].id()).toEqual('production'); - // }); - - // it('should return collection of servers - do not duplicate servers', function() { - // const doc = {}; - // const d = new Operation({ action: 'send', channel: {} }, { asyncapi: { parsed: { servers: { production: {}, development: {} }, channels: { 'user/signup': { publish: doc, servers: ['production'] }, 'user/logout': { subscribe: doc } } } } as any, pointer: '', id: 'operation' }); - // expect(d.servers()).toBeInstanceOf(Servers); - // expect(d.servers().all()).toHaveLength(2); - // expect(d.servers().all()[0]).toBeInstanceOf(Server); - // expect(d.servers().all()[0].id()).toEqual('production'); - // expect(d.servers().all()[1]).toBeInstanceOf(Server); - // expect(d.servers().all()[1].id()).toEqual('development'); - // }); - // }); - - // describe('.channels()', function() { - // it('should return collection of channels - single channel', function() { - // const doc = {}; - // const d = new Operation(doc, { asyncapi: { parsed: { channels: { 'user/signup': { publish: doc } } } } as any, pointer: '', id: 'operation', action: 'publish' }); - // expect(d.channels()).toBeInstanceOf(Channels); - // expect(d.channels().all()).toHaveLength(1); - // expect(d.channels().all()[0]).toBeInstanceOf(Channel); - // expect(d.channels().all()[0].address()).toEqual('user/signup'); - // }); + describe('.servers()', function() { + it('should return collection of servers - channel available on all servers', function() { + const d = new Operation({ action: 'send', channel: {} }, { asyncapi: { parsed: { servers: { production: {}, development: {}, } } } as any, pointer: '', id: 'operation' }); + expect(d.servers()).toBeInstanceOf(Servers); + expect(d.servers().all()).toHaveLength(2); + expect(d.servers().all()[0]).toBeInstanceOf(Server); + expect(d.servers().all()[0].id()).toEqual('production'); + expect(d.servers().all()[1]).toBeInstanceOf(Server); + expect(d.servers().all()[1].id()).toEqual('development'); + }); - // it('should return collection of channels - multiple channels', function() { - // const doc = {}; - // const d = new Operation(doc, { asyncapi: { parsed: { channels: { 'user/signup': { publish: doc }, 'user/logout': { subscribe: doc } } } } as any, pointer: '', id: 'operation', action: 'publish' }); - // expect(d.channels()).toBeInstanceOf(Channels); - // expect(d.channels().all()).toHaveLength(2); - // expect(d.channels().all()[0]).toBeInstanceOf(Channel); - // expect(d.channels().all()[0].address()).toEqual('user/signup'); - // expect(d.channels().all()[1]).toBeInstanceOf(Channel); - // expect(d.channels().all()[1].address()).toEqual('user/logout'); - // }); + it('should return collection of servers - channel available on selected servers', function() { + const production = {}; + const d = new Operation({ action: 'send', channel: { servers: [production as any] } }, { asyncapi: { parsed: { servers: { production, development: {}, } } } as any, pointer: '', id: 'operation' }); + expect(d.servers()).toBeInstanceOf(Servers); + expect(d.servers().all()).toHaveLength(1); + expect(d.servers().all()[0]).toBeInstanceOf(Server); + expect(d.servers().all()[0].id()).toEqual('production'); + }); + }); - // it('should return collection of channels - do not duplicate channels', function() { - // const doc = {}; - // const d = new Operation(doc, { asyncapi: { parsed: { channels: { 'user/signup': { publish: doc, subscribe: doc } } } } as any, pointer: '', id: 'operation', action: 'publish' }); - // expect(d.channels()).toBeInstanceOf(Channels); - // expect(d.channels().all()).toHaveLength(1); - // expect(d.channels().all()[0]).toBeInstanceOf(Channel); - // expect(d.channels().all()[0].address()).toEqual('user/signup'); - // }); - // }); + describe('.channels()', function() { + it('should return collection of channels - single channel', function() { + const d = new Operation({ action: 'send', channel: { address: 'user/signup' } }); + expect(d.channels()).toBeInstanceOf(Channels); + expect(d.channels().all()).toHaveLength(1); + expect(d.channels().all()[0]).toBeInstanceOf(Channel); + expect(d.channels().all()[0].address()).toEqual('user/signup'); + }); + }); - // describe('.messages()', function() { - // it('should return collection of messages - single message', function() { - // const doc = { message: { messageId: '...' } }; - // const d = new Operation(doc); - // expect(d.messages()).toBeInstanceOf(Messages); - // expect(d.messages().all()).toHaveLength(1); - // expect(d.messages().all()[0]).toBeInstanceOf(Message); - // }); + describe('.messages()', function() { + it('should return collection of messages - single message', function() { + const d = new Operation({ action: 'send', channel: { messages: { someMessage: { messageId: 'messageId' } } } }); + expect(d.messages()).toBeInstanceOf(Messages); + expect(d.messages().all()).toHaveLength(1); + expect(d.messages().all()[0]).toBeInstanceOf(Message); + }); - // it('should return collection of messages - oneOf messages', function() { - // const doc = { message: { oneOf: [{ messageId: '...' }, { messageId: '...' }] } }; - // const d = new Operation(doc); - // expect(d.messages()).toBeInstanceOf(Messages); - // expect(d.messages().all()).toHaveLength(2); - // expect(d.messages().all()[0]).toBeInstanceOf(Message); - // expect(d.messages().all()[1]).toBeInstanceOf(Message); - // }); + it('should return collection of messages - more than one messages', function() { + const d = new Operation({ action: 'send', channel: { messages: { someMessage1: { messageId: 'messageId1' }, someMessage2: { messageId: 'messageId2' } } } }); + expect(d.messages()).toBeInstanceOf(Messages); + expect(d.messages().all()).toHaveLength(2); + expect(d.messages().all()[0]).toBeInstanceOf(Message); + expect(d.messages().all()[0].messageId()).toEqual('messageId1'); + expect(d.messages().all()[1]).toBeInstanceOf(Message); + expect(d.messages().all()[1].messageId()).toEqual('messageId2'); + }); - // it('should return undefined when there is no value', function() { - // const doc = {}; - // const d = new Operation(doc); - // expect(d.messages()).toBeInstanceOf(Messages); - // expect(d.messages().all()).toHaveLength(0); - // }); - // }); + it('should return undefined when there is no value', function() { + const d = new Operation({ action: 'send', channel: {} }); + expect(d.messages()).toBeInstanceOf(Messages); + expect(d.messages().all()).toHaveLength(0); + }); + }); describe('.traits()', function() { it('should return collection of traits', function() { diff --git a/test/models/v3/server.spec.ts b/test/models/v3/server.spec.ts index f32820016..354d60409 100644 --- a/test/models/v3/server.spec.ts +++ b/test/models/v3/server.spec.ts @@ -69,110 +69,102 @@ describe('Server Model', function () { }); }); - // describe('.channels()', function() { - // it('should return collection of channels - single channel', function() { - // const doc = serializeInput({}); - // const d = new Server(doc, { asyncapi: { parsed: { channels: { 'user/signup': {} } } } as any, pointer: '', id: 'production' }); - // expect(d.channels()).toBeInstanceOf(Channels); - // expect(d.channels().all()).toHaveLength(1); - // expect(d.channels().all()[0]).toBeInstanceOf(Channel); - // expect(d.channels().all()[0].address()).toEqual('user/signup'); - // }); - - // it('should return collection of channels - multiple channels', function() { - // const doc = serializeInput({}); - // const d = new Server(doc, { asyncapi: { parsed: { channels: { 'user/signup': {}, 'user/logout': {} } } } as any, pointer: '', id: 'production' }); - // expect(d.channels()).toBeInstanceOf(Channels); - // expect(d.channels().all()).toHaveLength(2); - // expect(d.channels().all()[0]).toBeInstanceOf(Channel); - // expect(d.channels().all()[0].address()).toEqual('user/signup'); - // expect(d.channels().all()[1]).toBeInstanceOf(Channel); - // expect(d.channels().all()[1].address()).toEqual('user/logout'); - // }); - - // it('should return collection of channels - server available only in particular channel', function() { - // const doc = serializeInput({}); - // const d = new Server(doc, { asyncapi: { parsed: { channels: { 'user/signup': { servers: ['production'] }, 'user/logout': { servers: ['development'] }, 'user/create': {} } } } as any, pointer: '', id: 'production', }); - // expect(d.channels()).toBeInstanceOf(Channels); - // expect(d.channels().all()).toHaveLength(2); - // expect(d.channels().all()[0]).toBeInstanceOf(Channel); - // expect(d.channels().all()[0].address()).toEqual('user/signup'); - // expect(d.channels().all()[1]).toBeInstanceOf(Channel); - // expect(d.channels().all()[1].address()).toEqual('user/create'); - // }); - // }); - - // describe('.operations()', function() { - // it('should return collection of operations - single channel', function() { - // const doc = serializeInput({}); - // const d = new Server(doc, { asyncapi: { parsed: { channels: { 'user/signup': { publish: { operationId: '1' } } } } } as any, pointer: '', id: 'production' }); - // expect(d.operations()).toBeInstanceOf(Operations); - // expect(d.operations().all()).toHaveLength(1); - // expect(d.operations().all()[0]).toBeInstanceOf(Operation); - // expect(d.operations().all()[0].id()).toEqual('1'); - // }); - - // it('should return collection of channels - multiple channels', function() { - // const doc = serializeInput({}); - // const d = new Server(doc, { asyncapi: { parsed: { channels: { 'user/signup': { publish: { operationId: '1' } }, 'user/logout': { subscribe: { operationId: '2' } } } } } as any, pointer: '', id: 'production' }); - // expect(d.operations()).toBeInstanceOf(Operations); - // expect(d.operations().all()).toHaveLength(2); - // expect(d.operations().all()[0]).toBeInstanceOf(Operation); - // expect(d.operations().all()[0].id()).toEqual('1'); - // expect(d.operations().all()[1]).toBeInstanceOf(Operation); - // expect(d.operations().all()[1].id()).toEqual('2'); - // }); - - // it('should return collection of channels - server available only in particular channel', function() { - // const doc = serializeInput({}); - // const d = new Server(doc, { asyncapi: { parsed: { channels: { 'user/signup': { servers: ['production'], publish: { operationId: '1' } }, 'user/logout': { servers: ['development'] }, 'user/create': { subscribe: { operationId: '3' }, publish: { operationId: '2' } } } } } as any, pointer: '', id: 'production', }); - // expect(d.operations()).toBeInstanceOf(Operations); - // expect(d.operations().all()).toHaveLength(3); - // expect(d.operations().all()[0]).toBeInstanceOf(Operation); - // expect(d.operations().all()[0].id()).toEqual('1'); - // expect(d.operations().all()[1]).toBeInstanceOf(Operation); - // expect(d.operations().all()[1].id()).toEqual('2'); - // expect(d.operations().all()[2]).toBeInstanceOf(Operation); - // expect(d.operations().all()[2].id()).toEqual('3'); - // }); - // }); - - // describe('.messages()', function() { - // it('should return collection of messages - single channel', function() { - // const doc = serializeInput({}); - // const d = new Server(doc, { asyncapi: { parsed: { channels: { 'user/signup': { publish: { message: { messageId: '1' } } } } } } as any, pointer: '', id: 'production' }); - // expect(d.messages()).toBeInstanceOf(Messages); - // expect(d.messages().all()).toHaveLength(1); - // expect(d.messages().all()[0]).toBeInstanceOf(Message); - // expect(d.messages().all()[0].messageId()).toEqual('1'); - // }); - - // it('should return collection of messages - multiple channels', function() { - // const doc = serializeInput({}); - // const d = new Server(doc, { asyncapi: { parsed: { channels: { 'user/signup': { publish: { message: { messageId: '1' } } }, 'user/logout': { subscribe: { message: { oneOf: [{ messageId: '2' }, { messageId: '3' }] } } } } } } as any, pointer: '', id: 'production' }); - // expect(d.messages()).toBeInstanceOf(Messages); - // expect(d.messages().all()).toHaveLength(3); - // expect(d.messages().all()[0]).toBeInstanceOf(Message); - // expect(d.messages().all()[0].messageId()).toEqual('1'); - // expect(d.messages().all()[1]).toBeInstanceOf(Message); - // expect(d.messages().all()[1].messageId()).toEqual('2'); - // expect(d.messages().all()[2]).toBeInstanceOf(Message); - // expect(d.messages().all()[2].messageId()).toEqual('3'); - // }); - - // it('should return collection of messages - server available only in particular channel', function() { - // const doc = serializeInput({}); - // const d = new Server(doc, { asyncapi: { parsed: { channels: { 'user/signup': { servers: ['production'], publish: { message: { messageId: '1' } } }, 'user/logout': { servers: ['development'] }, 'user/create': { subscribe: { message: { messageId: '3' } }, publish: { message: { messageId: '2' } } } } } } as any, pointer: '', id: 'production', }); - // expect(d.messages()).toBeInstanceOf(Messages); - // expect(d.messages().all()).toHaveLength(3); - // expect(d.messages().all()[0]).toBeInstanceOf(Message); - // expect(d.messages().all()[0].messageId()).toEqual('1'); - // expect(d.messages().all()[1]).toBeInstanceOf(Message); - // expect(d.messages().all()[1].messageId()).toEqual('2'); - // expect(d.messages().all()[2]).toBeInstanceOf(Message); - // expect(d.messages().all()[2].messageId()).toEqual('3'); - // }); - // }); + describe('.channels()', function() { + it('should return collection of channels - single channel', function() { + const doc = serializeInput({}); + const d = new Server(doc, { asyncapi: { parsed: { channels: { userSignup: { address: 'user/signup' } } } } as any, pointer: '', id: 'production' }); + expect(d.channels()).toBeInstanceOf(Channels); + expect(d.channels().all()).toHaveLength(1); + expect(d.channels().all()[0]).toBeInstanceOf(Channel); + expect(d.channels().all()[0].address()).toEqual('user/signup'); + }); + + it('should return collection of channels - multiple channels', function() { + const doc = serializeInput({}); + const d = new Server(doc, { asyncapi: { parsed: { channels: { userSignup: { address: 'user/signup' }, userLogout: { address: 'user/logout' } } } } as any, pointer: '', id: 'production' }); + expect(d.channels()).toBeInstanceOf(Channels); + expect(d.channels().all()).toHaveLength(2); + expect(d.channels().all()[0]).toBeInstanceOf(Channel); + expect(d.channels().all()[0].address()).toEqual('user/signup'); + expect(d.channels().all()[1]).toBeInstanceOf(Channel); + expect(d.channels().all()[1].address()).toEqual('user/logout'); + }); + + it('should return collection of channels - server available only in particular channel', function() { + const doc = serializeInput({}); + const d = new Server(doc, { asyncapi: { parsed: { channels: { userSignup: { address: 'user/signup', servers: [doc] }, userLogout: { address: 'user/logout', servers: [{}] }, userCreate: { address: 'user/create' } } } } as any, pointer: '', id: 'production', }); + expect(d.channels()).toBeInstanceOf(Channels); + expect(d.channels().all()).toHaveLength(2); + expect(d.channels().all()[0]).toBeInstanceOf(Channel); + expect(d.channels().all()[0].address()).toEqual('user/signup'); + expect(d.channels().all()[1]).toBeInstanceOf(Channel); + expect(d.channels().all()[1].address()).toEqual('user/create'); + }); + }); + + describe('.operations()', function() { + it('should return collection of operations - one operation', function() { + const doc = serializeInput({}); + const d = new Server(doc, { asyncapi: { parsed: { operations: { someOperation: { channel: {} } } } } as any, pointer: '', id: 'production' }); + expect(d.operations()).toBeInstanceOf(Operations); + expect(d.operations().all()).toHaveLength(1); + expect(d.operations().all()[0]).toBeInstanceOf(Operation); + expect(d.operations().all()[0].id()).toEqual('someOperation'); + }); + + it('should return collection of channels - multiple operations', function() { + const doc = serializeInput({}); + const d = new Server(doc, { asyncapi: { parsed: { operations: { someOperation1: { channel: {} }, someOperation2: { channel: {} } } } } as any, pointer: '', id: 'production' }); + expect(d.operations()).toBeInstanceOf(Operations); + expect(d.operations().all()).toHaveLength(2); + expect(d.operations().all()[0]).toBeInstanceOf(Operation); + expect(d.operations().all()[0].id()).toEqual('someOperation1'); + expect(d.operations().all()[1]).toBeInstanceOf(Operation); + expect(d.operations().all()[1].id()).toEqual('someOperation2'); + }); + + it('should return collection of operations - server available only in particular channel', function() { + const doc = serializeInput({}); + const d = new Server(doc, { asyncapi: { parsed: { operations: { someOperation1: { channel: { servers: [doc] } }, someOperation2: { channel: { servers: [{}] } } } } } as any, pointer: '', id: 'production' }); + expect(d.operations()).toBeInstanceOf(Operations); + expect(d.operations().all()).toHaveLength(1); + expect(d.operations().all()[0]).toBeInstanceOf(Operation); + expect(d.operations().all()[0].id()).toEqual('someOperation1'); + }); + }); + + describe('.messages()', function() { + it('should return collection of messages - one message', function() { + const doc = serializeInput({}); + const d = new Server(doc, { asyncapi: { parsed: { channels: { someChannel: { messages: { someMessage: { messageId: 'messageId' } } } } } } as any, pointer: '', id: 'production' }); + expect(d.messages()).toBeInstanceOf(Messages); + expect(d.messages().all()).toHaveLength(1); + expect(d.messages().all()[0]).toBeInstanceOf(Message); + expect(d.messages().all()[0].messageId()).toEqual('messageId'); + }); + + it('should return collection of messages - more than one messages', function() { + const doc = serializeInput({}); + const d = new Server(doc, { asyncapi: { parsed: { channels: { someChannel1: { messages: { someMessage1: { messageId: 'messageId1' } } }, someChannel2: { messages: { someMessage2: { messageId: 'messageId2' }, someMessage3: { messageId: 'messageId3' } } } } } } as any, pointer: '', id: 'production' }); + expect(d.messages()).toBeInstanceOf(Messages); + expect(d.messages().all()).toHaveLength(3); + expect(d.messages().all()[0]).toBeInstanceOf(Message); + expect(d.messages().all()[0].messageId()).toEqual('messageId1'); + expect(d.messages().all()[1]).toBeInstanceOf(Message); + expect(d.messages().all()[1].messageId()).toEqual('messageId2'); + expect(d.messages().all()[2]).toBeInstanceOf(Message); + expect(d.messages().all()[2].messageId()).toEqual('messageId3'); + }); + + it('should return collection of messages - server available only in particular channel', function() { + const doc = serializeInput({}); + const d = new Server(doc, { asyncapi: { parsed: { channels: { someChannel1: { servers: [doc], messages: { someMessage1: { messageId: 'messageId1' } } }, someChannel2: { servers: [{}], messages: { someMessage2: { messageId: 'messageId2' }, someMessage3: { messageId: 'messageId3' } } } } } } as any, pointer: '', id: 'production' }); + expect(d.messages()).toBeInstanceOf(Messages); + expect(d.messages().all()).toHaveLength(1); + expect(d.messages().all()[0]).toBeInstanceOf(Message); + expect(d.messages().all()[0].messageId()).toEqual('messageId1'); + }); + }); describe('.variables()', function () { it('should return ServerVariables object', function () { From cfff0640f0fcdebeb5dfbb66ea2c0ef10eb3f1d3 Mon Sep 17 00:00:00 2001 From: Matatjahu Date: Fri, 21 Oct 2022 12:21:18 +0200 Subject: [PATCH 15/17] fix sonarcloud errors --- src/models/v3/channel.ts | 2 +- src/models/v3/message.ts | 6 +++--- src/models/v3/server.ts | 4 ++-- test/browser/sample-page.html | 1 + test/models/v3/channel.spec.ts | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/models/v3/channel.ts b/src/models/v3/channel.ts index 06a42d0f0..c1e65cbc6 100644 --- a/src/models/v3/channel.ts +++ b/src/models/v3/channel.ts @@ -80,7 +80,7 @@ export class Channel extends BaseModel impleme messages(): MessagesInterface { return new Messages( Object.entries(this._json.messages || {}).map(([messageName, message]) => { - return this.createModel(Message, message, { id: messageName, pointer: this.jsonPath(`messages/${messageName}`) }) + return this.createModel(Message, message, { id: messageName, pointer: this.jsonPath(`messages/${messageName}`) }); }) ); } diff --git a/src/models/v3/message.ts b/src/models/v3/message.ts index df5e22c1e..7e859f7db 100644 --- a/src/models/v3/message.ts +++ b/src/models/v3/message.ts @@ -45,7 +45,7 @@ export class Message extends MessageTrait implements MessageIn channels(): ChannelsInterface { const channels: ChannelInterface[] = []; Object.entries((this._meta.asyncapi?.parsed?.channels || {}) as v3.ChannelsObject).forEach(([channelName, channel]) => { - const hasMessage = Object.entries(channel.messages || {}).some(([_, message]) => message === this._json); + const hasMessage = Object.entries(channel.messages || {}).some(([, message]) => message === this._json); if (hasMessage) { channels.push( this.createModel(Channel, channel, { id: channelName, pointer: `/channels/${channelName}` }), @@ -55,7 +55,7 @@ export class Message extends MessageTrait implements MessageIn Object.entries((this._meta.asyncapi?.parsed as v3.AsyncAPIObject)?.operations || {}).forEach(([operationId, operation]) => { const operationChannel = operation.channel as v3.ChannelObject | undefined; if (!channels.some(channel => channel.json() === operationChannel)) { - const hasMessage = Object.entries(operationChannel?.messages || {}).some(([_, message]) => message === this._json); + const hasMessage = Object.entries(operationChannel?.messages || {}).some(([, message]) => message === this._json); if (hasMessage) { channels.push( this.createModel(Channel, operationChannel as v3.ChannelObject, { id: '', pointer: `/operations/${operationId}/channel` }), @@ -70,7 +70,7 @@ export class Message extends MessageTrait implements MessageIn const operations: OperationInterface[] = []; this.channels().forEach(channel => { operations.push(...channel.operations()); - }) + }); return new Operations(operations); } diff --git a/src/models/v3/server.ts b/src/models/v3/server.ts index 46b42b1ac..0f5fb5c16 100644 --- a/src/models/v3/server.ts +++ b/src/models/v3/server.ts @@ -66,9 +66,9 @@ export class Server extends BaseModel implement Object.entries((this._meta.asyncapi?.parsed as v3.AsyncAPIObject)?.operations || {}).forEach(([operationId, operation]) => { const operationChannel = operation.channel as v3.ChannelObject | undefined; if (!channels.some(channel => channel.json() === operationChannel)) { - const allowedServers: v3.ServerObject[] = operationChannel!.servers || []; + const allowedServers: v3.ServerObject[] = (operationChannel as v3.ChannelObject).servers || []; if (allowedServers.length === 0 || allowedServers.includes(this._json)) { - channels.push(this.createModel(Channel, operationChannel!, { id: '', pointer: `/operations/${tilde(operationId)}/channel` })); + channels.push(this.createModel(Channel, operationChannel as v3.ChannelObject, { id: '', pointer: `/operations/${tilde(operationId)}/channel` })); } } }); diff --git a/test/browser/sample-page.html b/test/browser/sample-page.html index b30811629..559f446df 100644 --- a/test/browser/sample-page.html +++ b/test/browser/sample-page.html @@ -1,6 +1,7 @@ + Simple browser app diff --git a/test/models/v3/channel.spec.ts b/test/models/v3/channel.spec.ts index f75fb9844..71e0a5904 100644 --- a/test/models/v3/channel.spec.ts +++ b/test/models/v3/channel.spec.ts @@ -53,7 +53,7 @@ describe('Channel model', function() { }); it('should return collection of servers - available only on particular ones', function() { - const someServer2 = {} + const someServer2 = {}; const doc = serializeInput({ servers: [someServer2] }); const d = new Channel(doc, { asyncapi: { parsed: { servers: { someServer1: {}, someServer2, } } } as any, pointer: '', id: 'channel' }); expect(d.servers()).toBeInstanceOf(Servers); From df73fecb0f3363beaeb446878930e5fdd170faf0 Mon Sep 17 00:00:00 2001 From: Matatjahu Date: Fri, 21 Oct 2022 12:23:22 +0200 Subject: [PATCH 16/17] create AsyncAPIDocumentV3 --- src/document.ts | 6 +++--- test/document.spec.ts | 12 ++++++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/document.ts b/src/document.ts index 7dac145bc..10ae9782d 100644 --- a/src/document.ts +++ b/src/document.ts @@ -9,14 +9,14 @@ import { import type { AsyncAPIDocumentInterface } from './models'; import type { DetailedAsyncAPI } from './types'; -import type { v2 } from './spec-types'; +import type { v2, v3 } from './spec-types'; export function createAsyncAPIDocument(asyncapi: DetailedAsyncAPI): AsyncAPIDocumentInterface { switch (asyncapi.semver.major) { case 2: return new AsyncAPIDocumentV2(asyncapi.parsed as v2.AsyncAPIObject, { asyncapi, pointer: '/' }); - // case 3: - // return new AsyncAPIDocumentV3(asyncapi.parsed, { asyncapi, pointer: '/' }); + case 3: + return new AsyncAPIDocumentV3(asyncapi.parsed as v3.AsyncAPIObject, { asyncapi, pointer: '/' }); default: throw new Error(`Unsupported AsyncAPI version: ${asyncapi.semver.version}`); } diff --git a/test/document.spec.ts b/test/document.spec.ts index a009db5e4..2507b42c5 100644 --- a/test/document.spec.ts +++ b/test/document.spec.ts @@ -1,5 +1,5 @@ import { xParserSpecParsed, xParserSpecStringified } from '../src/constants'; -import { BaseModel, AsyncAPIDocumentV2 } from '../src/models'; +import { BaseModel, AsyncAPIDocumentV2, AsyncAPIDocumentV3 } from '../src/models'; import { createAsyncAPIDocument, toAsyncAPIDocument, @@ -13,13 +13,21 @@ describe('utils', function() { class Model extends BaseModel {} describe('createAsyncAPIDocument()', function() { - it('should create a valid document from v2.0.0', function() { + it('should create a valid document from v2.x.x', function() { const doc = { asyncapi: '2.0.0' }; const detailed = createDetailedAsyncAPI(doc, doc as any); const d = createAsyncAPIDocument(detailed); expect(d.version()).toEqual(doc.asyncapi); expect(d).toBeInstanceOf(AsyncAPIDocumentV2); }); + + it('should create a valid document from v3.x.x', function() { + const doc = { asyncapi: '3.0.0' }; + const detailed = createDetailedAsyncAPI(doc, doc as any); + const d = createAsyncAPIDocument(detailed); + expect(d.version()).toEqual(doc.asyncapi); + expect(d).toBeInstanceOf(AsyncAPIDocumentV3); + }); it('should fail trying to create a document from a non supported spec version', function() { const doc = { asyncapi: '99.99.99' }; From a70e365326bd088190a7bee3fd5c88b8ed6d6b91 Mon Sep 17 00:00:00 2001 From: Matatjahu Date: Fri, 21 Oct 2022 12:29:42 +0200 Subject: [PATCH 17/17] fix linter errors --- src/document.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/document.ts b/src/document.ts index 10ae9782d..b49252786 100644 --- a/src/document.ts +++ b/src/document.ts @@ -15,8 +15,8 @@ export function createAsyncAPIDocument(asyncapi: DetailedAsyncAPI): AsyncAPIDocu switch (asyncapi.semver.major) { case 2: return new AsyncAPIDocumentV2(asyncapi.parsed as v2.AsyncAPIObject, { asyncapi, pointer: '/' }); - case 3: - return new AsyncAPIDocumentV3(asyncapi.parsed as v3.AsyncAPIObject, { asyncapi, pointer: '/' }); + case 3: + return new AsyncAPIDocumentV3(asyncapi.parsed as v3.AsyncAPIObject, { asyncapi, pointer: '/' }); default: throw new Error(`Unsupported AsyncAPI version: ${asyncapi.semver.version}`); }