157 lines
5.3 KiB
JavaScript
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);
|
|
}
|
|
}
|
|
}
|