phase 8 server-side validation (configSchema, inline field errors, partial-success semantics)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { CONFIG } = require('../config');
|
||||
const { getValidator } = require('./configSchema');
|
||||
|
||||
const ENV_PATH = process.env.ENV_FILE
|
||||
? path.resolve(process.env.ENV_FILE)
|
||||
@@ -64,33 +65,40 @@ function writeEnvFile(updates) {
|
||||
|
||||
/**
|
||||
* Apply a flat object of { KEY: value } to both CONFIG and .env.
|
||||
* Returns { applied: string[], errors: string[] }
|
||||
* Returns { applied: string[], errors: Array<{ key, error }> }.
|
||||
*
|
||||
* Every value is routed through services/configSchema so invalid inputs are
|
||||
* rejected before being persisted. Valid values are typed via the validator's
|
||||
* coerced return (boolean/number/string) into CONFIG; .env always stores
|
||||
* String(coerced). Invalid values are reported and NEITHER written to .env NOR
|
||||
* applied to CONFIG — callers see them in the `errors` array.
|
||||
*/
|
||||
function applyConfigUpdates(updates) {
|
||||
const applied = [];
|
||||
const errors = [];
|
||||
const coercedForEnv = new Map();
|
||||
|
||||
for (const [key, rawValue] of Object.entries(updates)) {
|
||||
try {
|
||||
if (rawValue === 'true' || rawValue === 'false') {
|
||||
CONFIG[key] = rawValue === 'true';
|
||||
} else if (!isNaN(rawValue) && rawValue !== '') {
|
||||
CONFIG[key] = Number(rawValue);
|
||||
} else {
|
||||
CONFIG[key] = rawValue;
|
||||
}
|
||||
applied.push(key);
|
||||
} catch (err) {
|
||||
errors.push(`${key}: ${err.message}`);
|
||||
const validator = getValidator(key);
|
||||
const result = validator.validate(rawValue);
|
||||
if (!result.ok) {
|
||||
errors.push({ key, error: result.error });
|
||||
continue;
|
||||
}
|
||||
CONFIG[key] = result.coerced;
|
||||
coercedForEnv.set(key, String(result.coerced));
|
||||
applied.push(key);
|
||||
}
|
||||
|
||||
// Write to .env
|
||||
const envMap = readEnvFile();
|
||||
for (const [key, value] of Object.entries(updates)) {
|
||||
envMap.set(key, String(value));
|
||||
// Write only the valid entries to .env. Skip the disk write entirely when
|
||||
// nothing validated, so a fully-rejected save doesn't rewrite the file.
|
||||
if (applied.length > 0) {
|
||||
const envMap = readEnvFile();
|
||||
for (const [key, value] of coercedForEnv) {
|
||||
envMap.set(key, value);
|
||||
}
|
||||
writeEnvFile(envMap);
|
||||
}
|
||||
writeEnvFile(envMap);
|
||||
|
||||
return { applied, errors };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user