Skip to content

Commit

Permalink
refactor: add metadata to models (asyncapi#496)
Browse files Browse the repository at this point in the history
  • Loading branch information
magicmatatjahu committed Oct 3, 2022
1 parent d749906 commit ae786f8
Show file tree
Hide file tree
Showing 32 changed files with 513 additions and 273 deletions.
25 changes: 10 additions & 15 deletions src/models/asyncapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,22 @@ import { AsyncAPIDocumentV3 } from "./v3";
import type { InfoInterface } from "./info";
import type { BaseModel } from "./base";
import type { ExtensionsMixinInterface } from "./mixins";
import { ServersInterface } from "./servers";
import type { ServersInterface } from "./servers";
import type { DetailedAsyncAPI } from "../types";

export interface AsyncAPIDocumentInterface extends BaseModel, ExtensionsMixinInterface {
version(): string;
info(): InfoInterface;
servers(): ServersInterface
servers(): ServersInterface;
}

export function newAsyncAPIDocument(json: Record<string, any>): AsyncAPIDocumentInterface {
const version = json['asyncapi']; // Maybe this should be an arg.
if (version == undefined || version == null || version == '') {
throw new Error('Missing AsyncAPI version in document');
}

const major = version.split(".")[0];
switch (major) {
case '2':
return new AsyncAPIDocumentV2(json);
case '3':
return new AsyncAPIDocumentV3(json);
export function newAsyncAPIDocument(asyncapi: DetailedAsyncAPI): AsyncAPIDocumentInterface {
switch (asyncapi.semver.major) {
case 2:
return new AsyncAPIDocumentV2(asyncapi.parsed, { parent: null, asyncapi, pointer: '/' });
case 3:
return new AsyncAPIDocumentV3(asyncapi.parsed, { parent: null, asyncapi, pointer: '/' });
default:
throw new Error(`Unsupported version: ${version}`);
throw new Error(`Unsupported AsyncAPI version: ${asyncapi.semver.version}`);
}
}
31 changes: 30 additions & 1 deletion src/models/base.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
export class BaseModel {
import type { Constructor } from "./utils";
import type { DetailedAsyncAPI } from "../types";

export interface ModelMetadata<P = unknown> {
asyncapi: DetailedAsyncAPI;
pointer: string;
parent: P | null;
}

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

json<T = Record<string, any>>(): T;
Expand All @@ -10,4 +20,23 @@ export class BaseModel {
if (!this._json) return;
return this._json[String(key)];
}

meta(): ModelMetadata {
return this._meta!;
}

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

protected createModel<T extends BaseModel>(Model: Constructor<T>, value: any, { id, parent, pointer }: { id?: string, parent?: any, pointer: string | number }): T {
const meta = { asyncapi: this._meta.asyncapi, parent: parent || this, pointer } as ModelMetadata;
if (id) {
return new Model(id, value, meta);
}
return new Model(value, meta);
}
}
16 changes: 8 additions & 8 deletions src/models/server.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { BaseModel } from "./base";
import { BindingsMixinInterface, DescriptionMixinInterface } from './mixins';
import type { BaseModel } from "./base";
import type { BindingsMixinInterface, DescriptionMixinInterface, ExtensionsMixinInterface } from './mixins';

export interface ServerInterface extends BaseModel, DescriptionMixinInterface, BindingsMixinInterface {
id(): string
protocol(): string | undefined;
protocolVersion(): string;
hasProtocolVersion(): boolean;
url(): string;
export interface ServerInterface extends BaseModel, DescriptionMixinInterface, BindingsMixinInterface, ExtensionsMixinInterface {
id(): string
url(): string;
protocol(): string | undefined;
protocolVersion(): string;
hasProtocolVersion(): boolean;
}
2 changes: 1 addition & 1 deletion src/models/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { BaseModel } from './base';

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

Expand Down
14 changes: 8 additions & 6 deletions src/models/v2/asyncapi.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { BaseModel } from "../base";
import { Info } from "./info";
import { Servers } from "./servers";
import { Server } from "./server";

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

import { AsyncAPIDocumentInterface, InfoInterface } from "../../models";
import { ServersInterface } from "models/servers";
import { Servers } from "./servers";
import { Server } from "./server";
import type { AsyncAPIDocumentInterface, InfoInterface } from "../../models";
import type { ServersInterface } from "models/servers";

export class AsyncAPIDocument
extends Mixin(BaseModel, ExtensionsMixin)
Expand All @@ -18,12 +18,14 @@ export class AsyncAPIDocument
}

info(): InfoInterface {
return new Info(this._json.info);
return this.createModel(Info, this._json.info, { pointer: '/info' });
}

servers(): ServersInterface {
return new Servers(
Object.entries(this._json.servers).map(([serverName, server]) => new Server(serverName, server as Record<string, any>))
Object.entries(this._json.servers).map(([serverName, server]) =>
this.createModel(Server, server, { id: serverName, pointer: `/servers/${serverName}` })
)
);
}
}
36 changes: 26 additions & 10 deletions src/models/v2/info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import { License } from "./license";
import { Mixin } from '../utils';
import { DescriptionMixin } from './mixins/description';
import { ExtensionsMixin } from './mixins/extensions';
import { ExternalDocumentationMixin } from './mixins/external-docs';
import { TagsMixin } from './mixins/tags';
import { ExternalDocumentation } from './mixins/external-docs';
import { Tags, Tag } from './mixins/tags';

import type { InfoInterface } from "../../models/info";
import type { ExternalDocumentationInterface } from "../../models/external-docs";
import type { TagsInterface } from "../../models/tags";

export class Info
extends Mixin(BaseModel, DescriptionMixin, ExtensionsMixin, ExternalDocumentationMixin, TagsMixin)
extends Mixin(BaseModel, DescriptionMixin, ExtensionsMixin)
implements InfoInterface {

title(): string {
Expand All @@ -22,14 +24,12 @@ export class Info
return this._json.version;
}

// TODO: Implement it
id(): string | undefined {
return;
return this._meta.asyncapi.parsed.id as string;
}

// TODO: Implement it
hasId(): boolean {
return true;
return !!this._meta.asyncapi.parsed.id;
}

hasTermsOfService(): boolean {
Expand All @@ -46,7 +46,7 @@ export class Info

contact(): Contact | undefined {
const contact = this._json.contact;
return contact && new Contact(contact);
return contact && this.createModel(Contact, contact, { pointer: '/info/contact' });
}

hasLicense(): boolean {
Expand All @@ -55,6 +55,22 @@ export class Info

license(): License | undefined {
const license = this._json.license;
return license && new License(license);
return license && this.createModel(License, license, { pointer: `/info/license` });
}

hasExternalDocs(): boolean {
return Object.keys(this._meta.asyncapi.parsed.externalDocs || {}).length > 0;
};

externalDocs(): ExternalDocumentationInterface | undefined {
if (this.hasExternalDocs()) {
return this.createModel(ExternalDocumentation, this._meta.asyncapi.parsed.externalDocs, { pointer: `/externalDocs` });
}
return;
};

tags(): TagsInterface {
const tags = this._meta.asyncapi.parsed.tags || [];
return new Tags(tags.map((tag: any, idx: number) => this.createModel(Tag, tag, { pointer: `/tags/${idx}` })));
}
}
}
2 changes: 1 addition & 1 deletion src/models/v2/license.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ export class License extends Mixin(BaseModel, ExtensionsMixin) implements Licens
url(): string | undefined {
return this._json.url;
}
}
}
8 changes: 6 additions & 2 deletions src/models/v2/mixins/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Collection } from '../../collection';
import { Mixin } from '../../utils';
import { ExtensionsMixin } from './extensions';

