@opra/common
Version:
Opra common package
299 lines (298 loc) • 12.3 kB
JavaScript
import { isConstructor } from '@jsopen/objects';
import { resolveThunk } from '../../helpers/index.js';
import { HTTP_CONTROLLER_METADATA } from '../constants.js';
import { HttpApi } from '../http/http-api.js';
import { HttpController } from '../http/http-controller.js';
import { HttpMediaType } from '../http/http-media-type.js';
import { HttpMultipartField } from '../http/http-multipart-field.js';
import { HttpOperation } from '../http/http-operation.js';
import { HttpOperationResponse } from '../http/http-operation-response.js';
import { HttpParameter } from '../http/http-parameter.js';
import { HttpRequestBody } from '../http/http-request-body.js';
import { DataTypeFactory } from './data-type.factory.js';
/**
* @class HttpApiFactory
*/
export class HttpApiFactory {
/**
* Generates HttpApi
* @param context
* @param init
*/
static async createApi(context, init) {
const api = new HttpApi(init);
if (init.controllers) {
await context.enterAsync('.controllers', async () => {
await this._createControllers(context, api, init.controllers);
});
}
return api;
}
static async _createControllers(context, parent, controllers) {
if (Array.isArray(controllers)) {
let i = 0;
for (const c of controllers) {
let r;
await context.enterAsync(`[${i++}]`, async () => {
r = await this._resolveControllerMetadata(context, parent, c);
});
if (!r)
continue;
await context.enterAsync(`[${r.metadata.name}]`, async () => {
const controller = await this._createController(context, parent, r.metadata, r.instance, r.ctor);
if (controller) {
if (parent.controllers.get(controller.name))
context.addError(`Duplicate controller name (${r.name})`);
parent.controllers.set(controller.name, controller);
}
});
}
return;
}
for (const [k, c] of Object.entries(controllers)) {
await context.enterAsync(`[${k}]`, async () => {
const r = await this._resolveControllerMetadata(context, parent, c);
if (!r)
return;
const controller = await this._createController(context, parent, {
...r.metadata,
name: k,
}, r.instance, r.ctor);
if (controller) {
if (parent.controllers.get(controller.name))
context.addError(`Duplicate controller name (${k})`);
parent.controllers.set(controller.name, controller);
}
});
}
}
static async _resolveControllerMetadata(context, parent, thunk) {
if (typeof thunk === 'function' && !isConstructor(thunk)) {
thunk =
parent instanceof HttpController ? thunk(parent.instance) : thunk();
}
thunk = await resolveThunk(thunk);
let ctor;
let metadata;
let instance;
// If thunk is a class
if (typeof thunk === 'function') {
metadata = Reflect.getMetadata(HTTP_CONTROLLER_METADATA, thunk);
if (!metadata)
return context.addError(`Class "${thunk.name}" doesn't have a valid HttpController metadata`);
ctor = thunk;
}
else {
// If thunk is an instance of a class decorated with HttpController()
ctor = Object.getPrototypeOf(thunk).constructor;
metadata = Reflect.getMetadata(HTTP_CONTROLLER_METADATA, ctor);
if (metadata)
instance = thunk;
else {
// If thunk is a DecoratorMetadata or InitArguments
metadata = thunk;
if (thunk.instance === 'object') {
instance = thunk.instance;
ctor = Object.getPrototypeOf(instance).constructor;
}
}
}
if (!metadata)
return context.addError(`Class "${ctor.name}" is not decorated with HttpController()`);
return { metadata, instance, ctor };
}
static async _createController(context, parent, metadata, instance, ctor) {
if (!metadata.name)
throw new TypeError(`Controller name required`);
const controller = new HttpController(parent, {
...metadata,
instance,
ctor,
});
if (metadata.types) {
await context.enterAsync('.types', async () => {
await DataTypeFactory.addDataTypes(context, controller, metadata.types);
});
}
if (metadata.parameters) {
await context.enterAsync('.parameters', async () => {
let i = 0;
for (const v of metadata.parameters) {
await context.enterAsync(`[${i++}]`, async () => {
const prmArgs = { ...v };
await context.enterAsync('.type', async () => {
prmArgs.type = await DataTypeFactory.resolveDataType(context, controller, v.type);
});
const prm = new HttpParameter(controller, prmArgs);
controller.parameters.push(prm);
});
}
});
}
if (metadata.operations) {
await context.enterAsync('.operations', async () => {
for (const [k, v] of Object.entries(metadata.operations)) {
await context.enterAsync(`[${k}]`, async () => {
const operation = new HttpOperation(controller, {
name: k,
method: 'GET',
});
await this._initHttpOperation(context, operation, v);
controller.operations.set(k, operation);
});
}
});
}
if (metadata.controllers) {
await context.enterAsync('.controllers', async () => {
await this._createControllers(context, controller, metadata.controllers);
});
}
return controller;
}
/**
* Initializes HttpOperation
* @param context
* @param operation
* @param metadata
* @protected
*/
static async _initHttpOperation(context, operation, metadata) {
const initArgs = {
...metadata,
name: operation.name,
types: undefined,
};
HttpOperation.apply(operation, [operation.owner, initArgs]);
if (metadata.types) {
await context.enterAsync('.types', async () => {
await DataTypeFactory.addDataTypes(context, operation, metadata.types);
});
}
if (metadata.parameters) {
await context.enterAsync('.parameters', async () => {
let i = 0;
for (const v of metadata.parameters) {
await context.enterAsync(`[${i++}]`, async () => {
const prmArgs = { ...v };
await context.enterAsync('.type', async () => {
prmArgs.type = await DataTypeFactory.resolveDataType(context, operation, v.type);
});
const prm = new HttpParameter(operation, prmArgs);
operation.parameters.push(prm);
});
}
});
}
if (metadata.responses) {
await context.enterAsync('.responses', async () => {
let i = 0;
for (const v of metadata.responses) {
await context.enterAsync(`[${i++}]`, async () => {
const response = new HttpOperationResponse(operation, {
statusCode: v.statusCode,
});
await this._initHttpOperationResponse(context, response, v);
operation.responses.push(response);
});
}
});
}
if (metadata.requestBody) {
await context.enterAsync('.requestBody', async () => {
const requestBody = new HttpRequestBody(operation);
await this._initHttpRequestBody(context, requestBody, metadata.requestBody);
operation.requestBody = requestBody;
});
}
return operation;
}
/**
* Initializes HttpMediaType
* @param context
* @param target
* @param metadata
* @protected
*/
static async _initHttpMediaType(context, target, metadata) {
HttpMediaType.call(target, target.owner, {
...metadata,
type: undefined,
multipartFields: undefined,
});
if (metadata.type) {
await context.enterAsync('.type', async () => {
target.type = await DataTypeFactory.resolveDataType(context, target, metadata.type);
});
}
if (metadata.multipartFields) {
await context.enterAsync('.multipartFields', async () => {
for (let i = 0; i < metadata.multipartFields.length; i++) {
await context.enterAsync(`[${i}]`, async () => {
const src = metadata.multipartFields[i];
const field = new HttpMultipartField(target, {
fieldName: src.fieldName,
fieldType: src.fieldType,
});
await this._initHttpMediaType(context, field, src);
target.multipartFields.push(field);
});
}
});
}
}
/**
* Initializes HttpOperationResponse
* @param context
* @param target
* @param metadata
* @protected
*/
static async _initHttpOperationResponse(context, target, metadata) {
await this._initHttpMediaType(context, target, metadata);
target.partial = metadata.partial;
if (metadata.parameters) {
await context.enterAsync('.parameters', async () => {
let i = 0;
for (const v of metadata.parameters) {
await context.enterAsync(`[${i++}]`, async () => {
const prmArgs = { ...v };
await context.enterAsync('.type', async () => {
prmArgs.type = await DataTypeFactory.resolveDataType(context, target, v.type);
});
const prm = new HttpParameter(target, prmArgs);
target.parameters.push(prm);
});
}
});
}
}
/**
* Initializes HttpRequestBody
* @param context
* @param target
* @param metadata
* @protected
*/
static async _initHttpRequestBody(context, target, metadata) {
target.description = metadata.description;
target.required = metadata.required;
target.maxContentSize = metadata.maxContentSize;
target.immediateFetch = metadata.immediateFetch;
target.partial = metadata.partial;
target.allowPatchOperators = metadata.allowPatchOperators;
target.keepKeyFields = metadata.keepKeyFields;
if (metadata.content) {
await context.enterAsync('.content', async () => {
for (let i = 0; i < metadata.content.length; i++) {
await context.enterAsync(`[${i}]`, async () => {
const src = metadata.content[i];
const field = new HttpMediaType(target, String(i));
await this._initHttpMediaType(context, field, src);
target.content.push(field);
});
}
});
}
}
}