UNPKG

@opra/common

Version:
299 lines (298 loc) 12.3 kB
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); }); } }); } } }