feat(backend): add csv_analysis_config validator

This commit is contained in:
Experiments DB Dev
2026-05-01 07:47:17 -04:00
parent 7b023fb8a3
commit 1ae55591f4
2 changed files with 112 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
const REQUIRED_DEFAULT_BUCKETS = ['Success', 'Failure', 'Other'];
const ALLOWED_TYPES = new Set(['note', 'numerical']);
/**
* Validate a csv_analysis_config payload.
* @param {unknown} body
* @returns {string|null} error message, or null if valid.
*/
function validateCsvConfig(body) {
if (body === null || typeof body !== 'object' || Array.isArray(body)) {
return 'Body must be an object';
}
const { buckets } = body;
if (!Array.isArray(buckets)) {
return 'buckets must be an array';
}
const seenNames = new Set();
for (const b of buckets) {
if (b === null || typeof b !== 'object' || Array.isArray(b)) {
return 'Each bucket must be an object';
}
if (typeof b.name !== 'string' || b.name.trim() === '') {
return 'Each bucket must have a non-empty name';
}
if (seenNames.has(b.name)) {
return `Duplicate bucket name: ${b.name}`;
}
seenNames.add(b.name);
if (!ALLOWED_TYPES.has(b.type)) {
return `Invalid type for bucket ${b.name}: ${b.type}`;
}
if (!Array.isArray(b.notes)) {
return `notes must be an array for bucket ${b.name}`;
}
if (!b.notes.every((n) => typeof n === 'string')) {
return `notes must be an array of strings for bucket ${b.name}`;
}
}
for (const required of REQUIRED_DEFAULT_BUCKETS) {
if (!seenNames.has(required)) {
return `Missing required default bucket: ${required}`;
}
}
return null;
}
module.exports = { validateCsvConfig, REQUIRED_DEFAULT_BUCKETS };