Skip to content

Commit

Permalink
refactor: add extensions method to the bindings (#587)
Browse files Browse the repository at this point in the history
  • Loading branch information
magicmatatjahu authored Sep 6, 2022
1 parent 14458ef commit 3db1d09
Show file tree
Hide file tree
Showing 49 changed files with 381 additions and 275 deletions.
45 changes: 16 additions & 29 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 10 additions & 11 deletions src/models/base.ts
Original file line number Diff line number Diff line change
@@ -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<J extends Record<string, any> = Record<string, any>, M extends Record<string, any> = {}> {
export abstract class BaseModel<J extends any = any, M extends Record<string, any> = {}> {
constructor(
protected readonly _json: J,
protected readonly _meta: ModelMetadata & M = {} as any,
protected readonly _meta: ModelMetadata & M = {} as ModelMetadata & M,
) {}

json<T extends Record<string, any> = J>(): T;
json<T = 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)];
if (this._json === null || this._json === undefined) return this._json;
return this._json[key];
}

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

jsonPath(field?: string | undefined): string {
Expand All @@ -36,7 +35,7 @@ export abstract class BaseModel<J extends Record<string, any> = Record<string, a
return `${this._meta.pointer}/${field}`;
}

protected createModel<T extends BaseModel>(Model: Constructor<T>, value: any, meta: { pointer: string | number, [key: string]: any }): T {
protected createModel<T extends BaseModel>(Model: Constructor<T>, value: InferModelData<T>, meta: Omit<ModelMetadata, 'asyncapi'> & InferModelMetadata<T>): T {
return new Model(value, { ...meta, asyncapi: this._meta.asyncapi });
}
}
4 changes: 2 additions & 2 deletions src/models/binding.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { BaseModel } from "./base";
import type { ExtensionsMixinInterface } from './mixins';

export interface BindingInterface extends BaseModel, ExtensionsMixinInterface {
export interface BindingInterface<T extends Record<string, any> = Record<string, any>> extends BaseModel, ExtensionsMixinInterface {
protocol(): string;
version(): string;
value(): any;
value<V = T>(): V;
}
3 changes: 2 additions & 1 deletion src/models/bindings.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Collection } from './collection';
import type { BindingInterface } from './binding';
import type { ExtensionsMixinInterface } from "./mixins";

export interface BindingsInterface extends Collection<BindingInterface> {}
export interface BindingsInterface extends Collection<BindingInterface>, ExtensionsMixinInterface {}
25 changes: 22 additions & 3 deletions src/models/collection.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import type { BaseModel } from "./base";
import type { DetailedAsyncAPI } from "../types";

