193 lines
No EOL
4.6 KiB
TypeScript
193 lines
No EOL
4.6 KiB
TypeScript
import { z } from 'zod';
|
|
import { ConfigValidationError } from '../errors';
|
|
|
|
export interface ValidationResult {
|
|
valid: boolean;
|
|
errors?: Array<{
|
|
path: string;
|
|
message: string;
|
|
expected?: string;
|
|
received?: string;
|
|
}>;
|
|
warnings?: Array<{
|
|
path: string;
|
|
message: string;
|
|
}>;
|
|
}
|
|
|
|
/**
|
|
* Validate configuration against a schema
|
|
*/
|
|
export function validateConfig<T>(
|
|
config: unknown,
|
|
schema: z.ZodSchema<T>
|
|
): ValidationResult {
|
|
try {
|
|
schema.parse(config);
|
|
return { valid: true };
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
const errors = error.errors.map(err => ({
|
|
path: err.path.join('.'),
|
|
message: err.message,
|
|
expected: 'expected' in err ? String(err.expected) : undefined,
|
|
received: 'received' in err ? String(err.received) : undefined,
|
|
}));
|
|
|
|
return { valid: false, errors };
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check for deprecated configuration options
|
|
*/
|
|
export function checkDeprecations(
|
|
config: Record<string, any>,
|
|
deprecations: Record<string, string>
|
|
): ValidationResult['warnings'] {
|
|
const warnings: ValidationResult['warnings'] = [];
|
|
|
|
function checkObject(obj: any, path: string[] = []): void {
|
|
for (const [key, value] of Object.entries(obj)) {
|
|
const currentPath = [...path, key];
|
|
const pathStr = currentPath.join('.');
|
|
|
|
if (pathStr in deprecations) {
|
|
warnings?.push({
|
|
path: pathStr,
|
|
message: deprecations[pathStr],
|
|
});
|
|
}
|
|
|
|
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
checkObject(value, currentPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
checkObject(config);
|
|
return warnings;
|
|
}
|
|
|
|
/**
|
|
* Check for required environment variables
|
|
*/
|
|
export function checkRequiredEnvVars(
|
|
required: string[]
|
|
): ValidationResult {
|
|
const errors: ValidationResult['errors'] = [];
|
|
|
|
for (const envVar of required) {
|
|
if (!process.env[envVar]) {
|
|
errors.push({
|
|
path: `env.${envVar}`,
|
|
message: `Required environment variable ${envVar} is not set`,
|
|
});
|
|
}
|
|
}
|
|
|
|
return {
|
|
valid: errors.length === 0,
|
|
errors: errors.length > 0 ? errors : undefined,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Validate configuration completeness
|
|
*/
|
|
export function validateCompleteness(
|
|
config: Record<string, any>,
|
|
required: string[]
|
|
): ValidationResult {
|
|
const errors: ValidationResult['errors'] = [];
|
|
|
|
for (const path of required) {
|
|
const keys = path.split('.');
|
|
let current: any = config;
|
|
let found = true;
|
|
|
|
for (const key of keys) {
|
|
if (current && typeof current === 'object' && key in current) {
|
|
current = current[key];
|
|
} else {
|
|
found = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found || current === undefined || current === null) {
|
|
errors.push({
|
|
path,
|
|
message: `Required configuration value is missing`,
|
|
});
|
|
}
|
|
}
|
|
|
|
return {
|
|
valid: errors.length === 0,
|
|
errors: errors.length > 0 ? errors : undefined,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Format validation result for display
|
|
*/
|
|
export function formatValidationResult(result: ValidationResult): string {
|
|
const lines: string[] = [];
|
|
|
|
if (result.valid) {
|
|
lines.push('✅ Configuration is valid');
|
|
} else {
|
|
lines.push('❌ Configuration validation failed');
|
|
}
|
|
|
|
if (result.errors && result.errors.length > 0) {
|
|
lines.push('\nErrors:');
|
|
for (const error of result.errors) {
|
|
lines.push(` - ${error.path}: ${error.message}`);
|
|
if (error.expected && error.received) {
|
|
lines.push(` Expected: ${error.expected}, Received: ${error.received}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (result.warnings && result.warnings.length > 0) {
|
|
lines.push('\nWarnings:');
|
|
for (const warning of result.warnings) {
|
|
lines.push(` - ${warning.path}: ${warning.message}`);
|
|
}
|
|
}
|
|
|
|
return lines.join('\n');
|
|
}
|
|
|
|
/**
|
|
* Create a strict schema that doesn't allow extra properties
|
|
*/
|
|
export function createStrictSchema<T extends z.ZodRawShape>(
|
|
shape: T
|
|
): z.ZodObject<T, 'strict'> {
|
|
return z.object(shape).strict();
|
|
}
|
|
|
|
/**
|
|
* Merge multiple schemas
|
|
*/
|
|
export function mergeSchemas<T extends z.ZodSchema[]>(
|
|
...schemas: T
|
|
): z.ZodIntersection<T[0], T[1]> {
|
|
if (schemas.length < 2) {
|
|
throw new Error('At least two schemas required for merge');
|
|
}
|
|
|
|
let result = schemas[0].and(schemas[1]);
|
|
|
|
for (let i = 2; i < schemas.length; i++) {
|
|
result = result.and(schemas[i]) as any;
|
|
}
|
|
|
|
return result as any;
|
|
} |