Skip to content

Commit

Permalink
refactor: remove mixins and add AsyncAPI TS types (#581)
Browse files Browse the repository at this point in the history
  • Loading branch information
magicmatatjahu authored Aug 31, 2022
1 parent 0f8bd39 commit a40b685
Show file tree
Hide file tree
Showing 83 changed files with 1,579 additions and 919 deletions.
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

0 comments on commit a40b685

Please sign in to comment.