diff --git a/src/models/components.ts b/src/models/components.ts index c88d88991..d75745e97 100644 --- a/src/models/components.ts +++ b/src/models/components.ts @@ -9,6 +9,8 @@ import type { SchemasInterface } from './schemas'; import type { ChannelParametersInterface } from './channel-parameters'; import type { ServerVariablesInterface } from './server-variables'; import type { OperationTraitsInterface } from './operation-traits'; +import type { OperationRepliesInterface } from './operation-replies'; +import type { OperationReplyAddressesInterface } from './operation-reply-addresses'; import type { MessageTraitsInterface } from './message-traits'; import type { SecuritySchemesInterface } from './security-schemes'; import type { CorrelationIdsInterface } from './correlation-ids'; @@ -25,6 +27,8 @@ export interface ComponentsInterface extends BaseModel, ExtensionsMixinInterface serverVariables(): ServerVariablesInterface; operationTraits(): OperationTraitsInterface; messageTraits(): MessageTraitsInterface; + replies(): OperationRepliesInterface; + replyAddresses(): OperationReplyAddressesInterface; correlationIds(): CorrelationIdsInterface; securitySchemes(): SecuritySchemesInterface; tags(): TagsInterface; diff --git a/src/models/external-documentation.ts b/src/models/external-documentation.ts index eefc56df2..9692a71d8 100644 --- a/src/models/external-documentation.ts +++ b/src/models/external-documentation.ts @@ -4,5 +4,6 @@ import type { DescriptionMixinInterface, ExtensionsMixinInterface } from './mixi export interface ExternalDocumentationInterface extends BaseModel, DescriptionMixinInterface, ExtensionsMixinInterface { + id(): string | undefined; url(): string; } \ No newline at end of file diff --git a/src/models/index.ts b/src/models/index.ts index cb0aa250f..167341c99 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -27,6 +27,10 @@ export * from './message'; export * from './messages'; export * from './oauth-flow'; export * from './oauth-flows'; +export * from './operation-replies'; +export * from './operation-reply-address'; +export * from './operation-reply-addresses'; +export * from './operation-reply'; export * from './operation-trait'; export * from './operation-traits'; export * from './operation'; diff --git a/src/models/operation-replies.ts b/src/models/operation-replies.ts new file mode 100644 index 000000000..a90768e08 --- /dev/null +++ b/src/models/operation-replies.ts @@ -0,0 +1,10 @@ +import { Collection } from './collection'; +import type { OperationReplyInterface } from './operation-reply'; + +export type OperationRepliesInterface = Collection + +export class OperationReplies extends Collection implements OperationRepliesInterface { + override get(id: string): OperationReplyInterface | undefined { + return this.collections.find(reply => reply.id() === id); + } +} diff --git a/src/models/operation-reply-address.ts b/src/models/operation-reply-address.ts new file mode 100644 index 000000000..ecdaac2a9 --- /dev/null +++ b/src/models/operation-reply-address.ts @@ -0,0 +1,7 @@ +import type { BaseModel } from './base'; +import type { DescriptionMixinInterface, ExtensionsMixinInterface } from './mixins'; + +export interface OperationReplyAddressInterface extends BaseModel, DescriptionMixinInterface, ExtensionsMixinInterface { + id(): string | undefined; + location(): string; +} diff --git a/src/models/operation-reply-addresses.ts b/src/models/operation-reply-addresses.ts new file mode 100644 index 000000000..34e9bf771 --- /dev/null +++ b/src/models/operation-reply-addresses.ts @@ -0,0 +1,10 @@ +import { Collection } from './collection'; +import type { OperationReplyAddressInterface } from './operation-reply-address'; + +export type OperationReplyAddressesInterface = Collection + +export class OperationReplyAddresses extends Collection implements OperationReplyAddressesInterface { + override get(id: string): OperationReplyAddressInterface | undefined { + return this.collections.find(reply => reply.id() === id); + } +} diff --git a/src/models/operation-reply.ts b/src/models/operation-reply.ts new file mode 100644 index 000000000..ba8b89496 --- /dev/null +++ b/src/models/operation-reply.ts @@ -0,0 +1,12 @@ +import type { BaseModel } from './base'; +import type { ExtensionsMixinInterface } from './mixins'; +import type { ChannelInterface } from './channel'; +import type { OperationReplyAddressInterface } from './operation-reply-address'; + +export interface OperationReplyInterface extends BaseModel, ExtensionsMixinInterface { + id(): string | undefined; + hasAddress(): boolean; + address(): OperationReplyAddressInterface | undefined; + hasChannel(): boolean; + channel(): ChannelInterface | undefined; +} diff --git a/src/models/operation.ts b/src/models/operation.ts index ef6c9516a..99d9b20e0 100644 --- a/src/models/operation.ts +++ b/src/models/operation.ts @@ -1,6 +1,7 @@ import type { BaseModel } from './base'; import type { OperationTraitsInterface } from './operation-traits'; import type { OperationTraitInterface } from './operation-trait'; +import type { OperationReplyInterface } from './operation-reply'; import type { ChannelsInterface } from './channels'; import type { ServersInterface } from './servers'; import type { MessagesInterface } from './messages'; @@ -14,5 +15,6 @@ export interface OperationInterface extends BaseModel, OperationTraitInterface { servers(): ServersInterface; channels(): ChannelsInterface messages(): MessagesInterface; + reply(): OperationReplyInterface | undefined; traits(): OperationTraitsInterface; } diff --git a/src/models/security-scheme.ts b/src/models/security-scheme.ts index e3c084605..9e70e2b88 100644 --- a/src/models/security-scheme.ts +++ b/src/models/security-scheme.ts @@ -9,6 +9,7 @@ export interface SecuritySchemeInterface extends BaseModel, DescriptionMixinInte openIdConnectUrl(): string | undefined; scheme(): string | undefined; flows(): OAuthFlowsInterface | undefined; + scopes(): string[] | undefined; type(): string; in(): string | undefined; } diff --git a/src/models/server.ts b/src/models/server.ts index 3406a1d38..2104d52be 100644 --- a/src/models/server.ts +++ b/src/models/server.ts @@ -9,7 +9,10 @@ import type { SecurityRequirementsInterface } from './security-requirements'; export interface ServerInterface extends BaseModel, CoreMixinInterface { id(): string url(): string; + host(): string; protocol(): string; + hasPathname(): boolean; + pathname(): string | undefined; protocolVersion(): string | undefined; hasProtocolVersion(): boolean; channels(): ChannelsInterface; diff --git a/src/models/tag.ts b/src/models/tag.ts index 87fc5b2ab..237633558 100644 --- a/src/models/tag.ts +++ b/src/models/tag.ts @@ -4,5 +4,6 @@ import type { DescriptionMixinInterface, ExtensionsMixinInterface, ExternalDocum export interface TagInterface extends BaseModel, DescriptionMixinInterface, ExtensionsMixinInterface, ExternalDocumentationMixinInterface { + id(): string | undefined; name(): string; } \ No newline at end of file diff --git a/src/models/v2/components.ts b/src/models/v2/components.ts index d66dc06d9..ec2d22321 100644 --- a/src/models/v2/components.ts +++ b/src/models/v2/components.ts @@ -21,6 +21,8 @@ import { ChannelParameters } from '../channel-parameters'; import { ServerVariables } from '../server-variables'; import { OperationTraits } from '../operation-traits'; import { MessageTraits } from '../message-traits'; +import { OperationReplies } from '../operation-replies'; +import { OperationReplyAddresses } from '../operation-reply-addresses'; import { SecuritySchemes } from '../security-schemes'; import { CorrelationIds } from '../correlation-ids'; import { Operations } from '../operations'; @@ -43,6 +45,8 @@ import type { ServerVariablesInterface } from '../server-variables'; import type { OperationTraitsInterface } from '../operation-traits'; import type { SecuritySchemesInterface } from '../security-schemes'; import type { MessageTraitsInterface } from '../message-traits'; +import type { OperationRepliesInterface } from '../operation-replies'; +import type { OperationReplyAddressesInterface } from '../operation-reply-addresses'; import type { OperationsInterface } from '../operations'; import type { ExternalDocumentationsInterface } from '../external-documentations'; import type { TagsInterface } from '../tags'; @@ -86,6 +90,14 @@ export class Components extends BaseModel implements Compon return this.createCollection('messageTraits', MessageTraits, MessageTrait); } + replies(): OperationRepliesInterface { + return new OperationReplies([]); + } + + replyAddresses(): OperationReplyAddressesInterface { + return new OperationReplyAddresses([]); + } + correlationIds(): CorrelationIds { return this.createCollection('correlationIds', CorrelationIds, CorrelationId); } diff --git a/src/models/v2/external-documentation.ts b/src/models/v2/external-documentation.ts index ccaf9d7a3..1e0423949 100644 --- a/src/models/v2/external-documentation.ts +++ b/src/models/v2/external-documentation.ts @@ -7,7 +7,11 @@ import type { ExtensionsInterface } from '../extensions'; import type { v2 } from '../../spec-types'; -export class ExternalDocumentation extends BaseModel implements ExternalDocumentationInterface { +export class ExternalDocumentation extends BaseModel implements ExternalDocumentationInterface { + id(): string | undefined { + return; + } + url(): string { return this._json.url; } diff --git a/src/models/v2/operation.ts b/src/models/v2/operation.ts index e71871981..9f2b34e19 100644 --- a/src/models/v2/operation.ts +++ b/src/models/v2/operation.ts @@ -12,6 +12,7 @@ import type { ChannelsInterface } from '../channels'; import type { ChannelInterface } from '../channel'; import type { MessagesInterface } from '../messages'; import type { OperationAction, OperationInterface } from '../operation'; +import type { OperationReplyInterface } from '../operation-reply'; import type { OperationTraitsInterface } from '../operation-traits'; import type { ServersInterface } from '../servers'; import type { ServerInterface } from '../server'; @@ -76,6 +77,10 @@ export class Operation extends OperationTrait implements Ope ); } + reply(): OperationReplyInterface | undefined { + return undefined; + } + traits(): OperationTraitsInterface { return new OperationTraits( (this._json.traits || []).map((trait: any, index: number) => { diff --git a/src/models/v2/security-scheme.ts b/src/models/v2/security-scheme.ts index 2e3b29930..65d8bb60b 100644 --- a/src/models/v2/security-scheme.ts +++ b/src/models/v2/security-scheme.ts @@ -43,6 +43,10 @@ export class SecurityScheme extends BaseModel implement return this._json.url; } + host(): string { + return resolveServerUrl(this._json.url).host; + } + protocol(): string { return this._json.protocol; } + hasPathname(): boolean { + return !!this.pathname(); + } + + pathname(): string | undefined { + return resolveServerUrl(this._json.url).pathname; + } + hasProtocolVersion(): boolean { return !!this._json.protocolVersion; } diff --git a/src/models/v2/tag.ts b/src/models/v2/tag.ts index e67144333..7b1578c1b 100644 --- a/src/models/v2/tag.ts +++ b/src/models/v2/tag.ts @@ -8,7 +8,11 @@ import type { TagInterface } from '../tag'; import type { v2 } from '../../spec-types'; -export class Tag extends BaseModel implements TagInterface { +export class Tag extends BaseModel implements TagInterface { + id(): string | undefined { + return; + } + name(): string { return this._json.name; } diff --git a/src/models/v3/asyncapi.ts b/src/models/v3/asyncapi.ts index dae3d17bb..b6c480814 100644 --- a/src/models/v3/asyncapi.ts +++ b/src/models/v3/asyncapi.ts @@ -47,7 +47,7 @@ export class AsyncAPIDocument extends BaseModel implements As servers(): ServersInterface { return new Servers( Object.entries(this._json.servers || {}).map(([serverName, server]) => - this.createModel(Server, server, { id: serverName, pointer: `/servers/${tilde(serverName)}` }) + this.createModel(Server, server as v3.ServerObject, { id: serverName, pointer: `/servers/${tilde(serverName)}` }) ) ); } @@ -55,7 +55,7 @@ export class AsyncAPIDocument extends BaseModel implements As channels(): ChannelsInterface { return new Channels( Object.entries(this._json.channels || {}).map(([channelId, channel]) => - this.createModel(Channel, channel, { id: channelId, pointer: `/channels/${tilde(channelId)}` }) + this.createModel(Channel, channel as v3.ChannelObject, { id: channelId, pointer: `/channels/${tilde(channelId)}` }) ) ); } @@ -63,7 +63,7 @@ export class AsyncAPIDocument extends BaseModel implements As operations(): OperationsInterface { return new Operations( Object.entries(this._json.operations || {}).map(([operationId, operation]) => - this.createModel(Operation, operation, { id: operationId, pointer: `/operations/${tilde(operationId)}` }) + this.createModel(Operation, operation as v3.OperationObject, { id: operationId, pointer: `/operations/${tilde(operationId)}` }) ) ); } @@ -73,8 +73,9 @@ export class AsyncAPIDocument extends BaseModel implements As const messagesData: any[] = []; this.channels().forEach(channel => { channel.messages().forEach(message => { - if (!messagesData.includes(message.json())) { - messagesData.push(message.json()); + const messageData = message.json(); + if (!messagesData.includes(messageData)) { + messagesData.push(messageData); messages.push(message); } }); diff --git a/src/models/v3/channel.ts b/src/models/v3/channel.ts index 355a900ab..394791177 100644 --- a/src/models/v3/channel.ts +++ b/src/models/v3/channel.ts @@ -31,14 +31,9 @@ export class Channel extends CoreModel impleme servers(): ServersInterface { const servers: ServerInterface[] = []; const allowedServers = this._json.servers || []; - Object.entries(this._meta.asyncapi?.parsed?.servers || {}).forEach(([serverName, server]) => { - if (allowedServers.length === 0) { + Object.entries(this._meta.asyncapi?.parsed.servers || {}).forEach(([serverName, server]) => { + if (allowedServers.length === 0 || allowedServers.includes(server)) { 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); @@ -47,9 +42,9 @@ export class Channel extends CoreModel impleme operations(): OperationsInterface { const operations: OperationInterface[] = []; Object.entries(((this._meta.asyncapi?.parsed as v3.AsyncAPIObject)?.operations || {})).forEach(([operationId, operation]) => { - if (operation.channel === this._json) { + if ((operation as v3.OperationObject).channel === this._json) { operations.push( - this.createModel(Operation, operation, { id: operationId, pointer: `/operations/${operationId}` }), + this.createModel(Operation, operation as v3.OperationObject, { id: operationId, pointer: `/operations/${operationId}` }), ); } }); @@ -59,7 +54,7 @@ export class Channel extends CoreModel 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 as v3.MessageObject, { id: messageName, pointer: this.jsonPath(`messages/${messageName}`) }); }) ); } @@ -69,7 +64,7 @@ export class Channel extends CoreModel impleme Object.entries(this._json.parameters || {}).map(([channelParameterName, channelParameter]) => { return this.createModel(ChannelParameter, channelParameter as v3.ParameterObject, { id: channelParameterName, - pointer: `${this._meta.pointer}/parameters/${channelParameterName}` + pointer: this.jsonPath(`parameters/${channelParameterName}`), }); }) ); diff --git a/src/models/v3/components.ts b/src/models/v3/components.ts index b58501a79..1415b8fe3 100644 --- a/src/models/v3/components.ts +++ b/src/models/v3/components.ts @@ -8,6 +8,8 @@ import { ChannelParameter } from './channel-parameter'; import { CorrelationId } from './correlation-id'; import { MessageTrait } from './message-trait'; import { OperationTrait } from './operation-trait'; +import { OperationReply } from './operation-reply'; +import { OperationReplyAddress } from './operation-reply-address'; import { Schema } from './schema'; import { SecurityScheme } from './security-scheme'; import { Server } from './server'; @@ -21,6 +23,8 @@ import { ChannelParameters } from '../channel-parameters'; import { ServerVariables } from '../server-variables'; import { OperationTraits } from '../operation-traits'; import { MessageTraits } from '../message-traits'; +import { OperationReplies } from '../operation-replies'; +import { OperationReplyAddresses } from '../operation-reply-addresses'; import { SecuritySchemes } from '../security-schemes'; import { CorrelationIds } from '../correlation-ids'; import { Operations } from '../operations'; @@ -46,6 +50,8 @@ import type { ServerVariablesInterface } from '../server-variables'; import type { OperationTraitsInterface } from '../operation-traits'; import type { SecuritySchemesInterface } from '../security-schemes'; import type { MessageTraitsInterface } from '../message-traits'; +import type { OperationRepliesInterface } from '../operation-replies'; +import type { OperationReplyAddressesInterface } from '../operation-reply-addresses'; import type { OperationsInterface } from '../operations'; import type { ExternalDocumentationsInterface } from '../external-documentations'; import type { TagsInterface } from '../tags'; @@ -89,6 +95,14 @@ export class Components extends BaseModel implements Compon return this.createCollection('messageTraits', MessageTraits, MessageTrait); } + replies(): OperationRepliesInterface { + return this.createCollection('replies', OperationReplies, OperationReply); + } + + replyAddresses(): OperationReplyAddressesInterface { + return this.createCollection('replyAddresses', OperationReplyAddresses, OperationReplyAddress); + } + correlationIds(): CorrelationIds { return this.createCollection('correlationIds', CorrelationIds, CorrelationId); } diff --git a/src/models/v3/external-documentation.ts b/src/models/v3/external-documentation.ts index 27abd8aab..f98f1296e 100644 --- a/src/models/v3/external-documentation.ts +++ b/src/models/v3/external-documentation.ts @@ -7,7 +7,11 @@ import type { ExtensionsInterface } from '../extensions'; import type { v3 } from '../../spec-types'; -export class ExternalDocumentation extends BaseModel implements ExternalDocumentationInterface { +export class ExternalDocumentation extends BaseModel implements ExternalDocumentationInterface { + id(): string | undefined { + return this._meta.id; + } + url(): string { return this._json.url; } diff --git a/src/models/v3/index.ts b/src/models/v3/index.ts index 6187a29e2..d774e455d 100644 --- a/src/models/v3/index.ts +++ b/src/models/v3/index.ts @@ -23,6 +23,9 @@ 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 { OperationReplies as OperationRepliesV3 } from '../operation-replies'; +export { OperationReplyAddress as OperationReplyAddressV3 } from './operation-reply-address'; +export { OperationReply as OperationReplyV3 } from './operation-reply'; export { Operation as OperationV3 } from './operation'; export { Operations as OperationsV3 } from '../operations'; export { Schema as SchemaV3 } from './schema'; diff --git a/src/models/v3/message-trait.ts b/src/models/v3/message-trait.ts index e50e76d70..5a261e77d 100644 --- a/src/models/v3/message-trait.ts +++ b/src/models/v3/message-trait.ts @@ -37,7 +37,7 @@ export class MessageTrait { - return this.createModel(MessageExample, example, { pointer: `${this._meta.pointer}/examples/${index}` }); + return this.createModel(MessageExample, example, { pointer: this.jsonPath(`examples/${index}`) }); }) ); } diff --git a/src/models/v3/message.ts b/src/models/v3/message.ts index 7e859f7db..9ec639003 100644 --- a/src/models/v3/message.ts +++ b/src/models/v3/message.ts @@ -1,6 +1,6 @@ import { Channels } from '../channels'; -import { Channel } from './channel'; import { Operations } from '../operations'; +import { Operation } from './operation'; import { MessageTraits } from '../message-traits'; import { MessageTrait } from './message-trait'; import { Servers } from '../servers'; @@ -25,7 +25,7 @@ export class Message extends MessageTrait implements MessageIn payload(): SchemaInterface | undefined { if (!this._json.payload) return undefined; - return this.createModel(Schema, this._json.payload, { pointer: `${this._meta.pointer}/payload` }); + return this.createModel(Schema, this._json.payload, { pointer: this.jsonPath('payload') }); } servers(): ServersInterface { @@ -33,8 +33,9 @@ export class Message extends MessageTrait implements MessageIn const serversData: any[] = []; this.channels().forEach(channel => { channel.servers().forEach(server => { - if (!serversData.includes(server.json())) { - serversData.push(server.json()); + const serverData = server.json(); + if (!serversData.includes(serverData)) { + serversData.push(serverData); servers.push(server); } }); @@ -44,32 +45,26 @@ 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); - 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` }), - ); + const channelData: any[] = []; + this.operations().forEach(operation => { + operation.channels().forEach(channel => { + const channelsData = channel.json(); + if (!channelData.includes(channelsData)) { + channelData.push(channelsData); + channels.push(channel); } - } + }); }); return new Channels(channels); } operations(): OperationsInterface { const operations: OperationInterface[] = []; - this.channels().forEach(channel => { - operations.push(...channel.operations()); + Object.entries((this._meta.asyncapi?.parsed as v3.AsyncAPIObject)?.operations || {}).forEach(([operationId, operation]) => { + const operationModel = this.createModel(Operation, operation as v3.OperationObject, { id: operationId, pointer: `/operations/${operationId}` }); + if (operationModel.messages().some(m => m.json() === this._json)) { + operations.push(operationModel); + } }); return new Operations(operations); } @@ -77,7 +72,7 @@ export class Message extends MessageTrait implements MessageIn 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}` }); + return this.createModel(MessageTrait, trait, { id: '', pointer: this.jsonPath(`traits/${index}`) }); }) ); } diff --git a/src/models/v3/mixins.ts b/src/models/v3/mixins.ts index db7532e4c..974fc7416 100644 --- a/src/models/v3/mixins.ts +++ b/src/models/v3/mixins.ts @@ -29,7 +29,7 @@ export interface CoreObject extends v3.SpecificationExtensions { title?: string; summary?: string; description?: string; - externalDocs?: v3.ExternalDocumentationObject; + externalDocs?: v3.ExternalDocumentationObject | v3.ReferenceObject; tags?: v3.TagsObject; bindings?: BindingsObject; } @@ -110,12 +110,12 @@ export function extensions(model: BaseModel): Extens return new Extensions(extensions); } -export function hasExternalDocs(model: BaseModel<{ externalDocs?: v3.ExternalDocumentationObject }>): boolean { +export function hasExternalDocs(model: BaseModel<{ externalDocs?: v3.ExternalDocumentationObject | v3.ReferenceObject }>): boolean { return Object.keys(model.json('externalDocs') || {}).length > 0; } -export function externalDocs(model: BaseModel<{ externalDocs?: v3.ExternalDocumentationObject }>): ExternalDocumentationInterface | undefined { - if (hasExternalDocs(model)) { +export function externalDocs(model: BaseModel<{ externalDocs?: v3.ExternalDocumentationObject | v3.ReferenceObject }>): ExternalDocumentationInterface | undefined { + if (hasExternalDocs(model as BaseModel<{ externalDocs?: v3.ExternalDocumentationObject }>)) { return new ExternalDocumentation(model.json('externalDocs') as v3.ExternalDocumentationObject); } } @@ -123,7 +123,7 @@ export function externalDocs(model: BaseModel<{ externalDocs?: v3.ExternalDocume export function tags(model: BaseModel<{ tags?: v3.TagsObject }>): TagsInterface { return new Tags( (model.json('tags') || []).map((tag, idx) => - createModel(Tag, tag, { pointer: model.jsonPath(`tags/${idx}`) }, model) + createModel(Tag, tag as v3.TagObject, { pointer: model.jsonPath(`tags/${idx}`) }, model) ) ); } diff --git a/src/models/v3/oauth-flow.ts b/src/models/v3/oauth-flow.ts index 464d3dd7d..6c168dff6 100644 --- a/src/models/v3/oauth-flow.ts +++ b/src/models/v3/oauth-flow.ts @@ -5,11 +5,11 @@ import { extensions } from './mixins'; import type { ExtensionsInterface } from '../extensions'; import type { OAuthFlowInterface } from '../oauth-flow'; -import type { v2 } from '../../spec-types'; +import type { v3 } from '../../spec-types'; -export class OAuthFlow extends BaseModel implements OAuthFlowInterface { +export class OAuthFlow extends BaseModel implements OAuthFlowInterface { authorizationUrl(): string | undefined { - return this.json().authorizationUrl; + return this.json().authorizationUrl; } hasRefreshUrl(): boolean { @@ -21,11 +21,11 @@ export class OAuthFlow extends BaseModel im } scopes(): Record | undefined { - return this._json.scopes; + return this._json.availableScopes; } tokenUrl(): string | undefined { - return this.json>().tokenUrl; + return this.json>().tokenUrl; } extensions(): ExtensionsInterface { diff --git a/src/models/v3/operation-reply-address.ts b/src/models/v3/operation-reply-address.ts new file mode 100644 index 000000000..8e989a3ad --- /dev/null +++ b/src/models/v3/operation-reply-address.ts @@ -0,0 +1,30 @@ +import { BaseModel } from '../base'; + +import { hasDescription, description, extensions } from './mixins'; + +import type { ExtensionsInterface } from '../extensions'; +import type { OperationReplyAddressInterface } from '../operation-reply-address'; + +import type { v3 } from '../../spec-types'; + +export class OperationReplyAddress extends BaseModel implements OperationReplyAddressInterface { + id(): string | undefined { + return this._meta.id; + } + + location(): string { + 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/operation-reply.ts b/src/models/v3/operation-reply.ts new file mode 100644 index 000000000..f26b32ef1 --- /dev/null +++ b/src/models/v3/operation-reply.ts @@ -0,0 +1,43 @@ +import { BaseModel } from '../base'; +import { Channel } from './channel'; +import { OperationReplyAddress } from './operation-reply-address'; + +import { extensions } from './mixins'; + +import type { ExtensionsInterface } from '../extensions'; +import type { OperationReplyInterface } from '../operation-reply'; +import type { OperationReplyAddressInterface } from '../operation-reply-address'; +import type { ChannelInterface } from '../channel'; + +import type { v3 } from '../../spec-types'; + +export class OperationReply extends BaseModel implements OperationReplyInterface { + id(): string | undefined { + return this._meta.id; + } + + hasAddress(): boolean { + return !!this._json.address; + } + + address(): OperationReplyAddressInterface | undefined { + if (this._json.address) { + return this.createModel(OperationReplyAddress, this._json.address as v3.OperationReplyAddressObject, { pointer: this.jsonPath('address') }); + } + } + + hasChannel(): boolean { + return !!this._json.channel; + } + + channel(): ChannelInterface | undefined { + if (this._json.channel) { + return this.createModel(Channel, this._json.channel as v3.ChannelObject, { id: '', pointer: this.jsonPath('channel') }); + } + return this._json.channel; + } + + extensions(): ExtensionsInterface { + return extensions(this); + } +} diff --git a/src/models/v3/operation-trait.ts b/src/models/v3/operation-trait.ts index b94c72188..46668a2bb 100644 --- a/src/models/v3/operation-trait.ts +++ b/src/models/v3/operation-trait.ts @@ -18,16 +18,10 @@ export class OperationTrait; - 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); + return (this._json.security || []).map((security, index) => { + const scheme = this.createModel(SecurityScheme, security as v3.SecuritySchemeObject, { id: '', pointer: this.jsonPath(`security/${index}`) }); + const requirement = this.createModel(SecurityRequirement, { scheme, scopes: (security as v3.SecuritySchemeObject).scopes }, { id: '', pointer: this.jsonPath(`security/${index}`) }); + return new SecurityRequirements([requirement]); }); } } diff --git a/src/models/v3/operation.ts b/src/models/v3/operation.ts index 6f12ac880..655b1da3d 100644 --- a/src/models/v3/operation.ts +++ b/src/models/v3/operation.ts @@ -1,14 +1,17 @@ import { Messages } from '../messages'; +import { Message } from './message'; import { Channels } from '../channels'; import { Channel } from './channel'; import { OperationTraits } from '../operation-traits'; import { OperationTrait } from './operation-trait'; +import { OperationReply } from './operation-reply'; import { Servers } from '../servers'; import type { ChannelsInterface } from '../channels'; import type { MessagesInterface } from '../messages'; import type { MessageInterface } from '../message'; import type { OperationInterface, OperationAction } from '../operation'; +import type { OperationReplyInterface } from 'models/operation-reply'; import type { OperationTraitsInterface } from '../operation-traits'; import type { ServersInterface } from '../servers'; import type { ServerInterface } from '../server'; @@ -33,8 +36,9 @@ export class Operation extends OperationTrait implements Ope const serversData: any[] = []; this.channels().forEach(channel => { channel.servers().forEach(server => { - if (!serversData.includes(server.json())) { - serversData.push(server.json()); + const serverData = server.json(); + if (!serversData.includes(serverData)) { + serversData.push(serverData); servers.push(server); } }); @@ -44,25 +48,44 @@ export class Operation extends OperationTrait implements Ope channels(): ChannelsInterface { if (this._json.channel) { - return new Channels([ - this.createModel(Channel, this._json.channel as v3.ChannelObject, { id: '', pointer: this.jsonPath('channel') }) - ]); + for (const [channelName, channel] of Object.entries(this._meta.asyncapi?.parsed.channels || {})) { + if (channel === this._json.channel) { + return new Channels([ + this.createModel(Channel, this._json.channel as v3.ChannelObject, { id: channelName, pointer: `/channels/${channelName}` }) + ]); + } + } } return new Channels([]); } messages(): MessagesInterface { const messages: MessageInterface[] = []; + if (Array.isArray(this._json.messages)) { + this._json.messages.forEach((message, index) => { + messages.push( + this.createModel(Message, message as v3.MessageObject, { id: '', pointer: this.jsonPath(`messages/${index}`) }) + ); + }); + return new Messages(messages); + } + this.channels().forEach(channel => { messages.push(...channel.messages()); }); return new Messages(messages); } + reply(): OperationReplyInterface | undefined { + if (this._json.reply) { + return this.createModel(OperationReply, this._json.reply as v3.OperationReplyObject, { pointer: this.jsonPath('reply') }); + } + } + traits(): OperationTraitsInterface { return new OperationTraits( (this._json.traits || []).map((trait: any, index: number) => { - return this.createModel(OperationTrait, trait, { id: '', pointer: `${this._meta.pointer}/traits/${index}` }); + return this.createModel(OperationTrait, trait, { id: '', pointer: this.jsonPath(`traits/${index}`) }); }) ); } diff --git a/src/models/v3/security-scheme.ts b/src/models/v3/security-scheme.ts index 09040e9bf..d922664e4 100644 --- a/src/models/v3/security-scheme.ts +++ b/src/models/v3/security-scheme.ts @@ -43,6 +43,10 @@ export class SecurityScheme extends BaseModel implement } url(): string { - return this._json.url; + let host = this._json.host; + if (!host.endsWith('/')) { + host = `${host}/`; + } + let pathname = this._json.pathname || ''; + if (pathname.startsWith('/')) { + pathname = pathname.slice(1); + } + return `${host}${pathname}`; + } + + host(): string { + return this._json.host; } protocol(): string { return this._json.protocol; } + hasPathname(): boolean { + return !!this._json.pathname; + } + + pathname(): string | undefined { + return this._json.pathname; + } + hasProtocolVersion(): boolean { return !!this._json.protocolVersion; } @@ -46,18 +66,9 @@ export class Server extends CoreModel implement channels(): ChannelsInterface { const channels: ChannelInterface[] = []; Object.entries((this._meta.asyncapi?.parsed as v3.AsyncAPIObject)?.channels || {}).forEach(([channelName, channel]) => { - const allowedServers: v3.ServerObject[] = channel.servers || []; + const allowedServers = (channel as v3.ChannelObject).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 as v3.ChannelObject).servers || []; - if (allowedServers.length === 0 || allowedServers.includes(this._json)) { - channels.push(this.createModel(Channel, operationChannel as v3.ChannelObject, { id: '', pointer: `/operations/${tilde(operationId)}/channel` })); - } + channels.push(this.createModel(Channel, channel as v3.ChannelObject, { id: channelName, pointer: `/channels/${tilde(channelName)}` })); } }); return new Channels(channels); @@ -68,9 +79,10 @@ export class Server extends CoreModel implement const operationsData: v3.OperationObject[] = []; this.channels().forEach(channel => { channel.operations().forEach(operation => { - if (!operationsData.includes(operation.json())) { + const operationData = operation.json(); + if (!operationsData.includes(operationData)) { operations.push(operation); - operationsData.push(operation.json()); + operationsData.push(operationData); } }); }); @@ -82,9 +94,10 @@ export class Server extends CoreModel implement const messagedData: v3.MessageObject[] = []; this.channels().forEach(channel => { channel.messages().forEach(message => { - if (!messagedData.includes(message.json())) { + const messageData = message.json(); + if (!messagedData.includes(messageData)) { messages.push(message); - messagedData.push(message.json()); + messagedData.push(messageData); } }); }); @@ -94,25 +107,19 @@ export class Server extends CoreModel implement variables(): ServerVariablesInterface { return new ServerVariables( Object.entries(this._json.variables || {}).map(([serverVariableName, serverVariable]) => { - return this.createModel(ServerVariable, serverVariable, { + return this.createModel(ServerVariable, serverVariable as v3.ServerVariableObject, { id: serverVariableName, - pointer: `${this._meta.pointer}/variables/${serverVariableName}` + pointer: this.jsonPath(`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); + return (this._json.security || []).map((security, index) => { + const scheme = this.createModel(SecurityScheme, security as v3.SecuritySchemeObject, { id: '', pointer: this.jsonPath(`security/${index}`) }); + const requirement = this.createModel(SecurityRequirement, { scheme, scopes: (security as v3.SecuritySchemeObject).scopes }, { id: '', pointer: this.jsonPath(`security/${index}`) }); + return new SecurityRequirements([requirement]); }); } } diff --git a/src/models/v3/tag.ts b/src/models/v3/tag.ts index c3dec38a7..2aa7ccb0b 100644 --- a/src/models/v3/tag.ts +++ b/src/models/v3/tag.ts @@ -8,7 +8,11 @@ import type { TagInterface } from '../tag'; import type { v3 } from '../../spec-types'; -export class Tag extends BaseModel implements TagInterface { +export class Tag extends BaseModel implements TagInterface { + id(): string | undefined { + return this._meta.id; + } + name(): string { return this._json.name; } diff --git a/src/spec-types/v3.ts b/src/spec-types/v3.ts index df2d66d23..b225c821d 100644 --- a/src/spec-types/v3.ts +++ b/src/spec-types/v3.ts @@ -23,7 +23,7 @@ export interface InfoObject extends SpecificationExtensions { contact?: ContactObject; license?: LicenseObject; tags?: TagsObject; - externalDocs?: ExternalDocumentationObject; + externalDocs?: ExternalDocumentationObject | ReferenceObject; } export interface ContactObject extends SpecificationExtensions { @@ -37,16 +37,18 @@ export interface LicenseObject extends SpecificationExtensions { url?: string; } -export type ServersObject = Record; +export type ServersObject = Record; export interface ServerObject extends SpecificationExtensions { - url: string; + host: string; protocol: string; + pathname?: string; protocolVersion?: string; description?: string; - variables?: Record; - security?: Array; + variables?: Record; + security?: Array; tags?: TagsObject; + externalDocs?: ExternalDocumentationObject | ReferenceObject; bindings?: ServerBindingsObject | ReferenceObject; } @@ -74,19 +76,22 @@ export interface ServerBindingsObject extends SpecificationExtensions { redis?: Binding; mercure?: Binding; ibmmq?: Binding; + googlepubsub?: Binding; } -export type ChannelsObject = Record; +export type ChannelsObject = Record; export interface ChannelObject extends SpecificationExtensions { address?: string | null; messages?: MessagesObject; + title?: string; + summary?: string; description?: string; - servers?: Array; + servers?: Array; parameters?: ParametersObject; - bindings?: ChannelBindingsObject | ReferenceObject; tags?: TagsObject; - externalDocs?: ExternalDocumentationObject; + externalDocs?: ExternalDocumentationObject | ReferenceObject; + bindings?: ChannelBindingsObject | ReferenceObject; } export interface ChannelBindingsObject extends SpecificationExtensions { @@ -106,31 +111,46 @@ export interface ChannelBindingsObject extends SpecificationExtensions { redis?: Binding; mercure?: Binding; ibmmq?: Binding; + googlepubsub?: Binding; } -export type OperationsObject = Record; +export type OperationsObject = Record; export interface OperationObject extends SpecificationExtensions { action: 'send' | 'receive'; channel: ChannelObject | ReferenceObject; + messages?: Array; + reply?: OperationReplyObject | ReferenceObject; + title?: string; summary?: string; description?: string; - security?: Array; + security?: Array; tags?: TagsObject; - externalDocs?: ExternalDocumentationObject; + externalDocs?: ExternalDocumentationObject | ReferenceObject; bindings?: OperationBindingsObject | ReferenceObject; - traits?: OperationTraitObject[]; + traits?: Array; } export interface OperationTraitObject extends SpecificationExtensions { + title?: string; summary?: string; description?: string; - security?: Array; + security?: Array; tags?: TagsObject; - externalDocs?: ExternalDocumentationObject; + externalDocs?: ExternalDocumentationObject | ReferenceObject; bindings?: OperationBindingsObject | ReferenceObject; } +export interface OperationReplyObject extends SpecificationExtensions { + channel?: ChannelObject | ReferenceObject; + address?: OperationReplyAddressObject | ReferenceObject; +} + +export interface OperationReplyAddressObject extends SpecificationExtensions { + location: string; + description?: string; +} + export interface OperationBindingsObject extends SpecificationExtensions { http?: Binding; ws?: Binding; @@ -148,9 +168,10 @@ export interface OperationBindingsObject extends SpecificationExtensions { redis?: Binding; mercure?: Binding; ibmmq?: Binding; + googlepubsub?: Binding; } -export type MessagesObject = Record; +export type MessagesObject = Record; export interface MessageObject extends MessageTraitObject, SpecificationExtensions { payload?: any; @@ -168,7 +189,7 @@ export interface MessageTraitObject extends SpecificationExtensions { summary?: string; description?: string; tags?: TagsObject; - externalDocs?: ExternalDocumentationObject; + externalDocs?: ExternalDocumentationObject | ReferenceObject; bindings?: MessageBindingsObject | ReferenceObject; examples?: Array; } @@ -197,6 +218,7 @@ export interface MessageBindingsObject extends SpecificationExtensions { redis?: Binding; mercure?: Binding; ibmmq?: Binding; + googlepubsub?: Binding; } export type ParametersObject = Record; @@ -207,12 +229,12 @@ export interface ParameterObject extends SpecificationExtensions { location?: string; } -export type TagsObject = Array; +export type TagsObject = Array; export interface TagObject extends SpecificationExtensions { name: string; description?: string; - externalDocs?: ExternalDocumentationObject; + externalDocs?: ExternalDocumentationObject | ReferenceObject; } export interface ExternalDocumentationObject extends SpecificationExtensions { @@ -224,11 +246,13 @@ export interface ComponentsObject extends SpecificationExtensions { servers?: Record; channels?: Record; operations?: Record; - schemas?: Record; messages?: Record; + schemas?: Record; securitySchemes?: Record; - parameters?: Record; serverVariables?: Record; + parameters?: Record; + replies?: Record; + replyAddresses?: Record; correlationIds?: Record; operationTraits?: Record; messageTraits?: Record; @@ -249,6 +273,7 @@ export interface SecuritySchemeObject extends SpecificationExtensions { bearerFormat?: string; flows?: OAuthFlowsObject; openIdConnectUrl?: string; + scopes?: string[]; } export type SecuritySchemeType = @@ -314,6 +339,7 @@ export interface SecuritySchemeObjectHttp extends SecuritySchemeObjectBase, Spec export interface SecuritySchemeObjectOauth2 extends SecuritySchemeObjectBase, SpecificationExtensions { type: 'oauth2'; flows: OAuthFlowsObject; + scopes: string[]; } export interface SecuritySchemeObjectOpenIdConnect extends SecuritySchemeObjectBase, SpecificationExtensions { @@ -352,7 +378,7 @@ export type OAuthFlowObject = export interface OAuthFlowObjectBase extends SpecificationExtensions { refreshUrl?: string; - scopes: Record; + availableScopes: Record; } export interface OAuthFlowObjectImplicit extends OAuthFlowObjectBase, SpecificationExtensions { @@ -461,9 +487,7 @@ export interface ReferenceObject { $ref: string; } -export type SecurityRequirementObject = Record>; - export interface CorrelationIDObject extends SpecificationExtensions { location: string; description?: string; -} \ No newline at end of file +} diff --git a/src/utils.ts b/src/utils.ts index 1b218dbb8..be8b2c637 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -122,6 +122,20 @@ export function retrievePossibleRef(data: any, pathOfData: string, spec: any = { return data; } +export function resolveServerUrl(url: string): { host: string, pathname: string | undefined } { + // eslint-disable-next-line prefer-const + let [maybeProtocol, maybeHost] = url.split('://'); + if (!maybeHost) { + maybeHost = maybeProtocol; + } + + const [host, ...pathnames] = maybeHost.split('/'); + if (pathnames.length) { + return { host, pathname: `/${pathnames.join('/')}` }; + } + return { host, pathname: undefined }; +} + function retrieveDeepData(value: Record, path: string[]) { let index = 0; const length = path.length; diff --git a/test/models/v2/components.spec.ts b/test/models/v2/components.spec.ts index c9d624a99..ba855d480 100644 --- a/test/models/v2/components.spec.ts +++ b/test/models/v2/components.spec.ts @@ -20,6 +20,8 @@ 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 { OperationReplies } from '../../../src/models/operation-replies'; +import { OperationReplyAddresses } from '../../../src/models/operation-reply-addresses'; import { MessageTraits } from '../../../src/models/message-traits'; import { CorrelationIds } from '../../../src/models/correlation-ids'; import { SecuritySchemes } from '../../../src/models/security-schemes'; @@ -174,6 +176,26 @@ describe('Components model', function() { }); }); + describe('.replies()', function() { + it('should return OperationReplies with empty collection', function() { + const doc = {}; + const d = new Components(doc); + const replies = d.replies(); + expect(replies).toBeInstanceOf(OperationReplies); + expect(replies.all()).toHaveLength(0); + }); + }); + + describe('.replyAddresses()', function() { + it('should return OperationRepliesAddresses with empty collection', function() { + const doc = {}; + const d = new Components(doc); + const replies = d.replyAddresses(); + expect(replies).toBeInstanceOf(OperationReplyAddresses); + expect(replies.all()).toHaveLength(0); + }); + }); + describe('.correlationIds()', function() { it('should return CorrelationIds with CorrelationId Object', function() { const doc = serializeInput({ correlationIds: { id: {} } }); diff --git a/test/models/v2/operation.spec.ts b/test/models/v2/operation.spec.ts index 42689d14a..b00560894 100644 --- a/test/models/v2/operation.spec.ts +++ b/test/models/v2/operation.spec.ts @@ -145,6 +145,14 @@ describe('Operation model', function() { }); }); + describe('.reply()', function() { + it('should return undefined', function() { + const doc = {}; + const d = new Operation(doc); + expect(d.reply()).toBeUndefined(); + }); + }); + describe('.traits()', function() { it('should return collection of traits', function() { const doc = { traits: [{ operationId: '...' }] }; diff --git a/test/models/v2/server.spec.ts b/test/models/v2/server.spec.ts index 28ec854ef..8e432188c 100644 --- a/test/models/v2/server.spec.ts +++ b/test/models/v2/server.spec.ts @@ -16,9 +16,9 @@ import type { v2 } from '../../../src/spec-types'; const doc = { development: { + url: 'development.gigantic-server.com:2137/some/path', protocol: 'mqtt', protocolVersion: '1.0.0', - url: 'development.gigantic-server.com', variables: { username: { default: 'demo', @@ -33,13 +33,57 @@ const emptyItem = new Server(serializeInput({}), { asyncapi: {} describe('Server Model', function () { describe('.id()', function () { it('should return name if present', function () { - expect(docItem.id()).toMatch('development'); + expect(docItem.id()).toEqual('development'); + }); + }); + + describe('.url()', function () { + it('should return value', function () { + expect(docItem.url()).toEqual(doc.development.url); + }); + }); + + describe('.host()', function () { + it('should return value', function () { + expect(docItem.host()).toEqual('development.gigantic-server.com:2137'); + }); + + it('should return value (url with protocol case)', function () { + const server = new Server({ ...doc.development, url: 'mqtt://development.gigantic-server.com:2137/some/path' }, { asyncapi: {} as any, pointer: '', id: 'development' }); + expect(server.host()).toEqual('development.gigantic-server.com:2137'); }); }); describe('protocol()', function () { it('should return protocol ', function () { - expect(docItem.protocol()).toMatch(doc.development.protocol); + expect(docItem.protocol()).toEqual(doc.development.protocol); + }); + }); + + describe('.hasPathname()', function () { + it('should return true if pathname is not missing', function () { + expect(docItem.hasPathname()).toEqual(true); + }); + + it('should be false when protocolVersion is missing', function () { + const docItem = new Server({ ...doc.development, url: 'mqtt://development.gigantic-server.com:2137' }, { asyncapi: {} as any, pointer: '', id: 'development' }); + expect(docItem.hasPathname()).toEqual(false); + }); + }); + + describe('.pathname()', function () { + it('should return value', function () { + expect(docItem.pathname()).toEqual('/some/path'); + }); + + it('should return value (url with protocol case)', function () { + const server = new Server({ ...doc.development, url: 'mqtt://development.gigantic-server.com:2137/some/path' }, { asyncapi: {} as any, pointer: '', id: 'development' }); + expect(server.pathname()).toEqual('/some/path'); + }); + + it('should return undefined if pathname does not exist in url', function () { + const docItem = new Server({ ...doc.development, url: 'development.gigantic-server.com:2137' }, { asyncapi: {} as any, pointer: '', id: 'development' }); + expect(docItem.pathname()).toBeUndefined(); }); }); @@ -55,7 +99,7 @@ describe('Server Model', function () { describe('.protocolVersion()', function () { it('should return value', function () { - expect(docItem.protocolVersion()).toMatch(doc.development.protocolVersion); + expect(docItem.protocolVersion()).toEqual(doc.development.protocolVersion); }); it('should return undefined when protocolVersion is missing', function () { @@ -63,12 +107,6 @@ describe('Server Model', function () { }); }); - 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({}); diff --git a/test/models/v3/components.spec.ts b/test/models/v3/components.spec.ts index 48c06d7d6..f4439328d 100644 --- a/test/models/v3/components.spec.ts +++ b/test/models/v3/components.spec.ts @@ -4,6 +4,8 @@ import { Channel } from '../../../src/models/v3/channel'; import { ChannelParameter } from '../../../src/models/v3/channel-parameter'; import { CorrelationId } from '../../../src/models/v3/correlation-id'; import { OperationTrait } from '../../../src/models/v3/operation-trait'; +import { OperationReply } from '../../../src/models/v3/operation-reply'; +import { OperationReplyAddress } from '../../../src/models/v3/operation-reply-address'; import { Message } from '../../../src/models/v3/message'; import { MessageTrait } from '../../../src/models/v3/message-trait'; import { Schema } from '../../../src/models/v3/schema'; @@ -20,6 +22,8 @@ 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 { OperationReplies } from '../../../src/models/operation-replies'; +import { OperationReplyAddresses } from '../../../src/models/operation-reply-addresses'; import { MessageTraits } from '../../../src/models/message-traits'; import { CorrelationIds } from '../../../src/models/correlation-ids'; import { SecuritySchemes } from '../../../src/models/security-schemes'; @@ -183,6 +187,38 @@ describe('Components model', function() { }); }); + describe('.replies()', function() { + it('should return OperationReplies with OperationReply Object', function() { + const doc = serializeInput({ replies: { reply: {} } }); + const d = new Components(doc); + testCollection(doc, d.replies(), 'replies', OperationReplies, OperationReply); + }); + + it('should return OperationReplies with empty reply objects when replies are not defined', function() { + const doc = serializeInput({}); + const d = new Components(doc); + const items = d.replies(); + expect(items).toBeInstanceOf(OperationReplies); + expect(items.all()).toHaveLength(0); + }); + }); + + describe('.replyAddresses()', function() { + it('should return OperationRepliesAddresses with OperationReplyAddress Object', function() { + const doc = serializeInput({ replyAddresses: { address: {} } }); + const d = new Components(doc); + testCollection(doc, d.replyAddresses(), 'replyAddresses', OperationReplyAddresses, OperationReplyAddress); + }); + + it('should return OperationRepliesAddresses with empty reply objects when replyAddresses are not defined', function() { + const doc = serializeInput({}); + const d = new Components(doc); + const items = d.replyAddresses(); + expect(items).toBeInstanceOf(OperationReplyAddresses); + expect(items.all()).toHaveLength(0); + }); + }); + describe('.correlationIds()', function() { it('should return CorrelationIds with CorrelationId Object', function() { const doc = serializeInput({ correlationIds: { id: {} } }); diff --git a/test/models/v3/oauth-flow.spec.ts b/test/models/v3/oauth-flow.spec.ts index 882cc0105..0561f2846 100644 --- a/test/models/v3/oauth-flow.spec.ts +++ b/test/models/v3/oauth-flow.spec.ts @@ -6,7 +6,7 @@ import type { v3 } from '../../../src/spec-types'; const flowObject = { authorizationUrl: 'https://example.com/api/oauth/dialog', - scopes: { + availableScopes: { 'write:pets': 'modify pets in your account', 'read:pets': 'read your pets' } @@ -29,7 +29,7 @@ describe('OAuth Flow', function() { describe('.scopes()', function() { it('should return scopes if present', function() { expect(emptyObject.scopes()).toBeUndefined(); - expect(flow.scopes()!['write:pets']).toMatch(flowObject.scopes['write:pets']); + expect(flow.scopes()!['write:pets']).toMatch(flowObject.availableScopes['write:pets']); }); }); diff --git a/test/models/v3/operation-reply-address.spec.ts b/test/models/v3/operation-reply-address.spec.ts new file mode 100644 index 000000000..2187bb90b --- /dev/null +++ b/test/models/v3/operation-reply-address.spec.ts @@ -0,0 +1,17 @@ +import { OperationReplyAddress } from '../../../src/models/v3/operation-reply-address'; + +import { assertDescription, assertExtensions } from './utils'; + +describe('OperationReplyAddress model', function() { + describe('.location()', function() { + it('should return location', function() { + const d = new OperationReplyAddress({ location: 'location' }, { asyncapi: {} as any, pointer: '' }); + expect(d.location()).toEqual('location'); + }); + }); + + describe('mixins', function() { + assertDescription(OperationReplyAddress); + assertExtensions(OperationReplyAddress); + }); +}); diff --git a/test/models/v3/operation-reply.spec.ts b/test/models/v3/operation-reply.spec.ts new file mode 100644 index 000000000..74cfaffe1 --- /dev/null +++ b/test/models/v3/operation-reply.spec.ts @@ -0,0 +1,66 @@ +import { Channel } from '../../../src/models/v3/channel'; +import { OperationReply } from '../../../src/models/v3/operation-reply'; +import { OperationReplyAddress } from '../../../src/models/v3/operation-reply-address'; + +import { assertExtensions } from './utils'; + +describe('OperationReply model', function() { + describe('.id()', function() { + it('should return id', function() { + const d = new OperationReply({}, { asyncapi: {} as any, pointer: '', id: 'reply' }); + expect(d.id()).toEqual('reply'); + }); + }); + + describe('.hasAddress()', function() { + it('should return true if address is present', function() { + const d = new OperationReply({ address: { location: 'location' } }, { asyncapi: {} as any, pointer: '' }); + expect(d.hasAddress()).toEqual(true); + }); + + it('should return false if address is not present', function() { + const d = new OperationReply({}, { asyncapi: {} as any, pointer: '' }); + expect(d.hasAddress()).toEqual(false); + }); + }); + + describe('.address()', function() { + it('should return OperationReplyAdress Model if address is present', function() { + const d = new OperationReply({ address: { location: 'location' } }, { asyncapi: {} as any, pointer: '' }); + expect(d.address()).toBeInstanceOf(OperationReplyAddress); + }); + + it('should return undefined if address is not present', function() { + const d = new OperationReply({}, { asyncapi: {} as any, pointer: '' }); + expect(d.address()).toBeUndefined(); + }); + }); + + describe('.hasChannel()', function() { + it('should return true if channel is present', function() { + const d = new OperationReply({ channel: {} }, { asyncapi: {} as any, pointer: '' }); + expect(d.hasChannel()).toEqual(true); + }); + + it('should return false if channel is not present', function() { + const d = new OperationReply({}, { asyncapi: {} as any, pointer: '' }); + expect(d.hasChannel()).toEqual(false); + }); + }); + + describe('.channel()', function() { + it('should return Channel Model if address is present', function() { + const d = new OperationReply({ channel: {} }, { asyncapi: {} as any, pointer: '' }); + expect(d.channel()).toBeInstanceOf(Channel); + }); + + it('should return undefined if address is not present', function() { + const d = new OperationReply({}, { asyncapi: {} as any, pointer: '' }); + expect(d.channel()).toBeUndefined(); + }); + }); + + describe('mixins', function() { + assertExtensions(OperationReply); + }); +}); diff --git a/test/models/v3/operation-trait.spec.ts b/test/models/v3/operation-trait.spec.ts index e5f871e1c..7c355eff3 100644 --- a/test/models/v3/operation-trait.spec.ts +++ b/test/models/v3/operation-trait.spec.ts @@ -34,17 +34,15 @@ describe('OperationTrait model', function() { describe('.security()', function() { it('should return collection of security requirements', function() { - const doc = { security: [{ requirement: [] }] }; - const d = new OperationTrait(doc); + const d = new OperationTrait({ security: [{ type: 'apiKey' }] }); 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; + const requirement = security[0].all()[0] as SecurityRequirement; expect(requirement).toBeInstanceOf(SecurityRequirement); - expect(requirement.meta().id).toEqual('requirement'); expect(requirement.scheme()).toBeInstanceOf(SecurityScheme); expect(requirement.scopes()).toEqual([]); }); diff --git a/test/models/v3/operation.spec.ts b/test/models/v3/operation.spec.ts index 6096e388d..1d8004482 100644 --- a/test/models/v3/operation.spec.ts +++ b/test/models/v3/operation.spec.ts @@ -3,6 +3,7 @@ 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 { OperationReply } from '../../../src/models/v3/operation-reply'; import { Messages } from '../../../src/models/messages'; import { Message } from '../../../src/models/v3/message'; import { Servers } from '../../../src/models/servers'; @@ -54,7 +55,8 @@ describe('Operation model', function() { 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' }); + const channel = {}; + const d = new Operation({ action: 'send', channel }, { asyncapi: { parsed: { channels: { someChannel: channel }, 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); @@ -65,7 +67,8 @@ describe('Operation model', function() { 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' }); + const channel = { servers: [production as any] }; + const d = new Operation({ action: 'send', channel }, { asyncapi: { parsed: { channels: { someChannel: channel }, 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); @@ -75,7 +78,8 @@ describe('Operation model', function() { describe('.channels()', function() { it('should return collection of channels - single channel', function() { - const d = new Operation({ action: 'send', channel: { address: 'user/signup' } }); + const channel = { address: 'user/signup' }; + const d = new Operation({ action: 'send', channel }, { asyncapi: { parsed: { channels: { someChannel: channel } } } as any, pointer: '', id: 'operation' }); expect(d.channels()).toBeInstanceOf(Channels); expect(d.channels().all()).toHaveLength(1); expect(d.channels().all()[0]).toBeInstanceOf(Channel); @@ -85,14 +89,16 @@ describe('Operation model', function() { describe('.messages()', function() { it('should return collection of messages - single message', function() { - const d = new Operation({ action: 'send', channel: { messages: { someMessage: { messageId: 'messageId' } } } }); + const channel = { messages: { someMessage: { messageId: 'messageId' } } }; + const d = new Operation({ action: 'send', channel }, { asyncapi: { parsed: { channels: { someChannel: channel } } } as any, pointer: '', id: 'operation' }); expect(d.messages()).toBeInstanceOf(Messages); expect(d.messages().all()).toHaveLength(1); expect(d.messages().all()[0]).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' } } } }); + const channel = { messages: { someMessage1: { messageId: 'messageId1' }, someMessage2: { messageId: 'messageId2' } } }; + const d = new Operation({ action: 'send', channel }, { asyncapi: { parsed: { channels: { someChannel: channel } } } as any, pointer: '', id: 'operation' }); expect(d.messages()).toBeInstanceOf(Messages); expect(d.messages().all()).toHaveLength(2); expect(d.messages().all()[0]).toBeInstanceOf(Message); @@ -100,6 +106,15 @@ describe('Operation model', function() { expect(d.messages().all()[1]).toBeInstanceOf(Message); expect(d.messages().all()[1].messageId()).toEqual('messageId2'); }); + + it('should return collection of messages - defined message on operation level', function() { + const channel = { messages: { someMessage1: { messageId: 'messageId1' }, someMessage2: { messageId: 'messageId2' } } }; + const d = new Operation({ action: 'send', channel, messages: [channel.messages.someMessage1] }, { asyncapi: { parsed: { channels: { someChannel: channel } } } as any, pointer: '', id: 'operation' }); + 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'); + }); it('should return undefined when there is no value', function() { const d = new Operation({ action: 'send', channel: {} }); @@ -108,6 +123,18 @@ describe('Operation model', function() { }); }); + describe('.reply()', function() { + it('should return OperationReply model when there is value', function() { + const d = new Operation({ action: 'send', channel: {}, reply: {} }); + expect(d.reply()).toBeInstanceOf(OperationReply); + }); + + it('should return undefined when there is no value', function() { + const d = new Operation({ action: 'send', channel: {} }); + expect(d.reply()).toBeUndefined(); + }); + }); + describe('.traits()', function() { it('should return collection of traits', function() { const d = new Operation({ action: 'send', channel: {}, traits: [{}] }); diff --git a/test/models/v3/server.spec.ts b/test/models/v3/server.spec.ts index 454927b65..57b2501db 100644 --- a/test/models/v3/server.spec.ts +++ b/test/models/v3/server.spec.ts @@ -15,10 +15,11 @@ import { serializeInput, assertCoreModel } from './utils'; import type { v3 } from '../../../src/spec-types'; const doc = { - development: { - protocol: 'mqtt', + production: { + host: 'rabbitmq.in.mycompany.com:5672', + pathname: '/production', + protocol: 'amqp', protocolVersion: '1.0.0', - url: 'development.gigantic-server.com', variables: { username: { default: 'demo', @@ -27,19 +28,53 @@ const doc = { } } }; -const docItem = new Server(doc.development, { asyncapi: {} as any, pointer: '', id: 'development' }); +const docItem = new Server(doc.production, { asyncapi: {} as any, pointer: '', id: 'production' }); 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'); + expect(docItem.id()).toEqual('production'); + }); + }); + + describe('.url()', function () { + it('should return value', function () { + expect(docItem.url()).toEqual(`${doc.production.host}${doc.production.pathname}`); + }); + }); + + describe('.host()', function () { + it('should return value', function () { + expect(docItem.host()).toEqual(doc.production.host); }); }); describe('protocol()', function () { it('should return protocol ', function () { - expect(docItem.protocol()).toMatch(doc.development.protocol); + expect(docItem.protocol()).toEqual(doc.production.protocol); + }); + }); + + describe('.hasPathname()', function () { + it('should return true if pathname is not missing', function () { + expect(docItem.hasPathname()).toEqual(true); + }); + + it('should be false when protocolVersion is missing', function () { + const docItem = new Server({ ...doc.production, pathname: undefined }, { asyncapi: {} as any, pointer: '', id: 'development' }); + expect(docItem.hasPathname()).toEqual(false); + }); + }); + + describe('.pathname()', function () { + it('should return value', function () { + expect(docItem.pathname()).toEqual(doc.production.pathname); + }); + + it('should return undefined if value is not set', function () { + const docItem = new Server({ ...doc.production, pathname: undefined }, { asyncapi: {} as any, pointer: '', id: 'development' }); + expect(docItem.pathname()).toBeUndefined(); }); }); @@ -55,7 +90,7 @@ describe('Server Model', function () { describe('.protocolVersion()', function () { it('should return value', function () { - expect(docItem.protocolVersion()).toMatch(doc.development.protocolVersion); + expect(docItem.protocolVersion()).toMatch(doc.production.protocolVersion); }); it('should return undefined when protocolVersion is missing', function () { @@ -63,12 +98,6 @@ describe('Server Model', function () { }); }); - 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({}); @@ -105,7 +134,8 @@ describe('Server Model', function () { 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' }); + const channel = {}; + const d = new Server(doc, { asyncapi: { parsed: { channels: { someChannel: channel }, 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); @@ -114,7 +144,8 @@ describe('Server Model', function () { 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' }); + const channel = {}; + const d = new Server(doc, { asyncapi: { parsed: { channels: { someChannel: channel }, 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); @@ -125,7 +156,8 @@ describe('Server Model', function () { 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' }); + const channel = { servers: [doc] }; + const d = new Server(doc, { asyncapi: { parsed: { channels: { someChannel: channel }, operations: { someOperation1: { channel }, 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); @@ -174,7 +206,7 @@ describe('Server Model', function () { describe('.security()', function() { it('should return SecurityRequirements', function() { - const doc = serializeInput({ security: [{ requirement: [] }] }); + const doc = serializeInput({ security: [{ type: 'apiKey' }] }); const d = new Server(doc, {pointer: '/servers/test'} as any); const security = d.security(); @@ -182,12 +214,11 @@ describe('Server Model', function () { expect(security).toHaveLength(1); expect(security[0]).toBeInstanceOf(SecurityRequirements); - const requirement = security[0].get('requirement') as SecurityRequirement; + const requirement = security[0].all()[0] 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'); + expect(requirement.meta().pointer).toEqual('/servers/test/security/0'); }); it('should return SecurityRequirements when value is undefined', function() {