UNPKG

@opra/common

Version:
188 lines (187 loc) 7.57 kB
import { updateErrorMessage } from '@jsopen/objects'; import { resolveThunk } from '../../helpers/index.js'; import { OpraSchema } from '../../schema/index.js'; import { ApiDocument } from '../api-document.js'; import { DocumentInitContext } from '../common/document-init-context.js'; import { OpraDocumentError } from '../common/opra-document-error.js'; import { BUILTIN, CLASS_NAME_PATTERN, kCtorMap } from '../constants.js'; import * as extendedTypes from '../data-type/extended-types/index.js'; import * as primitiveTypes from '../data-type/primitive-types/index.js'; import { DataTypeFactory } from './data-type.factory.js'; import { HttpApiFactory } from './http-api.factory.js'; import { MQApiFactory } from './mq-api.factory.js'; import { WSApiFactory } from './ws-api.factory.js'; const OPRA_SPEC_URL = 'https://oprajs.com/spec/v' + OpraSchema.SpecVersion; /** * @class ApiDocumentFactory */ export class ApiDocumentFactory { _allDocuments = {}; /** * Creates ApiDocument instance from given schema object */ static async createDocument(schemaOrUrl, options) { const factory = new ApiDocumentFactory(); const context = options instanceof DocumentInitContext ? options : new DocumentInitContext(options); try { const document = new ApiDocument(); await factory.initDocument(document, context, schemaOrUrl); if (context.error.details.length) throw context.error; return document; } catch (e) { try { if (!(e instanceof OpraDocumentError)) { context.addError(e); } } catch { // } if (!context.error.message) { const l = context.error.details.length; let message = `(${l}) error${l > 1 ? 's' : ''} found in document schema.`; if (context.showErrorDetails) { message += context.error.details .map(d => `\n\n - ${d.message}` + (d.path ? `\n @${d.path}` : '')) .join(''); } updateErrorMessage(context.error, message); } throw context.error; } } /** * Downloads schema from the given URL and creates the document instance * @param url */ async initDocument(document, context, schemaOrUrl) { let init; if (typeof schemaOrUrl === 'string') { const resp = await fetch(schemaOrUrl, { method: 'GET' }); init = await resp.json(); if (!init) return context.addError(`Invalid response returned from url: ${schemaOrUrl}`); init.url = schemaOrUrl; } else init = schemaOrUrl; // Add builtin data types if this document is the root let builtinDocument; if (!document[BUILTIN]) { const t = document.node.findDataType('string'); builtinDocument = t?.node.getDocument(); if (!builtinDocument) { builtinDocument = await this.createBuiltinDocument(context); document.references.set('opra', builtinDocument); } } init.spec = init.spec || OpraSchema.SpecVersion; document.url = init.url; if (init.info) document.info = { ...init.info }; /** Add references */ if (init.references) { await context.enterAsync('.references', async () => { let ns; let r; for ([ns, r] of Object.entries(init.references)) { r = await resolveThunk(r); await context.enterAsync(`[${ns}]`, async () => { if (!CLASS_NAME_PATTERN.test(ns)) throw new TypeError(`Invalid namespace (${ns})`); if (r instanceof ApiDocument) { document.references.set(ns, r); return; } const refDoc = new ApiDocument(); if (builtinDocument) refDoc.references.set('opra', builtinDocument); await this.initDocument(refDoc, context, r); document.references.set(ns, this._allDocuments[refDoc.id]); }); } }); } if (init.types) { await context.enterAsync('.types', async () => { await DataTypeFactory.addDataTypes(context, document, init.types); }); } if (init.api) { await context.enterAsync(`.api`, async () => { if (init.api && init.api.transport === 'http') { const api = await HttpApiFactory.createApi(context, { ...init.api, owner: document, }); if (api) document.api = api; } else if (init.api && init.api.transport === 'mq') { const api = await MQApiFactory.createApi(context, { ...init.api, owner: document, }); if (api) document.api = api; } else if (init.api && init.api.transport === 'ws') { const api = await WSApiFactory.createApi(context, { ...init.api, owner: document, }); if (api) document.api = api; } else context.addError(`Unknown service transport (${init.api.transport})`); }); } document.invalidate(); /** Add document to global registry */ if (!this._allDocuments[document.id]) this._allDocuments[document.id] = document; } /** * * @param context * @protected */ async createBuiltinDocument(context) { const init = { spec: OpraSchema.SpecVersion, url: OPRA_SPEC_URL, info: { version: OpraSchema.SpecVersion, title: 'Opra built-in types', license: { url: 'https://github.com/oprajs/opra/blob/main/LICENSE', name: 'MIT', }, }, types: [ ...Object.values(primitiveTypes), ...Object.values(extendedTypes), ], }; const document = new ApiDocument(); document[BUILTIN] = true; const BigIntConstructor = Object.getPrototypeOf(BigInt(0)).constructor; const BufferConstructor = Object.getPrototypeOf(Buffer.from([])); const _ctorTypeMap = document.types[kCtorMap]; _ctorTypeMap.set(Object, 'object'); _ctorTypeMap.set(String, 'string'); _ctorTypeMap.set(Number, 'number'); _ctorTypeMap.set(Boolean, 'boolean'); _ctorTypeMap.set(Object, 'any'); _ctorTypeMap.set(Date, 'datetime'); _ctorTypeMap.set(BigIntConstructor, 'bigint'); _ctorTypeMap.set(ArrayBuffer, 'base64'); _ctorTypeMap.set(BufferConstructor, 'base64'); await this.initDocument(document, context, init); return document; } }