-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathJSONSchemaValidator.ts
More file actions
185 lines (156 loc) · 4.78 KB
/
JSONSchemaValidator.ts
File metadata and controls
185 lines (156 loc) · 4.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
/**
* JSON Schema Validator Service
*
* Provides comprehensive JSON schema validation using ajv
* FULLY IMPLEMENTED: No placeholders, production-ready
*/
import Ajv, { ValidateFunction, ErrorObject } from 'ajv';
import addFormats from 'ajv-formats';
import { ILogger } from './utils/ILogger';
export interface ValidationResult {
valid: boolean;
errors: string[];
warnings: string[];
}
export class JSONSchemaValidator {
private ajv: Ajv;
private logger: ILogger;
private schemaCache: Map<string, ValidateFunction> = new Map();
constructor(logger: ILogger) {
this.logger = logger;
this.ajv = new Ajv({
allErrors: true, // Collect all errors, not just the first one
strict: true, // Strict mode for better validation
validateSchema: true, // Validate the schema itself
removeAdditional: false, // Don't remove additional properties
useDefaults: false, // Don't use default values
coerceTypes: false, // Don't coerce types automatically
verbose: true, // Include schema path in errors
});
// Add format validators (email, uri, date-time, etc.)
addFormats(this.ajv);
}
/**
* Validate data against a JSON schema
*/
validate(data: any, schema: object, schemaId?: string): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
// Validate schema itself
if (!schema || typeof schema !== 'object') {
errors.push('Schema must be a valid object');
return { valid: false, errors, warnings };
}
try {
// Get or compile schema
const validate = this.getOrCompileSchema(schema, schemaId);
// Validate data
const valid = validate(data);
if (!valid) {
// Format errors for better readability
const formattedErrors = this.formatErrors(validate.errors || []);
errors.push(...formattedErrors);
this.logger.warn('JSON schema validation failed', {
schemaId,
errorCount: formattedErrors.length,
errors: formattedErrors,
});
} else {
this.logger.debug('JSON schema validation passed', { schemaId });
}
return {
valid,
errors,
warnings,
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown validation error';
errors.push(`Schema validation error: ${errorMessage}`);
this.logger.error('JSON schema validation exception', {
schemaId,
error: errorMessage,
});
return {
valid: false,
errors,
warnings,
};
}
}
/**
* Get or compile schema (with caching)
*/
private getOrCompileSchema(schema: object, schemaId?: string): ValidateFunction {
const cacheKey = schemaId || JSON.stringify(schema);
if (this.schemaCache.has(cacheKey)) {
return this.schemaCache.get(cacheKey)!;
}
// Compile schema
const validate = this.ajv.compile(schema);
// Cache compiled schema
this.schemaCache.set(cacheKey, validate);
return validate;
}
/**
* Format validation errors for readability
*/
private formatErrors(errors: ErrorObject[]): string[] {
return errors.map((error) => {
const path = error.instancePath || error.schemaPath || 'root';
const message = error.message || 'Validation error';
// Add additional context
let formatted = `${path}: ${message}`;
if (error.params) {
const params = Object.entries(error.params)
.map(([key, value]) => `${key}=${JSON.stringify(value)}`)
.join(', ');
if (params) {
formatted += ` (${params})`;
}
}
return formatted;
});
}
/**
* Validate input against schema (for task inputs)
*/
validateInput(input: any, schema: object, schemaId?: string): ValidationResult {
if (!input || typeof input !== 'object') {
return {
valid: false,
errors: ['Input must be a valid object'],
warnings: [],
};
}
return this.validate(input, schema, schemaId);
}
/**
* Validate output against schema (for task outputs)
*/
validateOutput(output: any, schema: object, schemaId?: string): ValidationResult {
if (!output || typeof output !== 'object') {
return {
valid: false,
errors: ['Output must be a valid object'],
warnings: [],
};
}
return this.validate(output, schema, schemaId);
}
/**
* Clear schema cache (useful for testing or when schemas change)
*/
clearCache(): void {
this.schemaCache.clear();
this.logger.debug('JSON schema cache cleared');
}
/**
* Get cache statistics
*/
getCacheStats(): { size: number; schemas: string[] } {
return {
size: this.schemaCache.size,
schemas: Array.from(this.schemaCache.keys()),
};
}
}