diff --git a/package-lock.json b/package-lock.json index 81c6ec26b..eae082e37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3158,14 +3158,20 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001314", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001314.tgz", - "integrity": "sha512-0zaSO+TnCHtHJIbpLroX7nsD+vYuOVjl3uzFbJO1wMVbuveJA0RK2WcQA9ZUIOiO0/ArMiMgHJLxfEZhQiC0kw==", + "version": "1.0.30001387", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001387.tgz", + "integrity": "sha512-fKDH0F1KOJvR+mWSOvhj8lVRr/Q/mc5u5nabU2vi1/sgvlSqEsE8dOq0Hy/BqVbDkCYQPRRHB1WRjW6PGB/7PA==", "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] }, "node_modules/cardinal": { "version": "2.1.1", @@ -8203,19 +8209,6 @@ "node": ">=0.10.0" } }, - "node_modules/marked": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", - "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", - "dev": true, - "peer": true, - "bin": { - "marked": "bin/marked" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/marked-terminal": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-4.1.1.tgz", @@ -18156,9 +18149,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001314", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001314.tgz", - "integrity": "sha512-0zaSO+TnCHtHJIbpLroX7nsD+vYuOVjl3uzFbJO1wMVbuveJA0RK2WcQA9ZUIOiO0/ArMiMgHJLxfEZhQiC0kw==", + "version": "1.0.30001387", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001387.tgz", + "integrity": "sha512-fKDH0F1KOJvR+mWSOvhj8lVRr/Q/mc5u5nabU2vi1/sgvlSqEsE8dOq0Hy/BqVbDkCYQPRRHB1WRjW6PGB/7PA==", "dev": true }, "cardinal": { @@ -21973,12 +21966,6 @@ "strip-color": "^0.1.0" } }, - "marked": { - "version": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", - "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", - "dev": true, - "peer": true - }, "marked-terminal": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-4.1.1.tgz", diff --git a/src/models/base.ts b/src/models/base.ts index db9323a54..a68e5f709 100644 --- a/src/models/base.ts +++ b/src/models/base.ts @@ -1,32 +1,31 @@ -import type { Constructor } from "./utils"; +import type { Constructor, InferModelData, InferModelMetadata } from "./utils"; import type { DetailedAsyncAPI } from "../types"; export interface ModelMetadata { asyncapi: DetailedAsyncAPI; pointer: string; - [key: string]: any; } -export abstract class BaseModel = Record, M extends Record = {}> { +export abstract class BaseModel = {}> { constructor( protected readonly _json: J, - protected readonly _meta: ModelMetadata & M = {} as any, + protected readonly _meta: ModelMetadata & M = {} as ModelMetadata & M, ) {} - json = J>(): T; + json(): T; json(key: K): J[K]; json(key?: keyof J) { if (key === undefined) return this._json; - if (!this._json) return; - return this._json[String(key)]; + if (this._json === null || this._json === undefined) return this._json; + return this._json[key]; } meta(): ModelMetadata & M; - meta(key: K): (ModelMetadata & M)[K]; - meta(key?: keyof ModelMetadata & M) { + meta(key: K): (ModelMetadata & M)[K]; + meta(key?: keyof (ModelMetadata & M)) { if (key === undefined) return this._meta; if (!this._meta) return; - return this._meta[String(key)]; + return this._meta[key]; } jsonPath(field?: string | undefined): string { @@ -36,7 +35,7 @@ export abstract class BaseModel = Record(Model: Constructor, value: any, meta: { pointer: string | number, [key: string]: any }): T { + protected createModel(Model: Constructor, value: InferModelData, meta: Omit & InferModelMetadata): T { return new Model(value, { ...meta, asyncapi: this._meta.asyncapi }); } } diff --git a/src/models/binding.ts b/src/models/binding.ts index 298c1eeb7..87970a472 100644 --- a/src/models/binding.ts +++ b/src/models/binding.ts @@ -1,8 +1,8 @@ import type { BaseModel } from "./base"; import type { ExtensionsMixinInterface } from './mixins'; -export interface BindingInterface extends BaseModel, ExtensionsMixinInterface { +export interface BindingInterface = Record> extends BaseModel, ExtensionsMixinInterface { protocol(): string; version(): string; - value(): any; + value(): V; } \ No newline at end of file diff --git a/src/models/bindings.ts b/src/models/bindings.ts index 9902a9c78..84e75f5e3 100644 --- a/src/models/bindings.ts +++ b/src/models/bindings.ts @@ -1,4 +1,5 @@ import type { Collection } from './collection'; import type { BindingInterface } from './binding'; +import type { ExtensionsMixinInterface } from "./mixins"; -export interface BindingsInterface extends Collection {} \ No newline at end of file +export interface BindingsInterface extends Collection, ExtensionsMixinInterface {} \ No newline at end of file diff --git a/src/models/collection.ts b/src/models/collection.ts index efc8a344b..5010dcd24 100644 --- a/src/models/collection.ts +++ b/src/models/collection.ts @@ -1,14 +1,25 @@ import type { BaseModel } from "./base"; +import type { DetailedAsyncAPI } from "../types"; -export abstract class Collection> extends Array { +export interface CollectionMetadata { + originalData?: Record; + asyncapi?: DetailedAsyncAPI; + pointer?: string; +} + +export abstract class Collection = {}> extends Array { constructor( - protected readonly collections: T[] + protected readonly collections: T[], + protected readonly _meta: CollectionMetadata & M = {} as CollectionMetadata & M, ) { super(...collections); } abstract get(id: string): T | undefined; - abstract has(id: string): boolean; + + has(id: string): boolean { + return typeof this.get(id) !== 'undefined'; + } all(): T[] { return this.collections; @@ -21,4 +32,12 @@ export abstract class Collection> extends filterBy(filter: (item: T) => boolean): T[] { return this.collections.filter(filter); } + + meta(): CollectionMetadata & M; + meta & M)>(key: K): (CollectionMetadata & M)[K]; + meta(key?: keyof (CollectionMetadata & M)) { + if (key === undefined) return this._meta; + if (!this._meta) return; + return this._meta[String(key)]; + } } diff --git a/src/models/extension.ts b/src/models/extension.ts index 8099c45a8..e31aab2c7 100644 --- a/src/models/extension.ts +++ b/src/models/extension.ts @@ -1,7 +1,7 @@ import type { BaseModel } from "./base"; -export interface ExtensionInterface extends BaseModel { +export interface ExtensionInterface extends BaseModel { name(): string; version(): string; - value(): any; + value(): V; } diff --git a/src/models/schema.ts b/src/models/schema.ts index 81ec0008a..72c224961 100644 --- a/src/models/schema.ts +++ b/src/models/schema.ts @@ -1,7 +1,8 @@ import type { BaseModel } from "./base"; import type { ExtensionsMixinInterface, ExternalDocumentationMixinInterface } from "./mixins"; +import type { v2 } from "../spec-types"; -export interface SchemaInterface extends BaseModel, ExtensionsMixinInterface, ExternalDocumentationMixinInterface { +export interface SchemaInterface extends BaseModel, ExtensionsMixinInterface, ExternalDocumentationMixinInterface { uid(): string; $comment(): string | undefined; $id(): string | undefined; diff --git a/src/models/utils.ts b/src/models/utils.ts index e58b5ce07..73c8ed97e 100644 --- a/src/models/utils.ts +++ b/src/models/utils.ts @@ -1,39 +1,13 @@ -import type { BaseModel } from './base'; +import type { BaseModel, ModelMetadata } from './base'; +import type { DetailedAsyncAPI } from '../types'; export interface Constructor extends Function { new (...any: any[]): T; } -export interface MixinType extends Function { - prototype: T; -} - -export function Mixin(a: typeof BaseModel): typeof BaseModel; -export function Mixin(a: typeof BaseModel, b: MixinType): typeof BaseModel & Constructor; -export function Mixin(a: typeof BaseModel, b: MixinType, c: MixinType): typeof BaseModel & Constructor & Constructor; -export function Mixin(a: typeof BaseModel, b: MixinType, c: MixinType, d: MixinType): typeof BaseModel & Constructor & Constructor & Constructor; -export function Mixin(a: typeof BaseModel, b: MixinType, c: MixinType, d: MixinType, e: MixinType): typeof BaseModel & Constructor & Constructor & Constructor & Constructor; -export function Mixin(a: typeof BaseModel, b: MixinType, c: MixinType, d: MixinType, e: MixinType, f: MixinType): typeof BaseModel & Constructor & Constructor & Constructor & Constructor & Constructor; -export function Mixin(baseCtor: typeof BaseModel, ...constructors: any[]) { - return mixin(class extends baseCtor {}, constructors); -} +export type InferModelData = T extends BaseModel ? J : never; +export type InferModelMetadata = T extends BaseModel ? M : never; -function mixin(derivedCtor: any, constructors: any[]): typeof BaseModel { - constructors.forEach((ctor) => { - Object.getOwnPropertyNames(ctor.prototype).forEach((name) => { - if (name === 'constructor') { - return; - } - Object.defineProperty( - derivedCtor.prototype, - name, - Object.getOwnPropertyDescriptor(ctor.prototype, name) || Object.create(null), - ); - }); - }); - return derivedCtor; +export function createModel(Model: Constructor, value: InferModelData, meta: Omit & { asyncapi?: DetailedAsyncAPI } & InferModelMetadata, parent?: BaseModel): T { + return new Model(value, { ...meta, asyncapi: meta.asyncapi || parent?.meta().asyncapi }); } - -export function createModel(Model: Constructor, value: any, meta: { pointer: string | number, [key: string]: any }, parent: BaseModel) { - return new Model(value, { ...meta, asyncapi: parent?.meta().asyncapi }); -} \ No newline at end of file diff --git a/src/models/v2/asyncapi.ts b/src/models/v2/asyncapi.ts index 7cf366593..76bef6b27 100644 --- a/src/models/v2/asyncapi.ts +++ b/src/models/v2/asyncapi.ts @@ -82,7 +82,7 @@ export class AsyncAPIDocument extends BaseModel implements As securitySchemes(): SecuritySchemesInterface { return new SecuritySchemes( Object.entries(this._json.components?.securitySchemes || {}).map(([securitySchemeName, securityScheme]) => - this.createModel(SecurityScheme, securityScheme, { id: securitySchemeName, pointer: `/components/securitySchemes/${securitySchemeName}` }) + this.createModel(SecurityScheme, securityScheme as v2.SecuritySchemeObject, { id: securitySchemeName, pointer: `/components/securitySchemes/${securitySchemeName}` }) ) ); } diff --git a/src/models/v2/binding.ts b/src/models/v2/binding.ts index c2601bca8..b313ace2f 100644 --- a/src/models/v2/binding.ts +++ b/src/models/v2/binding.ts @@ -7,7 +7,7 @@ import type { ExtensionsInterface } from "../extensions"; import type { v2 } from "../../spec-types"; -export class Binding extends BaseModel implements BindingInterface { +export class Binding = Record> extends BaseModel implements BindingInterface { protocol(): string { return this._meta.protocol; } @@ -16,10 +16,10 @@ export class Binding extends BaseModel impleme return this._json.bindingVersion || 'latest'; } - value = Record>(): T { + value(): V { const value = { ...this._json }; delete (value as any).bindingVersion; - return value as unknown as T; + return value as unknown as V; } extensions(): ExtensionsInterface { diff --git a/src/models/v2/bindings.ts b/src/models/v2/bindings.ts index e15981e4d..7d7b4646a 100644 --- a/src/models/v2/bindings.ts +++ b/src/models/v2/bindings.ts @@ -1,14 +1,31 @@ import { Collection } from "../collection"; +import { Extensions } from "./extensions"; +import { Extension } from "./extension"; + +import { createModel } from "../utils"; +import { EXTENSION_REGEX } from '../../constants'; import type { BindingsInterface } from "../bindings"; import type { BindingInterface } from "../binding"; +import type { ExtensionsInterface } from "../extensions"; +import type { ExtensionInterface } from "../extension"; + +import type { v2 } from "../../spec-types"; export class Bindings extends Collection implements BindingsInterface { - override get(name: string): BindingInterface | undefined { + override get = Record>(name: string): BindingInterface | undefined { return this.collections.find(binding => binding.protocol() === name); - }; + } - override has(name: string): boolean { - return this.collections.some(binding => binding.protocol() === name); - }; + extensions(): ExtensionsInterface { + const extensions: ExtensionInterface[] = []; + Object.entries(this._meta.originalData as v2.SpecificationExtensions || {}).forEach(([name, value]) => { + if (EXTENSION_REGEX.test(name)) { + extensions.push( + createModel(Extension, value, { name, pointer: `${this._meta.pointer}/${name}`, asyncapi: this._meta.asyncapi }) as Extension + ); + } + }); + return new Extensions(extensions); + } } diff --git a/src/models/v2/channel-parameters.ts b/src/models/v2/channel-parameters.ts index 486f0177a..71d7cf10e 100644 --- a/src/models/v2/channel-parameters.ts +++ b/src/models/v2/channel-parameters.ts @@ -7,8 +7,4 @@ export class ChannelParameters extends Collection imp override get(id: string): ChannelParameterInterface | undefined { return this.collections.find(parameter => parameter.id() === id); } - - override has(id: string): boolean { - return this.collections.some(parameter => parameter.id() === id); - } } diff --git a/src/models/v2/channel.ts b/src/models/v2/channel.ts index 8b316f537..14a2c0eb4 100644 --- a/src/models/v2/channel.ts +++ b/src/models/v2/channel.ts @@ -16,7 +16,7 @@ import type { ExtensionsInterface } from "models/extensions"; import type { MessagesInterface } from "../messages"; import type { MessageInterface } from "../message"; import type { OperationsInterface } from "../operations"; -import type { OperationInterface } from "../operation"; +import type { OperationAction, OperationInterface } from "../operation"; import type { ServersInterface } from "../servers"; import type { ServerInterface } from "../server"; @@ -55,7 +55,7 @@ export class Channel extends BaseModel { const id = this._json[operationAction as 'publish' | 'subscribe'] && (this._json[operationAction as 'publish' | 'subscribe'] as v2.OperationObject).operationId || this.meta().id + "_" + operationAction; this._json[operationAction as 'publish' | 'subscribe'] && operations.push( - this.createModel(Operation, this._json[operationAction as 'publish' | 'subscribe'], { id, action: operationAction, pointer: `${this._meta.pointer}/${operationAction}` }), + 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); @@ -70,7 +70,7 @@ export class Channel extends BaseModel { - return this.createModel(ChannelParameter, channelParameter, { + return this.createModel(ChannelParameter, channelParameter as v2.ParameterObject, { id: channelParameterName, pointer: `${this._meta.pointer}/parameters/${channelParameterName}` }) diff --git a/src/models/v2/channels.ts b/src/models/v2/channels.ts index 9e17583b8..2f285f51e 100644 --- a/src/models/v2/channels.ts +++ b/src/models/v2/channels.ts @@ -8,10 +8,6 @@ export class Channels extends Collection implements ChannelsIn return this.collections.find(channel => channel.id() === id); } - override has(id: string): boolean { - return this.collections.some(channel => channel.id() === id); - } - filterBySend(): ChannelInterface[] { return this.filterBy(channel => channel.operations().filterBySend().length > 0); } diff --git a/src/models/v2/components.ts b/src/models/v2/components.ts index 2ebe3c5f8..6aeaeaba4 100644 --- a/src/models/v2/components.ts +++ b/src/models/v2/components.ts @@ -1,4 +1,6 @@ import { BaseModel } from "../base"; +import { Collection } from "../collection"; + import { Bindings } from "./bindings"; import { Binding } from "./binding"; import { Channel } from "./channel"; @@ -11,37 +13,38 @@ import { SecurityScheme } from "./security-scheme"; import { Server } from "./server"; import { ServerVariable } from "./server-variable"; import { extensions } from './mixins'; -import type { BindingsInterface } from "../bindings"; -import type { ComponentsInterface } from "../components"; -import type { ExtensionsInterface } from "../extensions"; -import type { Constructor } from "../utils"; -import type { ServersInterface } from "models/servers"; import { Servers } from "./servers"; import { Channels } from "./channels"; -import type { ChannelsInterface } from "models/channels"; -import type { MessagesInterface } from "models/messages"; -import type { SchemasInterface } from "models/schemas"; -import type { ChannelParametersInterface } from "models/channel-parameters"; -import type { ServerVariablesInterface } from "models/server-variables"; -import type { OperationTraitsInterface } from "models/operation-traits"; -import type { SecuritySchemesInterface } from "models/security-schemes"; 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 type { MessageTraitsInterface } from "models/message-traits"; import { SecuritySchemes } from "./security-schemes"; -import { Collection } from "models/collection"; import { CorrelationIds } from "./correlation-ids"; -import { tilde } from '../../utils'; -import type { OperationsInterface } from "models/operations"; -import type { OperationInterface } from "models/operation"; import { Operations } from "./operations"; import { Message } from "./message"; + +import { tilde } from '../../utils'; + +import type { BindingsInterface } from "../bindings"; +import type { ComponentsInterface } from "../components"; +import type { ExtensionsInterface } from "../extensions"; +import type { Constructor } from "../utils"; +import type { ServersInterface } from "../servers"; +import type { ChannelsInterface } from "../channels"; +import type { MessagesInterface } from "../messages"; +import type { SchemasInterface } from "../schemas"; +import type { ChannelParametersInterface } from "../channel-parameters"; +import type { ServerVariablesInterface } from "../server-variables"; +import type { OperationTraitsInterface } from "../operation-traits"; +import type { SecuritySchemesInterface } from "../security-schemes"; +import type { MessageTraitsInterface } from "../message-traits"; +import type { OperationsInterface } from "../operations"; +import type { OperationInterface } from "../operation"; + import type { v2 } from "../../spec-types"; -import { ComponentsObject } from "spec-types/v2"; export class Components extends BaseModel implements ComponentsInterface { servers(): ServersInterface { @@ -51,7 +54,7 @@ export class Components extends BaseModel implements Compon channels(): ChannelsInterface { return new Channels( Object.entries(this._json.channels || {}).map(([channelAddress, channel]) => - this.createModel(Channel, channel, { id: channelAddress, pointer: `/components/channels/${tilde(channelAddress)}` }) + this.createModel(Channel, channel as v2.ChannelObject, { id: channelAddress, address: '', pointer: `/components/channels/${tilde(channelAddress)}` }) ) ); } @@ -114,23 +117,26 @@ export class Components extends BaseModel implements Compon return extensions(this); } - protected createCollection, T extends BaseModel>(itemsName: keyof ComponentsObject, collectionModel: Constructor, itemModel: Constructor): M { + protected createCollection, T extends BaseModel>(itemsName: keyof v2.ComponentsObject, collectionModel: Constructor, itemModel: Constructor): M { const collectionItems: T[] = []; - Object.entries(this._json[itemsName] || {}).forEach(([itemName, item]) => { - collectionItems.push(this.createModel(itemModel, item, { id: itemName, pointer: `/components/${itemsName}/${itemName}` })) + Object.entries(this._json[itemsName] || {}).forEach(([id, item]) => { + collectionItems.push(this.createModel(itemModel, item as any, { id, pointer: `/components/${itemsName}/${id}` } as any)) }); - return new collectionModel(collectionItems); } protected createBindings(itemsName: 'serverBindings' | 'channelBindings' | 'operationBindings' | 'messageBindings'): Record { return Object.entries(this._json[itemsName] || {}).reduce((bindings, [name, item]) => { + const bindingsData = item || {}; + const asyncapi = this.meta('asyncapi'); + const pointer = `components/${itemsName}/${name}`; bindings[name] = new Bindings( - Object.entries(item as any || {}).map(([protocol, binding]) => - this.createModel(Binding, binding, { id: protocol, pointer: `components/${itemsName}/${name}/${protocol}` }) - ) + Object.entries(bindingsData).map(([protocol, binding]) => + this.createModel(Binding, binding, { protocol, pointer: `${pointer}/${protocol}` }) + ), + { originalData: bindingsData as any, asyncapi, pointer } ); return bindings; - }, {} as Record); + }, {} as Record); } } diff --git a/src/models/v2/correlation-ids.ts b/src/models/v2/correlation-ids.ts index 9d73eccb3..d0a921171 100644 --- a/src/models/v2/correlation-ids.ts +++ b/src/models/v2/correlation-ids.ts @@ -5,10 +5,6 @@ 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()["key"] === id); - } - - override has(id: string): boolean { - return this.collections.some(correlationId => correlationId.meta()["key"] === id); + return this.collections.find(correlationId => correlationId.meta('id' as any) === id); } } diff --git a/src/models/v2/extension.ts b/src/models/v2/extension.ts index bb4588349..38e974455 100644 --- a/src/models/v2/extension.ts +++ b/src/models/v2/extension.ts @@ -4,7 +4,7 @@ import type { ExtensionInterface } from "../extension"; import type { v2 } from "../../spec-types"; -export class Extension extends BaseModel implements ExtensionInterface { +export class Extension extends BaseModel, { name: string }> implements ExtensionInterface { name(): string { return this._meta.name; } @@ -13,7 +13,7 @@ export class Extension extends BaseModel(): V { + return this._json as unknown as V; } } diff --git a/src/models/v2/extensions.ts b/src/models/v2/extensions.ts index fb14599fe..fbfb13e1c 100644 --- a/src/models/v2/extensions.ts +++ b/src/models/v2/extensions.ts @@ -4,13 +4,8 @@ import type { ExtensionsInterface } from "../extensions"; import type { ExtensionInterface } from "../extension"; export class Extensions extends Collection implements ExtensionsInterface { - override get(name: string): ExtensionInterface | undefined { + override get(name: string): ExtensionInterface | undefined { name = name.startsWith('x-') ? name : `x-${name}`; return this.collections.find(ext => ext.name() === name); - }; - - override has(name: string): boolean { - name = name.startsWith('x-') ? name : `x-${name}`; - return this.collections.some(ext => ext.name() === name); - }; + } } diff --git a/src/models/v2/info.ts b/src/models/v2/info.ts index 4415d5e7c..58cd2bbb3 100644 --- a/src/models/v2/info.ts +++ b/src/models/v2/info.ts @@ -73,7 +73,7 @@ export class Info extends BaseModel implements InfoInterface { externalDocs(): ExternalDocumentationInterface | undefined { if (this.hasExternalDocs()) { - return this.createModel(ExternalDocumentation, this._meta.asyncapi.parsed.externalDocs, { pointer: `/externalDocs` }); + return this.createModel(ExternalDocumentation, this._meta.asyncapi.parsed.externalDocs as v2.ExternalDocumentationObject, { pointer: `/externalDocs` }); } return; }; diff --git a/src/models/v2/message-examples.ts b/src/models/v2/message-examples.ts index 062a19abd..fa07cfa2f 100644 --- a/src/models/v2/message-examples.ts +++ b/src/models/v2/message-examples.ts @@ -5,10 +5,6 @@ import type { MessageExampleInterface } from '../message-example'; export class MessageExamples extends Collection implements MessageExamplesInterface { override get(name: string): MessageExampleInterface | undefined { - return this.collections.find(trait => trait.name() === name); - } - - override has(name: string): boolean { - return this.collections.some(trait => trait.name() === name); + 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 2216f8802..00f6bb799 100644 --- a/src/models/v2/message-trait.ts +++ b/src/models/v2/message-trait.ts @@ -41,7 +41,7 @@ export class MessageTrait implements override get(id: string): MessageTraitInterface | undefined { return this.collections.find(trait => trait.id() === id); } - - override has(id: string): boolean { - return this.collections.some(trait => trait.id() === id); - } } diff --git a/src/models/v2/message.ts b/src/models/v2/message.ts index 25e2617d7..dfbc6f0b6 100644 --- a/src/models/v2/message.ts +++ b/src/models/v2/message.ts @@ -13,7 +13,7 @@ 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 { OperationAction, OperationInterface } from "../operation"; import type { ServersInterface } from "../servers"; import type { ServerInterface } from "../server"; import type { SchemaInterface } from "../schema"; @@ -68,7 +68,7 @@ export class Message extends MessageTrait implements MessageIn (operation.message.oneOf || []).includes(this._json) )) { operations.push( - this.createModel(Operation, operation, { pointer: `/channels/${tilde(channelAddress)}/${operationAction}`, action: operationAction }) + this.createModel(Operation, operation, { id: '', pointer: `/channels/${tilde(channelAddress)}/${operationAction}`, action: operationAction as OperationAction }) ); } }); @@ -79,7 +79,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, { pointer: `${this._meta.pointer}/traits/${index}` }) + return this.createModel(MessageTrait, trait, { id: '', pointer: `${this._meta.pointer}/traits/${index}` }) }) ); } diff --git a/src/models/v2/messages.ts b/src/models/v2/messages.ts index 65fd7dca8..591c0d7ac 100644 --- a/src/models/v2/messages.ts +++ b/src/models/v2/messages.ts @@ -4,12 +4,8 @@ import type { MessagesInterface } from '../messages'; import type { MessageInterface } from '../message'; export class Messages extends Collection implements MessagesInterface { - override get(id: string): MessageInterface | undefined { - return this.collections.find(message => message.id() === id); - } - - override has(id: string): boolean { - return this.collections.some(message => message.id() === id); + override get(name: string): MessageInterface | undefined { + return this.collections.find(message => message.id() === name); } filterBySend(): MessageInterface[] { diff --git a/src/models/v2/mixins.ts b/src/models/v2/mixins.ts index 10d083b4b..76478cca1 100644 --- a/src/models/v2/mixins.ts +++ b/src/models/v2/mixins.ts @@ -19,15 +19,17 @@ import type { TagsInterface } from "../tags"; import type { v2 } from "../../spec-types"; export function bindings(model: BaseModel<{ bindings?: Record }>): BindingsInterface { + const bindings = model.json('bindings') || {}; return new Bindings( - Object.entries(model.json('bindings') || {}).map(([protocol, binding]) => - createModel(Binding, binding, { id: protocol, pointer: model.jsonPath(`bindings/${protocol}`) }, model) - ) + Object.entries(bindings || {}).map(([protocol, binding]) => + createModel(Binding, binding, { protocol, pointer: model.jsonPath(`bindings/${protocol}`) }, model) + ), + { originalData: bindings, asyncapi: model.meta('asyncapi'), pointer: model.jsonPath('bindings') } ); } export function hasDescription(model: BaseModel<{ description?: string }>) { - return Boolean(model.json('description')); + return Boolean(description(model)); }; export function description(model: BaseModel<{ description?: string }>): string | undefined { @@ -36,10 +38,10 @@ export function description(model: BaseModel<{ description?: string }>): string export function extensions(model: BaseModel): ExtensionsInterface { const extensions: ExtensionInterface[] = []; - Object.entries(model.json()).forEach(([key, value]) => { - if (EXTENSION_REGEX.test(key)) { + Object.entries(model.json()).forEach(([name, value]: [string, any]) => { + if (EXTENSION_REGEX.test(name)) { extensions.push( - createModel(Extension, value, { id: key, pointer: model.jsonPath(key) }, model) + createModel(Extension, value, { name, pointer: model.jsonPath(name) } as any, model) as Extension ); } }); @@ -59,7 +61,7 @@ export function externalDocs(model: BaseModel): ExternalDocumentationInterface | export function tags(model: BaseModel<{ tags?: v2.TagsObject }>): TagsInterface { return new Tags( - (model.json('tags') || []).map((tag: any, idx: number) => + (model.json('tags') || []).map((tag, idx) => createModel(Tag, tag, { pointer: model.jsonPath(`tags/${idx}`) }, model) ) ); diff --git a/src/models/v2/operation-trait.ts b/src/models/v2/operation-trait.ts index cc3ab6f41..75f0c607a 100644 --- a/src/models/v2/operation-trait.ts +++ b/src/models/v2/operation-trait.ts @@ -8,7 +8,6 @@ import type { ExtensionsInterface } from "../extensions"; import type { ExternalDocumentationInterface } from "../external-docs"; import type { OperationAction } from "../operation"; import type { OperationTraitInterface } from "../operation-trait"; -import type { SecuritySchemeInterface } from "../security-scheme"; import type { TagsInterface } from "../tags"; import type { v2 } from "../../spec-types"; @@ -71,7 +70,7 @@ export class OperationTrait { const scheme = this.createModel(SecurityScheme, securitySchemes[security], { id: security, pointer: `/components/securitySchemes/${security}` }); requirements.push( - this.createModel(SecurityRequirement, scopes, { id: security, scheme: scheme, pointer: `${this.meta().pointer}/security/${index}/${security}` }) + this.createModel(SecurityRequirement, { scheme: scheme, scopes }, { id: security, pointer: `${this.meta().pointer}/security/${index}/${security}` }) ); }); return new SecurityRequirements(requirements); diff --git a/src/models/v2/operation-traits.ts b/src/models/v2/operation-traits.ts index 95d5f9e32..32cae7902 100644 --- a/src/models/v2/operation-traits.ts +++ b/src/models/v2/operation-traits.ts @@ -7,8 +7,4 @@ export class OperationTraits extends Collection impleme override get(id: string): OperationTraitInterface | undefined { return this.collections.find(trait => trait.id() === id); } - - override has(id: string): boolean { - return this.collections.some(trait => trait.id() === id); - } } diff --git a/src/models/v2/operation.ts b/src/models/v2/operation.ts index f0a9c43d5..267bb1962 100644 --- a/src/models/v2/operation.ts +++ b/src/models/v2/operation.ts @@ -59,7 +59,7 @@ export class Operation extends OperationTrait implements Ope return new Messages( messages.map((message: any, index: number) => { - return this.createModel(Message, message, { pointer: `${this._meta.pointer}/message${isOneOf ? `/oneOf/${index}` : ''}` }) + return this.createModel(Message, message, { id: '', pointer: `${this._meta.pointer}/message${isOneOf ? `/oneOf/${index}` : ''}` }) }) ); } @@ -67,7 +67,7 @@ export class Operation extends OperationTrait implements Ope traits(): OperationTraitsInterface { return new OperationTraits( (this._json.traits || []).map((trait: any, index: number) => { - return this.createModel(OperationTrait, trait, { pointer: `${this._meta.pointer}/traits/${index}` }) + return this.createModel(OperationTrait, trait, { id: '', pointer: `${this._meta.pointer}/traits/${index}`, action: '' as 'publish' | 'subscribe' }) }) ); } diff --git a/src/models/v2/operations.ts b/src/models/v2/operations.ts index 5642fb2f1..f61b8c11c 100644 --- a/src/models/v2/operations.ts +++ b/src/models/v2/operations.ts @@ -8,10 +8,6 @@ export class Operations extends Collection implements Operat return this.collections.find(operation => operation.id() === id); } - override has(id: string): boolean { - return this.collections.some(operation => operation.id() === id); - } - filterBySend(): OperationInterface[] { return this.filterBy(operation => operation.isSend()); } diff --git a/src/models/v2/schema.ts b/src/models/v2/schema.ts index 258ae0ebc..98c837896 100644 --- a/src/models/v2/schema.ts +++ b/src/models/v2/schema.ts @@ -8,68 +8,79 @@ import type { SchemaInterface } from "../schema"; import type { v2 } from "../../spec-types"; -export class Schema extends BaseModel implements SchemaInterface { +export class Schema extends BaseModel implements SchemaInterface { uid(): string { - return this._meta.id; + return this._meta.id as any; } $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.contains !== 'object') return; + 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.definitions !== 'object') return 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; @@ -77,10 +88,12 @@ export class Schema extends BaseModel> | 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 }); @@ -89,35 +102,42 @@ export class Schema extends BaseModel | 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; } @@ -126,7 +146,7 @@ export class Schema extends BaseModel | undefined { - if (typeof this._json.items !== 'object') return; + 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 })); } @@ -148,57 +168,68 @@ export class Schema extends BaseModel | 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.patternProperties !== 'object') return 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; @@ -206,7 +237,7 @@ export class Schema extends BaseModel | undefined { - if (typeof this._json.properties !== 'object') return 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; @@ -214,53 +245,59 @@ export class Schema extends BaseModel | undefined { + if (typeof this._json === 'boolean') return; return this._json.required; } then(): SchemaInterface | undefined { - if (typeof this._json.then !== 'object') return; + 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); + return hasExternalDocs(this as BaseModel); } externalDocs(): ExternalDocumentationInterface | undefined { - return externalDocs(this); + return externalDocs(this as BaseModel); } extensions(): ExtensionsInterface { - return extensions(this); + return extensions(this as BaseModel); } } diff --git a/src/models/v2/schemas.ts b/src/models/v2/schemas.ts index dea892a87..162fd44bd 100644 --- a/src/models/v2/schemas.ts +++ b/src/models/v2/schemas.ts @@ -7,8 +7,4 @@ export class Schemas extends Collection implements SchemasInter override get(id: string): SchemaInterface | undefined { return this.collections.find(schema => schema.uid() === id); } - - override has(id: string): boolean { - return this.collections.some(schema => schema.uid() === id); - } } diff --git a/src/models/v2/security-requirement.ts b/src/models/v2/security-requirement.ts index 4a47a97d8..fdab68df1 100644 --- a/src/models/v2/security-requirement.ts +++ b/src/models/v2/security-requirement.ts @@ -1,15 +1,14 @@ import { BaseModel } from '../base'; + import type { SecuritySchemeInterface } from '../security-scheme'; -import type { v2 } from "../../spec-types"; -import { SecurityRequirementInterface } from 'models/security-requirement'; -import { SecurityScheme } from './security-scheme'; +import type { SecurityRequirementInterface } from '../security-requirement'; -export class SecurityRequirement extends BaseModel<{}, { id: string, scheme: SecuritySchemeInterface }> implements SecurityRequirementInterface { +export class SecurityRequirement extends BaseModel<{ scopes?: string[], scheme: SecuritySchemeInterface }, { id?: string }> implements SecurityRequirementInterface { scheme(): SecuritySchemeInterface { - return this.meta().scheme; + return this._json.scheme; } - scopes() : string[] { - return this._json as string[]; + scopes(): string[] { + return this._json.scopes || []; } } \ No newline at end of file diff --git a/src/models/v2/security-requirements.ts b/src/models/v2/security-requirements.ts index ee1ce0869..5fcf7dd30 100644 --- a/src/models/v2/security-requirements.ts +++ b/src/models/v2/security-requirements.ts @@ -5,10 +5,6 @@ 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 === id); - } - - override has(id: string): boolean { - return this.collections.some(securityRequirement => securityRequirement.meta().id === id); + 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 index 985a2d941..93ca01e1b 100644 --- a/src/models/v2/security-schemes.ts +++ b/src/models/v2/security-schemes.ts @@ -7,8 +7,4 @@ export class SecuritySchemes extends Collection impleme override get(id: string): SecuritySchemeInterface | undefined { return this.collections.find(securityScheme => securityScheme.id() === id); } - - override has(id: string): boolean { - return this.collections.some(securityScheme => securityScheme.id() === id); - } } diff --git a/src/models/v2/server-variables.ts b/src/models/v2/server-variables.ts index 9edc795d5..5faf082db 100644 --- a/src/models/v2/server-variables.ts +++ b/src/models/v2/server-variables.ts @@ -5,10 +5,6 @@ import type { ServerVariableInterface } from '../server-variable'; export class ServerVariables extends Collection implements ServerVariablesInterface { override get(id: string): ServerVariableInterface | undefined { - return this.collections.find(serverVariable => serverVariable.id() === id); - } - - override has(id: string): boolean { - return this.collections.some(serverVariable => serverVariable.id() === id); + return this.collections.find(variable => variable.id() === id); } } diff --git a/src/models/v2/server.ts b/src/models/v2/server.ts index 075fb89c0..15d4d6ab3 100644 --- a/src/models/v2/server.ts +++ b/src/models/v2/server.ts @@ -98,7 +98,7 @@ export class Server extends BaseModel implement Object.entries(requirement).forEach(([security, scopes]) => { const scheme = this.createModel(SecurityScheme, securitySchemes[security], { id: security, pointer: `/components/securitySchemes/${security}` }); requirements.push( - this.createModel(SecurityRequirement, scopes, { id: security, scheme: scheme, pointer: `${this.meta().pointer}/security/${index}/${security}` }) + this.createModel(SecurityRequirement, { scheme: scheme, scopes }, { id: security, pointer: `${this.meta().pointer}/security/${index}/${security}` }) ); }); return new SecurityRequirements(requirements); diff --git a/src/models/v2/servers.ts b/src/models/v2/servers.ts index 0f805137e..3c6ce0f4a 100644 --- a/src/models/v2/servers.ts +++ b/src/models/v2/servers.ts @@ -7,10 +7,6 @@ export class Servers extends Collection implements ServersInter return this.collections.find(server => server.id() === id); } - override has(id: string): boolean { - return this.collections.some(server => server.id() === id); - } - filterBySend(): ServerInterface[] { return this.filterBy(server => server.operations().filterBySend().length > 0); } diff --git a/src/models/v2/tags.ts b/src/models/v2/tags.ts index 133845b64..be1dd86d5 100644 --- a/src/models/v2/tags.ts +++ b/src/models/v2/tags.ts @@ -6,9 +6,5 @@ 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); - }; - - override has(name: string): boolean { - return this.collections.some(tag => tag.name() === name); - }; + } } diff --git a/src/schema-parser/asyncapi-schema-parser.ts b/src/schema-parser/asyncapi-schema-parser.ts index 60deeae78..43d360656 100644 --- a/src/schema-parser/asyncapi-schema-parser.ts +++ b/src/schema-parser/asyncapi-schema-parser.ts @@ -7,6 +7,7 @@ import { specVersions } from '../constants'; import type { ErrorObject, ValidateFunction } from "ajv"; import type { AsyncAPISchema, SchemaValidateResult } from '../types'; import type { SchemaParser, ParseSchemaInput, ValidateSchemaInput } from "../schema-parser"; +import type { v2 } from "../spec-types"; const ajv = new Ajv({ allErrors: true, @@ -81,7 +82,7 @@ function getSchemaValidator(version: string): ValidateFunction { * To validate the schema of the payload we just need a small portion of official AsyncAPI spec JSON Schema, the Schema Object in particular. The definition of Schema Object must be * included in the returned JSON Schema. */ -function preparePayloadSchema(asyncapiSchema: AsyncAPISchema, version: string): AsyncAPISchema { +function preparePayloadSchema(asyncapiSchema: v2.AsyncAPISchemaDefinition, version: string): v2.AsyncAPISchemaDefinition { const payloadSchema = `http://asyncapi.com/definitions/${version}/schema.json`; const definitions = asyncapiSchema.definitions; if (definitions === undefined) { diff --git a/src/schema-parser/avro-schema-parser.ts b/src/schema-parser/avro-schema-parser.ts index 1482f9457..67a687f01 100644 --- a/src/schema-parser/avro-schema-parser.ts +++ b/src/schema-parser/avro-schema-parser.ts @@ -5,6 +5,8 @@ import type { Schema } from "avsc"; import type { SchemaParser, ParseSchemaInput, ValidateSchemaInput } from "../schema-parser"; import type { AsyncAPISchema, SchemaValidateResult } from '../types'; +import type { v2 } from '../spec-types'; + type AvroSchema = Schema & { [key: string]: any } & any; export function AvroSchemaParser(): SchemaParser { @@ -65,7 +67,7 @@ const INT_MAX = Math.pow(2, 31) - 1; const LONG_MIN = Math.pow(-2, 63); const LONG_MAX = Math.pow(2, 63) - 1; -const typeMappings: { [key: string]: JSONSchema7TypeName } = { +const typeMappings: Record = { null: 'null', boolean: 'boolean', int: 'integer', @@ -82,7 +84,7 @@ const typeMappings: { [key: string]: JSONSchema7TypeName } = { uuid: 'string', }; -function commonAttributesMapping(avroDefinition: AvroSchema, jsonSchema: AsyncAPISchema, isTopLevel: boolean): void { +function commonAttributesMapping(avroDefinition: AvroSchema, jsonSchema: v2.AsyncAPISchemaDefinition, isTopLevel: boolean): void { if (avroDefinition.doc) jsonSchema.description = avroDefinition.doc; if (avroDefinition.default !== undefined) jsonSchema.default = avroDefinition.default; @@ -112,7 +114,7 @@ function getFullyQualifiedName(avroDefinition: AvroSchema) { * @param parentJsonSchema the parent json schema which contains the required property to enrich * @param haveDefaultValue we assure that a required field does not have a default value */ -function requiredAttributesMapping(fieldDefinition: any, parentJsonSchema: AsyncAPISchema, haveDefaultValue: boolean): void { +function requiredAttributesMapping(fieldDefinition: any, parentJsonSchema: v2.AsyncAPISchemaDefinition, haveDefaultValue: boolean): void { const isUnionWithNull = Array.isArray(fieldDefinition.type) && fieldDefinition.type.includes('null'); // we assume that a union type without null and a field without default value is required @@ -122,7 +124,7 @@ function requiredAttributesMapping(fieldDefinition: any, parentJsonSchema: Async } }; -function extractNonNullableTypeIfNeeded(typeInput: any, jsonSchemaInput: AsyncAPISchema): { type: string, jsonSchema: AsyncAPISchema } { +function extractNonNullableTypeIfNeeded(typeInput: any, jsonSchemaInput: v2.AsyncAPISchemaDefinition): { type: string, jsonSchema: v2.AsyncAPISchemaDefinition } { let type = typeInput; let jsonSchema = jsonSchemaInput; // Map example to first non-null type @@ -130,13 +132,13 @@ function extractNonNullableTypeIfNeeded(typeInput: any, jsonSchemaInput: AsyncAP const pickSecondType = typeInput.length > 1 && typeInput[0] === 'null'; type = typeInput[+pickSecondType]; if (jsonSchema.oneOf !== undefined) { - jsonSchema = jsonSchema.oneOf[0] as AsyncAPISchema; + jsonSchema = jsonSchema.oneOf[0] as v2.AsyncAPISchemaDefinition; } } return { type, jsonSchema }; } -function exampleAttributeMapping(type: any, example: any, jsonSchema: AsyncAPISchema): void { +function exampleAttributeMapping(type: any, example: any, jsonSchema: v2.AsyncAPISchemaDefinition): void { if (example === undefined || jsonSchema.examples || Array.isArray(type)) return; switch (type) { @@ -151,7 +153,7 @@ function exampleAttributeMapping(type: any, example: any, jsonSchema: AsyncAPISc } }; -function additionalAttributesMapping(typeInput: any, avroDefinition: AvroSchema, jsonSchemaInput: AsyncAPISchema): void { +function additionalAttributesMapping(typeInput: any, avroDefinition: AvroSchema, jsonSchemaInput: v2.AsyncAPISchemaDefinition): void { const __ret = extractNonNullableTypeIfNeeded(typeInput, jsonSchemaInput); const type = __ret.type; const jsonSchema = __ret.jsonSchema; @@ -211,8 +213,8 @@ function cacheAvroRecordDef(cache: {[key:string]: AsyncAPISchema}, key: string, } } -async function convertAvroToJsonSchema(avroDefinition: AvroSchema , isTopLevel: boolean, recordCache: Map | any = {}): Promise { - const jsonSchema: AsyncAPISchema = {}; +async function convertAvroToJsonSchema(avroDefinition: AvroSchema , isTopLevel: boolean, recordCache: Map | any = {}): Promise { + const jsonSchema: v2.AsyncAPISchemaDefinition = {}; const isUnion = Array.isArray(avroDefinition); if (isUnion) { @@ -275,7 +277,7 @@ async function convertAvroToJsonSchema(avroDefinition: AvroSchema , isTopLevel: * @param jsonSchema the schema for the record. * @returns {Promise>} */ -async function processRecordSchema(avroDefinition: AvroSchema, recordCache: {[key:string]: AsyncAPISchema}, jsonSchema: AsyncAPISchema): Promise> { +async function processRecordSchema(avroDefinition: AvroSchema, recordCache: Record, jsonSchema: v2.AsyncAPISchemaDefinition): Promise> { const propsMap = new Map(); for (const field of avroDefinition.fields) { // If the type is a sub schema it will have been stored in the cache. @@ -307,7 +309,7 @@ async function processRecordSchema(avroDefinition: AvroSchema, recordCache: {[ke * @param recordCache the cache of previously processed record types * @returns {Promise} the mutated jsonSchema that was provided to the function */ -async function processUnionSchema(jsonSchema: AsyncAPISchema, avroDefinition: AvroSchema, isTopLevel: boolean, recordCache: {[key:string]: AsyncAPISchema}): Promise { +async function processUnionSchema(jsonSchema: v2.AsyncAPISchemaDefinition, avroDefinition: AvroSchema, isTopLevel: boolean, recordCache: Record): Promise { jsonSchema.oneOf = []; let nullDef = null; for (const avroDef of avroDefinition) { diff --git a/src/schema-parser/spectral-rule-v2.ts b/src/schema-parser/spectral-rule-v2.ts index 5b514b989..a22d0a157 100644 --- a/src/schema-parser/spectral-rule-v2.ts +++ b/src/schema-parser/spectral-rule-v2.ts @@ -85,7 +85,7 @@ function rulesetFunction(parser: Parser) { } else { return [ { - message: `Error thrown during schema validation: name: ${err.name}, message:, ${err.message}, stack: ${err.stack}`, + message: `Error thrown during schema validation, name: ${err.name}, message: ${err.message}, stack: ${err.stack}`, path, } ] as SchemaValidateResult[]; diff --git a/src/spec-types/v2.ts b/src/spec-types/v2.ts index a5dfbdeb2..63284a05d 100644 --- a/src/spec-types/v2.ts +++ b/src/spec-types/v2.ts @@ -1,4 +1,4 @@ -import type { JSONSchema7, JSONSchema7Type } from "json-schema"; +import type { JSONSchema7Version, JSONSchema7TypeName, JSONSchema7Type } from "json-schema"; export type AsyncAPIVersion = string; export type Identifier = string; @@ -225,11 +225,76 @@ export interface ComponentsObject extends SpecificationExtensions { export type SchemaObject = AsyncAPISchemaObject | ReferenceObject; -export interface AsyncAPISchemaObject extends JSONSchema7, SpecificationExtensions { +export type AsyncAPISchemaObject = AsyncAPISchemaDefinition | boolean; +export interface AsyncAPISchemaDefinition extends SpecificationExtensions { + $id?: string; + $schema?: JSONSchema7Version; + $comment?: string; + + type?: JSONSchema7TypeName | JSONSchema7TypeName[]; + enum?: JSONSchema7Type[]; + const?: JSONSchema7Type; + + multipleOf?: number; + maximum?: number; + exclusiveMaximum?: number; + minimum?: number; + exclusiveMinimum?: number; + + maxLength?: number; + minLength?: number; + pattern?: string; + + items?: AsyncAPISchemaObject | AsyncAPISchemaObject[]; + additionalItems?: AsyncAPISchemaObject; + maxItems?: number; + minItems?: number; + uniqueItems?: boolean; + contains?: AsyncAPISchemaObject; + + maxProperties?: number; + minProperties?: number; + required?: string[]; + properties?: { + [key: string]: AsyncAPISchemaObject; + }; + patternProperties?: { + [key: string]: AsyncAPISchemaObject; + }; + additionalProperties?: AsyncAPISchemaObject; + dependencies?: { + [key: string]: AsyncAPISchemaObject | string[]; + }; + propertyNames?: AsyncAPISchemaObject; + + if?: AsyncAPISchemaObject; + then?: AsyncAPISchemaObject; + else?: AsyncAPISchemaObject; + + allOf?: AsyncAPISchemaObject[]; + anyOf?: AsyncAPISchemaObject[]; + oneOf?: AsyncAPISchemaObject[]; + not?: AsyncAPISchemaObject; + + format?: string; + + contentMediaType?: string; + contentEncoding?: string; + + definitions?: { + [key: string]: AsyncAPISchemaObject; + }; + + title?: string; + description?: string; + default?: JSONSchema7Type; + readOnly?: boolean; + writeOnly?: boolean; + examples?: Array | undefined; + discriminator?: string; externalDocs?: ExternalDocumentationObject; deprecated?: boolean; - examples?: Array | undefined; [keyword: string]: any; } @@ -366,15 +431,14 @@ export interface CorrelationIDObject extends SpecificationExtensions { export interface Binding { bindingVersion?: string; - [key: string]: any; } export interface SpecificationExtensions { [extension: `x-${string}`]: SpecificationExtension; } -export type SpecificationExtension = any; +export type SpecificationExtension = T; export interface ReferenceObject { - '$ref': string; + $ref: string; } \ No newline at end of file diff --git a/test/models/collection.spec.ts b/test/models/collection.spec.ts index 8064ff6a1..5089c1fa4 100644 --- a/test/models/collection.spec.ts +++ b/test/models/collection.spec.ts @@ -11,11 +11,7 @@ describe('Collection model', function() { class Model extends Collection { override get(name: string): ItemModel | undefined { return this.collections.find(item => item.name() === name); - }; - - override has(name: string): boolean { - return this.collections.some(item => item.name() === name); - }; + } }; describe('.isEmpty()', function() { diff --git a/test/models/v2/bindings.spec.ts b/test/models/v2/bindings.spec.ts new file mode 100644 index 000000000..7b621051e --- /dev/null +++ b/test/models/v2/bindings.spec.ts @@ -0,0 +1,64 @@ +import { Bindings } from '../../../src/models/v2/bindings'; +import { Binding } from '../../../src/models/v2/binding'; +import { Extensions } from '../../../src/models/v2/extensions'; +import { Extension } from '../../../src/models/v2/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/v2/components.spec.ts b/test/models/v2/components.spec.ts index 760f01130..f1e079f89 100644 --- a/test/models/v2/components.spec.ts +++ b/test/models/v2/components.spec.ts @@ -55,7 +55,7 @@ describe('Components model', function() { const items = d.channels(); expect(items).toBeInstanceOf(Channels); expect(items.all()).toEqual([ - new Channel(doc.channels?.channel as ChannelObject, {id: "channel", pointer: "/components/channels/channel"} as ModelMetadata & { id: string, address: string } | undefined) + new Channel(doc.channels?.channel as ChannelObject, {id: "channel", address: '', pointer: "/components/channels/channel"} as ModelMetadata & { id: string, address: string } | undefined) ]); }); diff --git a/test/models/v2/operation-trait.spec.ts b/test/models/v2/operation-trait.spec.ts index d266bd820..f0e673251 100644 --- a/test/models/v2/operation-trait.spec.ts +++ b/test/models/v2/operation-trait.spec.ts @@ -88,6 +88,7 @@ describe('OperationTrait model', function() { 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([]); }); diff --git a/test/models/v2/schema.spec.ts b/test/models/v2/schema.spec.ts index cf1eac221..bf48dc73e 100644 --- a/test/models/v2/schema.spec.ts +++ b/test/models/v2/schema.spec.ts @@ -8,7 +8,7 @@ describe('Channel 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', parent: null }); + const d = new Schema(doc, { asyncapi: {} as any, pointer: '', id: 'schema' }); expect(d.uid()).toEqual('schema'); }); }); diff --git a/test/models/v2/security-requirement.spec.ts b/test/models/v2/security-requirement.spec.ts index f153ecc82..31a8ad791 100644 --- a/test/models/v2/security-requirement.spec.ts +++ b/test/models/v2/security-requirement.spec.ts @@ -1,15 +1,13 @@ -import { ModelMetadata } from '../../../src/models/base'; -import { SecuritySchemeInterface } from '../../../src/models/security-scheme'; import { SecurityRequirement } from '../../../src/models/v2/security-requirement' import { SecurityScheme } from '../../../src/models/v2/security-scheme'; describe('SecurityRequirement model', function() { describe('.scheme()', function() { it('should return scheme', function() { - const doc = {}; - const expectedScheme = new SecurityScheme({type: "oauth2"}, {id: "test"} as any); - const d = new SecurityRequirement(doc, ({ id: "test", scheme: expectedScheme } as any)); // TODO Pointer + const expectedScheme = new SecurityScheme({ type: "oauth2" }); + const d = new SecurityRequirement({ scheme: expectedScheme }, { id: 'test' } as any); + expect(d.meta('id')).toEqual('test'); expect(d.scheme()).toBeInstanceOf(SecurityScheme); expect(d.scheme()).toEqual(expectedScheme); }); @@ -17,10 +15,16 @@ describe('SecurityRequirement model', function() { }) describe('.scopes()', function() { it('should return scopes', function() { - const doc = ["scope_one"]; - const d = new SecurityRequirement(doc); // TODO Pointer + const scopes = ["scope_one"]; + const scheme = new SecurityScheme({ type: "oauth2" }); + const d = new SecurityRequirement({ scheme, scopes }) + expect(d.scopes()).toEqual(scopes); + }); - expect(d.scopes()).toEqual(doc); + it('should return empty array when scopes are omit', function() { + const scheme = new SecurityScheme({ type: "oauth2" }); + const d = new SecurityRequirement({ scheme }) + expect(d.scopes()).toEqual([]); }); }); }); diff --git a/test/models/v2/security-requirements.spec.ts b/test/models/v2/security-requirements.spec.ts index 258b4bb4f..7dd983274 100644 --- a/test/models/v2/security-requirements.spec.ts +++ b/test/models/v2/security-requirements.spec.ts @@ -1,13 +1,7 @@ import { SecurityRequirements } from '../../../src/models/v2/security-requirements'; -import { Operation } from '../../../src/models/v2/operation'; import { SecurityRequirement } from '../../../src/models/v2/security-requirement'; -const operation = { - operationId: 'test', -}; -const operationItem = new Operation(operation, { asyncapi: {} as any, pointer: '', id: 'test', action: 'publish' }); - -const requirementItem = new SecurityRequirement({}, {id: "test"} as any); +const requirementItem = new SecurityRequirement({} as any, { id: 'test' } as any); describe('SecurityRequirements model', function () { describe('.isEmpty()', function () {