Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: remove mixins and add AsyncAPI TS types #581

Merged
merged 8 commits into from
Aug 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/custom-operations/apply-traits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { JSONPath } from 'jsonpath-plus';
import { xParserOriginalTraits } from '../constants';
import { mergePatch } from '../utils';

import type { v2 } from "../spec-types";

const v2TraitPaths = [
// operations
'$.channels.*.[publish,subscribe]',
Expand All @@ -15,7 +17,7 @@ const v2TraitPaths = [
'$.components.messages.*',
];

export function applyTraitsV2(asyncapi: Record<string, unknown>) {
export function applyTraitsV2(asyncapi: v2.AsyncAPIObject) {
applyAllTraits(asyncapi, v2TraitPaths);
}

Expand All @@ -31,11 +33,11 @@ const v3TraitPaths = [
'$.components.messages.*',
];

export function applyTraitsV3(asyncapi: Record<string, unknown>) {
export function applyTraitsV3(asyncapi: v2.AsyncAPIObject) { // TODO: Change type when we will have implemented types for v3
applyAllTraits(asyncapi, v3TraitPaths);
}

function applyAllTraits(asyncapi: Record<string, unknown>, paths: string[]) {
function applyAllTraits(asyncapi: Record<string, any>, paths: string[]) {
paths.forEach(path => {
JSONPath({
path,
Expand Down
4 changes: 3 additions & 1 deletion src/models/asyncapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import type { SecuritySchemesInterface } from "./security-schemes";
import type { ServersInterface } from "./servers";
import type { DetailedAsyncAPI } from "../types";

export interface AsyncAPIDocumentInterface extends BaseModel, ExtensionsMixinInterface {
import type { v2 } from "../spec-types";

export interface AsyncAPIDocumentInterface extends BaseModel<v2.AsyncAPIObject>, ExtensionsMixinInterface {
version(): string;
defaultContentType(): string | undefined;
hasDefaultContentType(): boolean;
Expand Down
30 changes: 17 additions & 13 deletions src/models/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,36 @@ export interface ModelMetadata {
[key: string]: any;
}

export abstract class BaseModel {
export abstract class BaseModel<J extends Record<string, any> = Record<string, any>, M extends Record<string, any> = {}> {
constructor(
protected readonly _json: Record<string, any>,
protected readonly _meta: ModelMetadata = {} as any,
protected readonly _json: J,
protected readonly _meta: ModelMetadata & M = {} as any,
) {}

json<T = Record<string, any>>(): T;
json<T = any>(key: string | number): T;
json(key?: string | number) {
json<T extends Record<string, any> = J>(): T;
json<K extends keyof J>(key: K): J[K];
json(key?: keyof J) {
if (key === undefined) return this._json;
if (!this._json) return;
return this._json[String(key)];
}

meta(): ModelMetadata {
return this._meta!;
meta(): ModelMetadata & M;
meta<K extends keyof ModelMetadata & M>(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)];
}

jsonPath(field?: string): string | undefined {
jsonPath(field?: string | undefined): string {
if (typeof field !== 'string') {
return this._meta?.pointer;
return this._meta.pointer;
}
return `${this._meta?.pointer}/${field}`;
return `${this._meta.pointer}/${field}`;
}

protected createModel<T extends BaseModel>(Model: Constructor<T>, value: any, { pointer, ...rest }: { pointer: string | number, [key: string]: any }): T {
return new Model(value, { ...rest, asyncapi: this._meta.asyncapi, pointer });
protected createModel<T extends BaseModel>(Model: Constructor<T>, value: any, meta: { pointer: string | number, [key: string]: any }): T {
return new Model(value, { ...meta, asyncapi: this._meta.asyncapi });
}
}
4 changes: 2 additions & 2 deletions src/models/message-example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import type { ExtensionsMixinInterface } from './mixins';

export interface MessageExampleInterface extends BaseModel, ExtensionsMixinInterface {
hasName(): boolean;
name(): string;
name(): string | undefined;
hasSummary(): boolean;
summary(): string;
summary(): string | undefined;
hasHeaders(): boolean;
headers(): Record<string, any> | undefined;
hasPayload(): boolean;
Expand Down
2 changes: 1 addition & 1 deletion src/models/oauth-flow.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BaseModel } from './base';
import { ExtensionsMixinInterface } from './mixins';

export interface OAuthFlowInterface extends BaseModel, ExtensionsMixinInterface {
export interface OAuthFlowInterface<J extends Record<string, any> = Record<string, any>> extends BaseModel<J>, ExtensionsMixinInterface {
authorizationUrl(): string | undefined;
hasRefreshUrl(): boolean;
refreshUrl(): string | undefined;
Expand Down
3 changes: 1 addition & 2 deletions src/models/security-scheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ export interface SecuritySchemeInterface extends BaseModel, DescriptionMixinInte
id(): string
hasBearerFormat(): boolean;
bearerFormat(): string | undefined;
openIdConnectUrl(): string;
openIdConnectUrl(): string | undefined;
scheme(): string | undefined;
flows(): OAuthFlowsInterface | undefined;
scopes(): string[];
type(): SecuritySchemaType;
in(): string | undefined;
}
2 changes: 1 addition & 1 deletion src/models/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface ServerInterface extends BaseModel, DescriptionMixinInterface, B
id(): string
url(): string;
protocol(): string;
protocolVersion(): string;
protocolVersion(): string | undefined;
hasProtocolVersion(): boolean;
channels(): ChannelsInterface;
operations(): OperationsInterface;
Expand Down
4 changes: 4 additions & 0 deletions src/models/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ function mixin(derivedCtor: any, constructors: any[]): typeof BaseModel {
});
return derivedCtor;
}

export function createModel<T>(Model: Constructor<T>, value: any, meta: { pointer: string | number, [key: string]: any }, parent: BaseModel) {
return new Model(value, { ...meta, asyncapi: parent?.meta().asyncapi });
}
12 changes: 9 additions & 3 deletions src/models/v2/asyncapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import { SecuritySchemes } from "./security-schemes";
import { SecurityScheme } from "./security-scheme";
import { Schemas } from "./schemas";

import { Mixin } from '../utils';
import { ExtensionsMixin } from './mixins/extensions';
import { extensions } from './mixins';

import { tilde } from '../../utils';

Expand All @@ -27,8 +26,11 @@ import type { MessagesInterface } from "../messages";
import type { MessageInterface } from "../message";
import type { SchemasInterface } from "../schemas";
import type { SecuritySchemesInterface } from "../security-schemes";
import type { ExtensionsInterface } from "../extensions";

export class AsyncAPIDocument extends Mixin(BaseModel, ExtensionsMixin) implements AsyncAPIDocumentInterface {
import type { v2 } from "../../spec-types";

export class AsyncAPIDocument extends BaseModel<v2.AsyncAPIObject> implements AsyncAPIDocumentInterface {
version(): string {
return this._json.asyncapi;
}
Expand Down Expand Up @@ -88,4 +90,8 @@ export class AsyncAPIDocument extends Mixin(BaseModel, ExtensionsMixin) implemen
components(): ComponentsInterface {
return new Components(this._json.components || {});
}

extensions(): ExtensionsInterface {
return extensions(this);
}
}
28 changes: 28 additions & 0 deletions src/models/v2/binding.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { BaseModel } from "../base";

import { extensions } from "./mixins";

import type { BindingInterface } from "../binding";
import type { ExtensionsInterface } from "../extensions";

import type { v2 } from "../../spec-types";

export class Binding extends BaseModel<v2.Binding, { protocol: string }> implements BindingInterface {
protocol(): string {
return this._meta.protocol;
}

version(): string {
return this._json.bindingVersion || 'latest';
}

value<T extends Record<string, any> = Record<string, any>>(): T {
const value = { ...this._json };
delete (value as any).bindingVersion;
return value as unknown as T;
}

extensions(): ExtensionsInterface {
return extensions(this);
}
}
14 changes: 14 additions & 0 deletions src/models/v2/bindings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Collection } from "../collection";

import type { BindingsInterface } from "../bindings";
import type { BindingInterface } from "../binding";

export class Bindings extends Collection<BindingInterface> implements BindingsInterface {
override get(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);
};
}
27 changes: 16 additions & 11 deletions src/models/v2/channel-parameter.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
import { BaseModel } from "../base";
import { Schema } from "./schema";

import { Mixin } from '../utils';
import { DescriptionMixin } from './mixins/description';
import { ExtensionsMixin } from './mixins/extensions';
import { hasDescription, description, extensions } from './mixins';

import type { ModelMetadata } from "../base";
import type { ChannelParameterInterface } from "../channel-parameter";
import type { SchemaInterface } from "../schema";
import type { ExtensionsInterface } from "../extensions";

export class ChannelParameter extends Mixin(BaseModel, DescriptionMixin, ExtensionsMixin) implements ChannelParameterInterface {
constructor(
_json: Record<string,any>,
protected readonly _meta: ModelMetadata & { id: string } = {} as any
) {
super(_json, _meta);
}
import type { v2 } from "../../spec-types";

export class ChannelParameter extends BaseModel<v2.ParameterObject, { id: string }> implements ChannelParameterInterface {
id(): string {
return this._meta.id;
}
Expand All @@ -37,4 +30,16 @@ export class ChannelParameter extends Mixin(BaseModel, DescriptionMixin, Extensi
location(): string | undefined {
return this._json.location;
}

hasDescription(): boolean {
return hasDescription(this);
}

description(): string | undefined {
return description(this);
}

extensions(): ExtensionsInterface {
return extensions(this);
}
}
37 changes: 23 additions & 14 deletions src/models/v2/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,22 @@ import { Operation } from './operation';
import { Servers } from './servers';
import { Server } from './server';

import { Mixin } from '../utils';
import { BindingsMixin } from './mixins/bindings';
import { DescriptionMixin } from './mixins/description';
import { ExtensionsMixin } from './mixins/extensions';
import { bindings, hasDescription, description, extensions } from './mixins';

import type { ModelMetadata } from "../base";
import type { BindingsInterface } from "models/bindings";
import type { ChannelInterface } from "../channel";
import type { ChannelParametersInterface } from "../channel-parameters";
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 { ServersInterface } from "../servers";
import type { ServerInterface } from "../server";

export class Channel extends Mixin(BaseModel, BindingsMixin, DescriptionMixin, ExtensionsMixin) implements ChannelInterface {
constructor(
_json: Record<string,any>,
protected readonly _meta: ModelMetadata & { id: string, address: string } = {} as any
) {
super(_json, _meta);
}
import type { v2 } from "../../spec-types";

export class Channel extends BaseModel<v2.ChannelObject, { id: string, address: string }> implements ChannelInterface {
id(): string {
return this._meta.id;
}
Expand All @@ -38,6 +31,14 @@ export class Channel extends Mixin(BaseModel, BindingsMixin, DescriptionMixin, E
return this._meta.address;
}

hasDescription(): boolean {
return hasDescription(this);
}

description(): string | undefined {
return description(this);
}

servers(): ServersInterface {
const servers: ServerInterface[] = [];
const allowedServers: string[] = this._json.servers || [];
Expand All @@ -52,8 +53,8 @@ export class Channel extends Mixin(BaseModel, BindingsMixin, DescriptionMixin, E
operations(): OperationsInterface {
const operations: OperationInterface[] = [];
['publish', 'subscribe'].forEach(operationAction => {
this._json[operationAction] && operations.push(
this.createModel(Operation, this._json[operationAction], { id: operationAction, action: operationAction, pointer: `${this._meta.pointer}/${operationAction}` }),
this._json[operationAction as 'publish' | 'subscribe'] && operations.push(
this.createModel(Operation, this._json[operationAction as 'publish' | 'subscribe'], { id: operationAction, action: operationAction, pointer: `${this._meta.pointer}/${operationAction}` }),
);
});
return new Operations(operations);
Expand All @@ -75,4 +76,12 @@ export class Channel extends Mixin(BaseModel, BindingsMixin, DescriptionMixin, E
})
);
}

bindings(): BindingsInterface {
return bindings(this);
}

extensions(): ExtensionsInterface {
return extensions(this);
}
}
19 changes: 13 additions & 6 deletions src/models/v2/components.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { BaseModel } from "../base";
import { Bindings } from "./bindings";
import { Binding } from "./binding";
import { Channel } from "./channel";
import { ChannelParameter } from "./channel-parameter";
import { CorrelationId } from "./correlation-id";
Expand All @@ -10,15 +12,14 @@ import { SecurityScheme } from "./security-scheme";
import { Server } from "./server";
import { ServerVariable } from "./server-variable";

import { Mixin } from '../utils';
import { Bindings, Binding } from "./mixins/bindings";
import { ExtensionsMixin } from './mixins/extensions';
import { extensions } from './mixins';

import type { BindingsInterface } from "../bindings";
import type { ComponentsInterface } from "../components";
import type { ChannelInterface } from "../channel";
import type { ChannelParameterInterface } from "../channel-parameter";
import type { CorrelationIdInterface } from "../correlation-id";
import type { ExtensionsInterface } from "../extensions";
import type { MessageInterface } from "../message";
import type { MessageTraitInterface } from "../message-trait";
import type { OperationTraitInterface } from "../operation-trait";
Expand All @@ -28,7 +29,9 @@ import type { ServerInterface } from "../server";
import type { ServerVariableInterface } from "../server-variable";
import type { Constructor } from "../utils";

export class Components extends Mixin(BaseModel, ExtensionsMixin) implements ComponentsInterface {
import type { v2 } from "../../spec-types";

export class Components extends BaseModel<v2.ComponentsObject> implements ComponentsInterface {
servers(): Record<string, ServerInterface> {
return this.createMap('servers', Server);
}
Expand Down Expand Up @@ -85,14 +88,18 @@ export class Components extends Mixin(BaseModel, ExtensionsMixin) implements Com
return this.createBindings('messageBindings');
}

protected createMap<M extends BaseModel>(itemsName: string, model: Constructor<M>): Record<string, M> {
extensions(): ExtensionsInterface {
return extensions(this);
}

protected createMap<M extends BaseModel>(itemsName: keyof v2.ComponentsObject, model: Constructor<M>): Record<string, M> {
return Object.entries(this._json[itemsName] || {}).reduce((items, [itemName, item]) => {
items[itemName] = this.createModel(model, item, { id: itemName, pointer: `/components/${itemsName}/${itemName}` })
return items;
}, {} as Record<string, M>);
}

protected createBindings(itemsName: string): Record<string, BindingsInterface> {
protected createBindings(itemsName: 'serverBindings' | 'channelBindings' | 'operationBindings' | 'messageBindings'): Record<string, BindingsInterface> {
return Object.entries(this._json[itemsName] || {}).reduce((bindings, [name, item]) => {
bindings[name] = new Bindings(
Object.entries(item as any || {}).map(([protocol, binding]) =>
Expand Down
Loading