export abstract class Collection<T extends BaseModel | Collection<any>> extends Array<T> {
export interface CollectionMetadata<T = any> {
originalData?: Record<string, T>;
asyncapi?: DetailedAsyncAPI;
pointer?: string;
}

export abstract class Collection<T extends BaseModel = BaseModel, M extends Record<string, any> = {}> extends Array<T> {
constructor(
protected readonly collections: T[]
protected readonly collections: T[],
protected readonly _meta: CollectionMetadata<T> & M = {} as CollectionMetadata<T> & 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;
Expand All @@ -21,4 +32,12 @@ export abstract class Collection<T extends BaseModel | Collection<any>> extends
filterBy(filter: (item: T) => boolean): T[] {
return this.collections.filter(filter);
}

meta(): CollectionMetadata<T> & M;
meta<K extends keyof (CollectionMetadata<T> & M)>(key: K): (CollectionMetadata<T> & M)[K];
meta(key?: keyof (CollectionMetadata<T> & M)) {
if (key === undefined) return this._meta;
if (!this._meta) return;
return this._meta[String(key)];
}
}
4 changes: 2 additions & 2 deletions src/models/extension.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { BaseModel } from "./base";

export interface ExtensionInterface extends BaseModel {
export interface ExtensionInterface<T = any> extends BaseModel {
name(): string;
version(): string;
value(): any;
value<V = T>(): V;
}
3 changes: 2 additions & 1 deletion src/models/schema.ts
Original file line number Diff line number Diff line change
@@ -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<v2.AsyncAPISchemaObject>, ExtensionsMixinInterface, ExternalDocumentationMixinInterface {
uid(): string;
$comment(): string | undefined;
$id(): string | undefined;
Expand Down
38 changes: 6 additions & 32 deletions src/models/utils.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,13 @@
import type { BaseModel } from './base';
import type { BaseModel, ModelMetadata } from './base';
import type { DetailedAsyncAPI } from '../types';

export interface Constructor<T> extends Function {
new (...any: any[]): T;
}

export interface MixinType<T = any> extends Function {
prototype: T;
}

export function Mixin(a: typeof BaseModel): typeof BaseModel;
export function Mixin<A>(a: typeof BaseModel, b: MixinType<A>): typeof BaseModel & Constructor<A>;
export function Mixin<A, B>(a: typeof BaseModel, b: MixinType<A>, c: MixinType<B>): typeof BaseModel & Constructor<A> & Constructor<B>;
export function Mixin<A, B, C>(a: typeof BaseModel, b: MixinType<A>, c: MixinType<B>, d: MixinType<C>): typeof BaseModel & Constructor<A> & Constructor<B> & Constructor<C>;
export function Mixin<A, B, C, D>(a: typeof BaseModel, b: MixinType<A>, c: MixinType<B>, d: MixinType<C>, e: MixinType<D>): typeof BaseModel & Constructor<A> & Constructor<B> & Constructor<C> & Constructor<D>;
export function Mixin<A, B, C, D, E>(a: typeof BaseModel, b: MixinType<A>, c: MixinType<B>, d: MixinType<C>, e: MixinType<D>, f: MixinType<E>): typeof BaseModel & Constructor<A> & Constructor<B> & Constructor<C> & Constructor<D> & Constructor<E>;
export function Mixin(baseCtor: typeof BaseModel, ...constructors: any[]) {
return mixin(class extends baseCtor {}, constructors);
}
export type InferModelData<T> = T extends BaseModel<infer J> ? J : never;
export type InferModelMetadata<T> = T extends BaseModel<infer J, infer M> ? 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<T extends BaseModel>(Model: Constructor<T>, value: InferModelData<T>, meta: Omit<ModelMetadata, 'asyncapi'> & { asyncapi?: DetailedAsyncAPI } & InferModelMetadata<T>, parent?: BaseModel): T {
return new Model(value, { ...meta, asyncapi: meta.asyncapi || parent?.meta().asyncapi });
}

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 });
}
2 changes: 1 addition & 1 deletion src/models/v2/asyncapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export class AsyncAPIDocument extends BaseModel<v2.AsyncAPIObject> 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}` })
)
);
}
Expand Down
6 changes: 3 additions & 3 deletions src/models/v2/binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { ExtensionsInterface } from "../extensions";

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

export class Binding extends BaseModel<v2.Binding, { protocol: string }> implements BindingInterface {
export class Binding<T extends Record<string, any> = Record<string, any>> extends BaseModel<v2.Binding & T, { protocol: string }> implements BindingInterface<T> {
protocol(): string {
return this._meta.protocol;
}
Expand All @@ -16,10 +16,10 @@ export class Binding extends BaseModel<v2.Binding, { protocol: string }> impleme
return this._json.bindingVersion || 'latest';
}

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

extensions(): ExtensionsInterface {
Expand Down
27 changes: 22 additions & 5 deletions src/models/v2/bindings.ts
Original file line number Diff line number Diff line change
@@ -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<BindingInterface> implements BindingsInterface {
override get(name: string): BindingInterface | undefined {
override get<T extends Record<string, any> = Record<string, any>>(name: string): BindingInterface<T> | 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);
}
}
4 changes: 0 additions & 4 deletions src/models/v2/channel-parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,4 @@ export class ChannelParameters extends Collection<ChannelParameterInterface> 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);
}
}
6 changes: 3 additions & 3 deletions src/models/v2/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -55,7 +55,7 @@ export class Channel extends BaseModel<v2.ChannelObject, { id: string, address:
['publish', 'subscribe'].forEach(operationAction => {
const id = this._json[operationAction as 'publish' | 'subscribe'] && (this._json[operationAction as 'publish' | 'subscribe'] as v2.OperationObject).operationId || this.meta().id + "_" + operationAction;
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);
Expand All @@ -70,7 +70,7 @@ export class Channel extends BaseModel<v2.ChannelObject, { id: string, address:
parameters(): ChannelParametersInterface {
return new ChannelParameters(
Object.entries(this._json.parameters || {}).map(([channelParameterName, channelParameter]) => {
return this.createModel(ChannelParameter, channelParameter, {
return this.createModel(ChannelParameter, channelParameter as v2.ParameterObject, {
id: channelParameterName,
pointer: `${this._meta.pointer}/parameters/${channelParameterName}`
})
Expand Down
4 changes: 0 additions & 4 deletions src/models/v2/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ export class Channels extends Collection<ChannelInterface> 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);
}
Expand Down
Loading

0 comments on commit 3db1d09

Please sign in to comment.