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: add metadata to models #496

Merged
merged 7 commits into from
Mar 25, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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);
}
}