api-v1/core/builders/SchemaBuilder.js

157 lines
5.3 KiB
JavaScript

import PayloadValidator from '../validation/PayloadValidator.js';
import QueryBuilder from './QueryBuilder.js';
export default class SchemaBuilder {
constructor() {
this.errorSchemas = new Map();
}
requestSchema(s) { this.reqSchema = s; return this; }
responseSchema(s) { this.resSchema = s; return this; }
requestTranslator(f) { this.reqT = f; return this; }
responseTranslator(f) { this.resT = f; return this; }
preProcess(f) { this.pre = f; return this; }
postProcess(f) { this.post = f; return this; }
onError(c, s) { this.errorSchemas.set(c, s); return this; }
defaultError(s) { this.defErr = s; return this; }
pathSchema(schema) { this._pathSchema = schema; return this; }
querySchema(schema) { this._querySchema = schema; return this; }
headerSchema(schema) { this._headerSchema = schema; return this; }
getErrorSchema(statusCode) {
return this.errorSchemas.has(statusCode)
? this.errorSchemas.get(statusCode)
: this.defErr;
}
log(req, level, message, meta = {}) {
const requestId = req?.requestId || 'unknown';
console[level](
`[SchemaBuilder] [${level.toUpperCase()}] [req:${requestId}] ${message}`,
Object.keys(meta).length ? meta : ''
);
}
async execute(req, res, dbClient) {
const start = Date.now();
try {
this.log(req, 'info', 'Execution started');
// -----------------------------
// PATH PARAM VALIDATION
// -----------------------------
if (this._pathSchema) {
this.log(req, 'debug', 'Validating path params');
const vPath = PayloadValidator.validate(this._pathSchema, req.params);
if (!vPath.valid) {
this.log(req, 'warn', 'Path validation failed', vPath);
return res.status(400).json(vPath);
}
}
// -----------------------------
// QUERY PARAM VALIDATION
// -----------------------------
if (this._querySchema) {
this.log(req, 'debug', 'Validating query params');
const vQuery = PayloadValidator.validate(this._querySchema, req.query);
if (!vQuery.valid) {
this.log(req, 'warn', 'Query validation failed', vQuery);
return res.status(400).json(vQuery);
}
}
// -----------------------------
// HEADER VALIDATION
// -----------------------------
if (this._headerSchema) {
this.log(req, 'debug', 'Validating headers');
const vHeader = PayloadValidator.validate(this._headerSchema, req.headers);
if (!vHeader.valid) {
this.log(req, 'warn', 'Header validation failed', vHeader);
return res.status(400).json(vHeader);
}
}
// -----------------------------
// BODY VALIDATION
// -----------------------------
if (this.reqSchema) {
this.log(req, 'debug', 'Validating request body');
const vReq = PayloadValidator.validate(this.reqSchema, req.body);
if (!vReq.valid) {
this.log(req, 'warn', 'Body validation failed', vReq);
return res.status(400).json(vReq);
}
}
// -----------------------------
// PRE-PROCESS
// -----------------------------
if (this.pre) {
this.log(req, 'debug', 'Running pre-process hook');
const c = await this.pre(req.headers, req.params, req.query, req.body);
if (c !== 200) {
this.log(req, 'warn', 'Pre-process failed', { status: c });
return res.status(c).json(this.getErrorSchema(c));
}
}
// -----------------------------
// REQUEST TRANSLATION
// -----------------------------
this.log(req, 'debug', 'Translating request to query');
const q = this.reqT(req.headers, req.params, req.query, req.body);
// -----------------------------
// DB EXECUTION
// -----------------------------
this.log(req, 'info', 'Executing DB query', { type: q?.type });
const qb = new QueryBuilder().fromJSON(q);
const dbResp = await qb.execute(dbClient);
// -----------------------------
// RESPONSE TRANSLATION
// -----------------------------
this.log(req, 'debug', 'Translating DB response');
const resp = this.resT(dbResp, this.resSchema);
// -----------------------------
// RESPONSE VALIDATION
// -----------------------------
this.log(req, 'debug', 'Validating response');
const vResp = PayloadValidator.validate(this.resSchema, resp);
if (!vResp.valid) {
this.log(req, 'error', 'Response validation failed', vResp);
return res.status(500).json(vResp);
}
// -----------------------------
// POST-PROCESS
// -----------------------------
if (this.post) {
this.log(req, 'debug', 'Running post-process hook');
const c = await this.post(req, resp);
if (c !== 200) {
this.log(req, 'warn', 'Post-process failed', { status: c });
return res.status(c).json(this.getErrorSchema(c));
}
}
const duration = Date.now() - start;
this.log(req, 'info', 'Execution completed', { durationMs: duration });
res.json(resp);
} catch (e) {
this.log(req, 'error', 'Unhandled exception', {
message: e.message,
stack: e.stack
});
res.status(500).json(this.defErr);
}
}
}