koas-core
Version:
> [Koa][] + [OpenAPI Specification][] = Koas
108 lines (107 loc) • 4.09 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.koas = exports.SchemaValidationError = void 0;
const compose = require("koa-compose");
const jsonRefs_1 = require("./jsonRefs");
const matcher_1 = require("./matcher");
const validation_1 = require("./validation");
Object.defineProperty(exports, "SchemaValidationError", { enumerable: true, get: function () { return validation_1.SchemaValidationError; } });
const methods = new Set([
'delete',
'get',
'head',
'options',
'patch',
'post',
'put',
'trace',
]);
/**
* This symbol is used internally to specify a middleware should always be run.
*/
const RUN_ALWAYS = Symbol('This middleware is always run');
/**
* Mark that middleware always needs to run, even if there is no matching OpenAPI operation.
*
* @param middleware - The middleware to mark.
* @returns The marked middleware itself.
*/
function markRunAlways(middleware) {
// @ts-expect-error This is an internal hack.
// eslint-disable-next-line no-param-reassign
middleware[RUN_ALWAYS] = true;
return middleware;
}
/**
* Create a Koa middleware from Koas middlewares.
*
* @param document - The OpenAPI document from which to create an API.
* @param middlewares - The Koas middlewares to use for creating an API.
* @param options - Advanced options
* @returns Koa middleware that processes requests according the the OpenAPI document.
*/
function koas(document, middlewares = [], { onSchemaValidationError = (error) => ({ message: error.message, errors: error.result.errors }), } = {}) {
const resolveRef = (0, jsonRefs_1.createResolver)(document);
const matchers = Object.entries(document.paths).map(([pathTemplate, pathItemObject]) => {
const matcher = (0, matcher_1.createMatcher)(pathTemplate, resolveRef, pathItemObject.parameters);
return [matcher, pathItemObject];
});
const validator = (0, validation_1.createValidator)(document);
const validate = (instance, schema, { message = 'JSON schema validation failed', preValidateProperty, rewrite, status, throw: throwError = true, } = {}) => {
const result = validator.validate(instance, schema, {
base: '#',
nestedErrors: true,
rewrite,
preValidateProperty,
});
if (throwError && !result.valid) {
throw new validation_1.SchemaValidationError(message, { result, status });
}
return result;
};
const pluginOptions = {
document,
resolveRef,
runAlways: markRunAlways,
validate,
};
const injected = middlewares.map((middleware) => middleware(pluginOptions));
const composed = compose(injected);
// @ts-expect-error This is an internal hack.
const runAlways = compose(injected.filter((middleware) => middleware[RUN_ALWAYS]));
return async (ctx, next) => {
let params;
const match = matchers.find(([matcher]) => {
params = matcher(ctx.path);
return Boolean(params);
});
ctx.openApi = { document, validate };
try {
if (!match) {
return await runAlways(ctx, next);
}
const [, pathItemObject] = match;
ctx.openApi.pathItemObject = pathItemObject;
ctx.params = params;
const method = ctx.method.toLowerCase();
if (!methods.has(method)) {
return await runAlways(ctx, next);
}
const operationObject = pathItemObject[method];
if (!operationObject) {
return await runAlways(ctx, next);
}
ctx.openApi.operationObject = operationObject;
await composed(ctx, next);
}
catch (error) {
if (error instanceof validation_1.SchemaValidationError) {
ctx.status = error.status;
ctx.body = onSchemaValidationError(error, ctx);
return;
}
throw error;
}
};
}
exports.koas = koas;