@opra/common
Version:
Opra common package
188 lines (187 loc) • 7.57 kB
JavaScript
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;
}
}