uploaded
This commit is contained in:
137
server/middleware/validation.js
Normal file
137
server/middleware/validation.js
Normal file
@@ -0,0 +1,137 @@
|
||||
class ValidationError extends Error {
|
||||
constructor(message, details = []) {
|
||||
super(message);
|
||||
this.name = "ValidationError";
|
||||
this.details = details;
|
||||
}
|
||||
}
|
||||
|
||||
function isObject(value) {
|
||||
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function normalizeString(input, rule = {}) {
|
||||
let value = String(input);
|
||||
|
||||
if (rule.trim !== false) {
|
||||
value = value.trim();
|
||||
}
|
||||
|
||||
if (rule.toLowerCase) {
|
||||
value = value.toLowerCase();
|
||||
}
|
||||
|
||||
if (rule.toUpperCase) {
|
||||
value = value.toUpperCase();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function validateField(value, key, rule, errors) {
|
||||
if (value == null) {
|
||||
if (rule.required) {
|
||||
errors.push({ field: key, issue: "required" });
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (rule.type === "string") {
|
||||
const normalized = normalizeString(value, rule);
|
||||
|
||||
if (rule.minLength != null && normalized.length < rule.minLength) {
|
||||
errors.push({ field: key, issue: "minLength", expected: rule.minLength });
|
||||
}
|
||||
|
||||
if (rule.maxLength != null && normalized.length > rule.maxLength) {
|
||||
errors.push({ field: key, issue: "maxLength", expected: rule.maxLength });
|
||||
}
|
||||
|
||||
if (rule.pattern && !rule.pattern.test(normalized)) {
|
||||
errors.push({ field: key, issue: "pattern" });
|
||||
}
|
||||
|
||||
if (rule.enum && !rule.enum.includes(normalized)) {
|
||||
errors.push({ field: key, issue: "enum", expected: rule.enum });
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
if (rule.type === "int") {
|
||||
const asString = String(value).trim();
|
||||
if (!/^-?\d+$/.test(asString)) {
|
||||
errors.push({ field: key, issue: "type", expected: "int" });
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const parsed = Number.parseInt(asString, 10);
|
||||
|
||||
if (!Number.isSafeInteger(parsed)) {
|
||||
errors.push({ field: key, issue: "type", expected: "safe-int" });
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (rule.min != null && parsed < rule.min) {
|
||||
errors.push({ field: key, issue: "min", expected: rule.min });
|
||||
}
|
||||
|
||||
if (rule.max != null && parsed > rule.max) {
|
||||
errors.push({ field: key, issue: "max", expected: rule.max });
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
errors.push({ field: key, issue: "unsupported-rule" });
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function validateObject(payload, schema, locationName) {
|
||||
const target = payload == null ? {} : payload;
|
||||
const errors = [];
|
||||
|
||||
if (!isObject(target)) {
|
||||
throw new ValidationError(`Invalid ${locationName}. Expected an object.`, [
|
||||
{ field: locationName, issue: "type", expected: "object" },
|
||||
]);
|
||||
}
|
||||
|
||||
const output = {};
|
||||
const fields = schema.fields || {};
|
||||
const allowUnknown = schema.allowUnknown === true;
|
||||
|
||||
if (!allowUnknown) {
|
||||
for (const key of Object.keys(target)) {
|
||||
if (!Object.prototype.hasOwnProperty.call(fields, key)) {
|
||||
errors.push({ field: key, issue: "unknown" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const [key, rule] of Object.entries(fields)) {
|
||||
const validated = validateField(target[key], key, rule, errors);
|
||||
if (validated !== undefined) {
|
||||
output[key] = validated;
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new ValidationError(`Invalid ${locationName}.`, errors);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
function validateRequest(req, spec = {}) {
|
||||
return {
|
||||
params: spec.params ? validateObject(req.params, spec.params, "path parameters") : {},
|
||||
query: spec.query ? validateObject(req.query, spec.query, "query parameters") : {},
|
||||
body: spec.body ? validateObject(req.body, spec.body, "request body") : {},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ValidationError,
|
||||
validateRequest,
|
||||
};
|
||||
Reference in New Issue
Block a user