import type { ModelMetadata } from "../../base";
import type { BindingsMixinInterface } from "../../mixins";
import type { BindingsInterface } from "../../bindings";
import type { BindingInterface } from "../../binding";
Expand All @@ -12,8 +13,9 @@ export class Binding extends Mixin(BaseModel, ExtensionsMixin) implements Bindin
constructor(
private readonly _protocol: string,
_json: Record<string, any>,
_meta: ModelMetadata = {} as any,
) {
super(_json);
super(_json, _meta);
}

protocol(): string {
Expand Down Expand Up @@ -45,7 +47,9 @@ export abstract class BindingsMixin extends BaseModel implements BindingsMixinIn
bindings(): BindingsInterface {
const bindings: Record<string, any> = this._json.bindings || {};
return new Bindings(
Object.entries(bindings).map(([protocol, binding]) => new Binding(protocol, binding))
Object.entries(bindings).map(([protocol, binding]) =>
this.createModel(Binding, binding, { id: protocol, pointer: `${this._meta.pointer}/bindings/${protocol}` })
)
);
}
}
8 changes: 6 additions & 2 deletions src/models/v2/mixins/extensions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Collection } from '../../collection';
import { BaseModel } from "../../base";

import type { ModelMetadata } from "../../base";
import type { ExtensionsMixinInterface } from "../../mixins";
import type { ExtensionsInterface } from "../../extensions";
import type { ExtensionInterface } from "../../extension";
Expand All @@ -11,8 +12,9 @@ export class Extension extends BaseModel implements ExtensionInterface {
constructor(
private readonly _id: string,
_json: Record<string, any>,
_meta: ModelMetadata = {} as any,
) {
super(_json);
super(_json, _meta);
}

id(): string {
Expand Down Expand Up @@ -43,7 +45,9 @@ export abstract class ExtensionsMixin extends BaseModel implements ExtensionsMix
const extensions: Extension[] = [];
Object.entries(this._json).forEach(([key, value]) => {
if (EXTENSION_REGEX.test(key)) {
extensions.push(new Extension(key, value));
extensions.push(
this.createModel(Extension, value, { id: key, pointer: `${this._meta.pointer}/${key}` })
);
}
});
return new Extensions(extensions);
Expand Down
6 changes: 5 additions & 1 deletion src/models/v2/mixins/tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ export class Tags extends Collection<TagInterface> implements TagsInterface {
export abstract class TagsMixin extends BaseModel implements TagsMixinInterface {
tags(): TagsInterface {
const tags = this._json.tags || [];
return new Tags(tags.map((tag: any) => new Tag(tag)));
return new Tags(
tags.map((tag: any, idx: number) =>
this.createModel(Tag, tag, { pointer: `${this._meta.pointer}/tags/${idx}` })
)
);
}
}
65 changes: 35 additions & 30 deletions src/models/v2/server.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,39 @@
import { Mixin } from '../utils';
import { BaseModel } from '../base';
import { ServerInterface } from '../server';
import { DescriptionMixin } from './mixins/description';

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

import type { ModelMetadata } from "../base";
import type { ServerInterface } from '../server';

export class Server extends Mixin(BaseModel, BindingsMixin, DescriptionMixin, ExtensionsMixin) implements ServerInterface {
constructor(
private readonly _id: string,
_json: Record<string, any>,
_meta: ModelMetadata = {} as any,
) {
super(_json, _meta);
}

id(): string {
return this._id;
}

url(): string {
return this._json.url;
}

protocol(): string | undefined {
return this._json.protocol;
}

hasProtocolVersion(): boolean {
return !!this._json.protocolVersion;
}

export class Server extends Mixin(BaseModel, DescriptionMixin, BindingsMixin) implements ServerInterface {
constructor(
private readonly _id: string,
_json: Record<string, any>
){
super(_json);
}

id(): string {
return this._id;
}

protocol(): string | undefined {
return this.json('protocol');
}

hasProtocolVersion(): boolean {
return !!this.json('protocolVersion');
}

protocolVersion(): string {
return this.json('protocolVersion');
}

url(): string {
return this.json('url');
}
protocolVersion(): string {
return this._json.protocolVersion;
}
}
14 changes: 7 additions & 7 deletions src/models/v2/servers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { ServerInterface } from '../server';
import { ServersInterface } from '../servers';

export class Servers extends Collection<ServerInterface> implements ServersInterface {
override get(id: string): ServerInterface | undefined {
return this.collections.find(server => server.id() === id);
}
override get(id: string): ServerInterface | undefined {
return this.collections.find(server => server.id() === id);
}

override has(id: string): boolean {
return this.collections.some(server => server.id() === id);
}
}
override has(id: string): boolean {
return this.collections.some(server => server.id() === id);
}
}
Loading

0 comments on commit ae786f8

Please sign in to comment.