driftdetect-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analyzers/ast-analyzer.d.ts +251 -0
- package/dist/analyzers/ast-analyzer.d.ts.map +1 -0
- package/dist/analyzers/ast-analyzer.js +548 -0
- package/dist/analyzers/ast-analyzer.js.map +1 -0
- package/dist/analyzers/flow-analyzer.d.ts +241 -0
- package/dist/analyzers/flow-analyzer.d.ts.map +1 -0
- package/dist/analyzers/flow-analyzer.js +1219 -0
- package/dist/analyzers/flow-analyzer.js.map +1 -0
- package/dist/analyzers/index.d.ts +18 -0
- package/dist/analyzers/index.d.ts.map +1 -0
- package/dist/analyzers/index.js +19 -0
- package/dist/analyzers/index.js.map +1 -0
- package/dist/analyzers/semantic-analyzer.d.ts +252 -0
- package/dist/analyzers/semantic-analyzer.d.ts.map +1 -0
- package/dist/analyzers/semantic-analyzer.js +1182 -0
- package/dist/analyzers/semantic-analyzer.js.map +1 -0
- package/dist/analyzers/type-analyzer.d.ts +289 -0
- package/dist/analyzers/type-analyzer.d.ts.map +1 -0
- package/dist/analyzers/type-analyzer.js +1269 -0
- package/dist/analyzers/type-analyzer.js.map +1 -0
- package/dist/analyzers/types.d.ts +537 -0
- package/dist/analyzers/types.d.ts.map +1 -0
- package/dist/analyzers/types.js +11 -0
- package/dist/analyzers/types.js.map +1 -0
- package/dist/config/config-loader.d.ts +166 -0
- package/dist/config/config-loader.d.ts.map +1 -0
- package/dist/config/config-loader.js +429 -0
- package/dist/config/config-loader.js.map +1 -0
- package/dist/config/config-validator.d.ts +204 -0
- package/dist/config/config-validator.d.ts.map +1 -0
- package/dist/config/config-validator.js +632 -0
- package/dist/config/config-validator.js.map +1 -0
- package/dist/config/defaults.d.ts +8 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +26 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +10 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +10 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/types.d.ts +47 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +7 -0
- package/dist/config/types.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest/exporter.d.ts +21 -0
- package/dist/manifest/exporter.d.ts.map +1 -0
- package/dist/manifest/exporter.js +339 -0
- package/dist/manifest/exporter.js.map +1 -0
- package/dist/manifest/index.d.ts +14 -0
- package/dist/manifest/index.d.ts.map +1 -0
- package/dist/manifest/index.js +15 -0
- package/dist/manifest/index.js.map +1 -0
- package/dist/manifest/manifest-store.d.ts +111 -0
- package/dist/manifest/manifest-store.d.ts.map +1 -0
- package/dist/manifest/manifest-store.js +418 -0
- package/dist/manifest/manifest-store.js.map +1 -0
- package/dist/manifest/types.d.ts +238 -0
- package/dist/manifest/types.d.ts.map +1 -0
- package/dist/manifest/types.js +11 -0
- package/dist/manifest/types.js.map +1 -0
- package/dist/matcher/confidence-scorer.d.ts +188 -0
- package/dist/matcher/confidence-scorer.d.ts.map +1 -0
- package/dist/matcher/confidence-scorer.js +302 -0
- package/dist/matcher/confidence-scorer.js.map +1 -0
- package/dist/matcher/index.d.ts +24 -0
- package/dist/matcher/index.d.ts.map +1 -0
- package/dist/matcher/index.js +26 -0
- package/dist/matcher/index.js.map +1 -0
- package/dist/matcher/outlier-detector.d.ts +252 -0
- package/dist/matcher/outlier-detector.d.ts.map +1 -0
- package/dist/matcher/outlier-detector.js +544 -0
- package/dist/matcher/outlier-detector.js.map +1 -0
- package/dist/matcher/pattern-matcher.d.ts +169 -0
- package/dist/matcher/pattern-matcher.d.ts.map +1 -0
- package/dist/matcher/pattern-matcher.js +692 -0
- package/dist/matcher/pattern-matcher.js.map +1 -0
- package/dist/matcher/types.d.ts +476 -0
- package/dist/matcher/types.d.ts.map +1 -0
- package/dist/matcher/types.js +36 -0
- package/dist/matcher/types.js.map +1 -0
- package/dist/parsers/base-parser.d.ts +282 -0
- package/dist/parsers/base-parser.d.ts.map +1 -0
- package/dist/parsers/base-parser.js +421 -0
- package/dist/parsers/base-parser.js.map +1 -0
- package/dist/parsers/css-parser.d.ts +225 -0
- package/dist/parsers/css-parser.d.ts.map +1 -0
- package/dist/parsers/css-parser.js +477 -0
- package/dist/parsers/css-parser.js.map +1 -0
- package/dist/parsers/index.d.ts +15 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +15 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/parsers/json-parser.d.ts +219 -0
- package/dist/parsers/json-parser.d.ts.map +1 -0
- package/dist/parsers/json-parser.js +602 -0
- package/dist/parsers/json-parser.js.map +1 -0
- package/dist/parsers/markdown-parser.d.ts +276 -0
- package/dist/parsers/markdown-parser.d.ts.map +1 -0
- package/dist/parsers/markdown-parser.js +731 -0
- package/dist/parsers/markdown-parser.js.map +1 -0
- package/dist/parsers/parser-manager.d.ts +294 -0
- package/dist/parsers/parser-manager.d.ts.map +1 -0
- package/dist/parsers/parser-manager.js +738 -0
- package/dist/parsers/parser-manager.js.map +1 -0
- package/dist/parsers/python-parser.d.ts +204 -0
- package/dist/parsers/python-parser.d.ts.map +1 -0
- package/dist/parsers/python-parser.js +517 -0
- package/dist/parsers/python-parser.js.map +1 -0
- package/dist/parsers/types.d.ts +43 -0
- package/dist/parsers/types.d.ts.map +1 -0
- package/dist/parsers/types.js +7 -0
- package/dist/parsers/types.js.map +1 -0
- package/dist/parsers/typescript-parser.d.ts +264 -0
- package/dist/parsers/typescript-parser.d.ts.map +1 -0
- package/dist/parsers/typescript-parser.js +658 -0
- package/dist/parsers/typescript-parser.js.map +1 -0
- package/dist/rules/evaluator.d.ts +305 -0
- package/dist/rules/evaluator.d.ts.map +1 -0
- package/dist/rules/evaluator.js +579 -0
- package/dist/rules/evaluator.js.map +1 -0
- package/dist/rules/index.d.ts +13 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +13 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/quick-fix-generator.d.ts +334 -0
- package/dist/rules/quick-fix-generator.d.ts.map +1 -0
- package/dist/rules/quick-fix-generator.js +1075 -0
- package/dist/rules/quick-fix-generator.js.map +1 -0
- package/dist/rules/rule-engine.d.ts +241 -0
- package/dist/rules/rule-engine.d.ts.map +1 -0
- package/dist/rules/rule-engine.js +585 -0
- package/dist/rules/rule-engine.js.map +1 -0
- package/dist/rules/severity-manager.d.ts +394 -0
- package/dist/rules/severity-manager.d.ts.map +1 -0
- package/dist/rules/severity-manager.js +619 -0
- package/dist/rules/severity-manager.js.map +1 -0
- package/dist/rules/types.d.ts +370 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +133 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/rules/variant-manager.d.ts +388 -0
- package/dist/rules/variant-manager.d.ts.map +1 -0
- package/dist/rules/variant-manager.js +777 -0
- package/dist/rules/variant-manager.js.map +1 -0
- package/dist/scanner/change-detector.d.ts +164 -0
- package/dist/scanner/change-detector.d.ts.map +1 -0
- package/dist/scanner/change-detector.js +263 -0
- package/dist/scanner/change-detector.js.map +1 -0
- package/dist/scanner/dependency-graph.d.ts +270 -0
- package/dist/scanner/dependency-graph.d.ts.map +1 -0
- package/dist/scanner/dependency-graph.js +436 -0
- package/dist/scanner/dependency-graph.js.map +1 -0
- package/dist/scanner/file-walker.d.ts +127 -0
- package/dist/scanner/file-walker.d.ts.map +1 -0
- package/dist/scanner/file-walker.js +526 -0
- package/dist/scanner/file-walker.js.map +1 -0
- package/dist/scanner/index.d.ts +12 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +12 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/scanner/types.d.ts +218 -0
- package/dist/scanner/types.d.ts.map +1 -0
- package/dist/scanner/types.js +10 -0
- package/dist/scanner/types.js.map +1 -0
- package/dist/scanner/worker-pool.d.ts +317 -0
- package/dist/scanner/worker-pool.d.ts.map +1 -0
- package/dist/scanner/worker-pool.js +571 -0
- package/dist/scanner/worker-pool.js.map +1 -0
- package/dist/store/cache-manager.d.ts +179 -0
- package/dist/store/cache-manager.d.ts.map +1 -0
- package/dist/store/cache-manager.js +391 -0
- package/dist/store/cache-manager.js.map +1 -0
- package/dist/store/history-store.d.ts +314 -0
- package/dist/store/history-store.d.ts.map +1 -0
- package/dist/store/history-store.js +707 -0
- package/dist/store/history-store.js.map +1 -0
- package/dist/store/index.d.ts +20 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +26 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/lock-file-manager.d.ts +202 -0
- package/dist/store/lock-file-manager.d.ts.map +1 -0
- package/dist/store/lock-file-manager.js +475 -0
- package/dist/store/lock-file-manager.js.map +1 -0
- package/dist/store/pattern-store.d.ts +289 -0
- package/dist/store/pattern-store.d.ts.map +1 -0
- package/dist/store/pattern-store.js +936 -0
- package/dist/store/pattern-store.js.map +1 -0
- package/dist/store/schema-validator.d.ts +159 -0
- package/dist/store/schema-validator.d.ts.map +1 -0
- package/dist/store/schema-validator.js +1096 -0
- package/dist/store/schema-validator.js.map +1 -0
- package/dist/store/types.d.ts +585 -0
- package/dist/store/types.d.ts.map +1 -0
- package/dist/store/types.js +82 -0
- package/dist/store/types.js.map +1 -0
- package/dist/types/analysis.d.ts +19 -0
- package/dist/types/analysis.d.ts.map +1 -0
- package/dist/types/analysis.js +5 -0
- package/dist/types/analysis.js.map +1 -0
- package/dist/types/common.d.ts +7 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +5 -0
- package/dist/types/common.js.map +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +10 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/patterns.d.ts +40 -0
- package/dist/types/patterns.d.ts.map +1 -0
- package/dist/types/patterns.js +7 -0
- package/dist/types/patterns.js.map +1 -0
- package/dist/types/violations.d.ts +7 -0
- package/dist/types/violations.d.ts.map +1 -0
- package/dist/types/violations.js +7 -0
- package/dist/types/violations.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,1096 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema Validator - JSON schema validation for patterns and config
|
|
3
|
+
*
|
|
4
|
+
* Provides comprehensive JSON schema validation for all store data structures
|
|
5
|
+
* including patterns, pattern files, history files, lock files, and variants.
|
|
6
|
+
*
|
|
7
|
+
* @requirements 4.5 - Pattern schema SHALL be validated on load/save
|
|
8
|
+
*/
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Schema Version Constants
|
|
11
|
+
// ============================================================================
|
|
12
|
+
/**
|
|
13
|
+
* Current schema versions for validation
|
|
14
|
+
*/
|
|
15
|
+
export const SCHEMA_VERSIONS = {
|
|
16
|
+
pattern: '1.0.0',
|
|
17
|
+
patternFile: '1.0.0',
|
|
18
|
+
historyFile: '1.0.0',
|
|
19
|
+
lockFile: '1.0.0',
|
|
20
|
+
variantsFile: '1.0.0',
|
|
21
|
+
config: '1.0.0',
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Supported schema versions for backward compatibility
|
|
25
|
+
*/
|
|
26
|
+
export const SUPPORTED_VERSIONS = {
|
|
27
|
+
pattern: ['1.0.0'],
|
|
28
|
+
patternFile: ['1.0.0'],
|
|
29
|
+
historyFile: ['1.0.0'],
|
|
30
|
+
lockFile: ['1.0.0'],
|
|
31
|
+
variantsFile: ['1.0.0'],
|
|
32
|
+
config: ['1.0.0'],
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Custom error class for schema validation failures
|
|
36
|
+
*/
|
|
37
|
+
export class SchemaValidationError extends Error {
|
|
38
|
+
errors;
|
|
39
|
+
schemaType;
|
|
40
|
+
constructor(message, errors, schemaType) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.errors = errors;
|
|
43
|
+
this.schemaType = schemaType;
|
|
44
|
+
this.name = 'SchemaValidationError';
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Format errors as a human-readable string
|
|
48
|
+
*/
|
|
49
|
+
formatErrors() {
|
|
50
|
+
return this.errors
|
|
51
|
+
.map((e) => ` - ${e.path}: ${e.message}`)
|
|
52
|
+
.join('\n');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// ============================================================================
|
|
56
|
+
// Valid Value Sets
|
|
57
|
+
// ============================================================================
|
|
58
|
+
const VALID_PATTERN_CATEGORIES = [
|
|
59
|
+
'structural',
|
|
60
|
+
'components',
|
|
61
|
+
'styling',
|
|
62
|
+
'api',
|
|
63
|
+
'auth',
|
|
64
|
+
'errors',
|
|
65
|
+
'data-access',
|
|
66
|
+
'testing',
|
|
67
|
+
'logging',
|
|
68
|
+
'security',
|
|
69
|
+
'config',
|
|
70
|
+
'types',
|
|
71
|
+
'performance',
|
|
72
|
+
'accessibility',
|
|
73
|
+
'documentation',
|
|
74
|
+
];
|
|
75
|
+
const VALID_PATTERN_STATUSES = ['discovered', 'approved', 'ignored'];
|
|
76
|
+
const VALID_SEVERITIES = ['error', 'warning', 'info', 'hint'];
|
|
77
|
+
const VALID_CONFIDENCE_LEVELS = ['high', 'medium', 'low', 'uncertain'];
|
|
78
|
+
const VALID_DETECTOR_TYPES = ['ast', 'regex', 'semantic', 'structural', 'custom'];
|
|
79
|
+
const VALID_VARIANT_SCOPES = ['global', 'directory', 'file'];
|
|
80
|
+
const VALID_HISTORY_EVENT_TYPES = [
|
|
81
|
+
'created',
|
|
82
|
+
'approved',
|
|
83
|
+
'ignored',
|
|
84
|
+
'updated',
|
|
85
|
+
'deleted',
|
|
86
|
+
'confidence_changed',
|
|
87
|
+
'locations_changed',
|
|
88
|
+
'severity_changed',
|
|
89
|
+
];
|
|
90
|
+
const VALID_AI_PROVIDERS = ['openai', 'anthropic', 'ollama'];
|
|
91
|
+
const VALID_CI_FAIL_ON = ['error', 'warning', 'none'];
|
|
92
|
+
const VALID_REPORT_FORMATS = ['json', 'text', 'github', 'gitlab'];
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// Helper Validation Functions
|
|
95
|
+
// ============================================================================
|
|
96
|
+
/**
|
|
97
|
+
* Check if a value is a non-null object
|
|
98
|
+
*/
|
|
99
|
+
function isObject(value) {
|
|
100
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Get a property from an object safely (for use with index signatures)
|
|
104
|
+
*/
|
|
105
|
+
function get(obj, key) {
|
|
106
|
+
return obj[key];
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Check if a value is a non-empty string
|
|
110
|
+
*/
|
|
111
|
+
function isNonEmptyString(value) {
|
|
112
|
+
return typeof value === 'string' && value.length > 0;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Check if a value is a valid ISO date string
|
|
116
|
+
*/
|
|
117
|
+
function isISODateString(value) {
|
|
118
|
+
if (typeof value !== 'string')
|
|
119
|
+
return false;
|
|
120
|
+
const date = new Date(value);
|
|
121
|
+
return !isNaN(date.getTime()) && value.includes('T');
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Check if a value is a number within a range
|
|
125
|
+
*/
|
|
126
|
+
function isNumberInRange(value, min, max) {
|
|
127
|
+
return typeof value === 'number' && !isNaN(value) && value >= min && value <= max;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Check if a value is a positive integer
|
|
131
|
+
*/
|
|
132
|
+
function isPositiveInteger(value) {
|
|
133
|
+
return typeof value === 'number' && Number.isInteger(value) && value >= 0;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Check if a value is in a valid set
|
|
137
|
+
*/
|
|
138
|
+
function isOneOf(value, validValues) {
|
|
139
|
+
return validValues.includes(value);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Check if a value is a valid semver version string
|
|
143
|
+
*/
|
|
144
|
+
function isSemverVersion(value) {
|
|
145
|
+
if (typeof value !== 'string')
|
|
146
|
+
return false;
|
|
147
|
+
return /^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?$/.test(value);
|
|
148
|
+
}
|
|
149
|
+
// ============================================================================
|
|
150
|
+
// Component Validators
|
|
151
|
+
// ============================================================================
|
|
152
|
+
/**
|
|
153
|
+
* Validate a PatternLocation object
|
|
154
|
+
*/
|
|
155
|
+
function validatePatternLocation(location, path, errors) {
|
|
156
|
+
if (!isObject(location)) {
|
|
157
|
+
errors.push({ path, message: 'Must be an object', expected: 'object', actual: typeof location });
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
let valid = true;
|
|
161
|
+
const file = get(location, 'file');
|
|
162
|
+
const line = get(location, 'line');
|
|
163
|
+
const column = get(location, 'column');
|
|
164
|
+
const endLine = get(location, 'endLine');
|
|
165
|
+
const endColumn = get(location, 'endColumn');
|
|
166
|
+
if (!isNonEmptyString(file)) {
|
|
167
|
+
errors.push({ path: `${path}.file`, message: 'Must be a non-empty string', expected: 'string', actual: file });
|
|
168
|
+
valid = false;
|
|
169
|
+
}
|
|
170
|
+
if (!isPositiveInteger(line) || line < 1) {
|
|
171
|
+
errors.push({ path: `${path}.line`, message: 'Must be a positive integer >= 1', expected: 'number >= 1', actual: line });
|
|
172
|
+
valid = false;
|
|
173
|
+
}
|
|
174
|
+
if (!isPositiveInteger(column) || column < 1) {
|
|
175
|
+
errors.push({ path: `${path}.column`, message: 'Must be a positive integer >= 1', expected: 'number >= 1', actual: column });
|
|
176
|
+
valid = false;
|
|
177
|
+
}
|
|
178
|
+
// Optional fields
|
|
179
|
+
if (endLine !== undefined && (!isPositiveInteger(endLine) || endLine < 1)) {
|
|
180
|
+
errors.push({ path: `${path}.endLine`, message: 'Must be a positive integer >= 1', expected: 'number >= 1', actual: endLine });
|
|
181
|
+
valid = false;
|
|
182
|
+
}
|
|
183
|
+
if (endColumn !== undefined && (!isPositiveInteger(endColumn) || endColumn < 1)) {
|
|
184
|
+
errors.push({ path: `${path}.endColumn`, message: 'Must be a positive integer >= 1', expected: 'number >= 1', actual: endColumn });
|
|
185
|
+
valid = false;
|
|
186
|
+
}
|
|
187
|
+
return valid;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Validate an OutlierLocation object
|
|
191
|
+
*/
|
|
192
|
+
function validateOutlierLocation(location, path, errors) {
|
|
193
|
+
if (!validatePatternLocation(location, path, errors)) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
const loc = location;
|
|
197
|
+
let valid = true;
|
|
198
|
+
const reason = get(loc, 'reason');
|
|
199
|
+
const deviationScore = get(loc, 'deviationScore');
|
|
200
|
+
if (!isNonEmptyString(reason)) {
|
|
201
|
+
errors.push({ path: `${path}.reason`, message: 'Must be a non-empty string', expected: 'string', actual: reason });
|
|
202
|
+
valid = false;
|
|
203
|
+
}
|
|
204
|
+
if (deviationScore !== undefined && !isNumberInRange(deviationScore, 0, 1)) {
|
|
205
|
+
errors.push({ path: `${path}.deviationScore`, message: 'Must be a number between 0 and 1', expected: '0-1', actual: deviationScore });
|
|
206
|
+
valid = false;
|
|
207
|
+
}
|
|
208
|
+
return valid;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Validate a ConfidenceInfo object
|
|
212
|
+
*/
|
|
213
|
+
function validateConfidenceInfo(confidence, path, errors) {
|
|
214
|
+
if (!isObject(confidence)) {
|
|
215
|
+
errors.push({ path, message: 'Must be an object', expected: 'object', actual: typeof confidence });
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
let valid = true;
|
|
219
|
+
const frequency = get(confidence, 'frequency');
|
|
220
|
+
const consistency = get(confidence, 'consistency');
|
|
221
|
+
const age = get(confidence, 'age');
|
|
222
|
+
const spread = get(confidence, 'spread');
|
|
223
|
+
const score = get(confidence, 'score');
|
|
224
|
+
const level = get(confidence, 'level');
|
|
225
|
+
if (!isNumberInRange(frequency, 0, 1)) {
|
|
226
|
+
errors.push({ path: `${path}.frequency`, message: 'Must be a number between 0 and 1', expected: '0-1', actual: frequency });
|
|
227
|
+
valid = false;
|
|
228
|
+
}
|
|
229
|
+
if (!isNumberInRange(consistency, 0, 1)) {
|
|
230
|
+
errors.push({ path: `${path}.consistency`, message: 'Must be a number between 0 and 1', expected: '0-1', actual: consistency });
|
|
231
|
+
valid = false;
|
|
232
|
+
}
|
|
233
|
+
if (!isPositiveInteger(age) && age !== 0) {
|
|
234
|
+
errors.push({ path: `${path}.age`, message: 'Must be a non-negative number', expected: 'number >= 0', actual: age });
|
|
235
|
+
valid = false;
|
|
236
|
+
}
|
|
237
|
+
if (!isPositiveInteger(spread)) {
|
|
238
|
+
errors.push({ path: `${path}.spread`, message: 'Must be a non-negative integer', expected: 'integer >= 0', actual: spread });
|
|
239
|
+
valid = false;
|
|
240
|
+
}
|
|
241
|
+
if (!isNumberInRange(score, 0, 1)) {
|
|
242
|
+
errors.push({ path: `${path}.score`, message: 'Must be a number between 0 and 1', expected: '0-1', actual: score });
|
|
243
|
+
valid = false;
|
|
244
|
+
}
|
|
245
|
+
if (!isOneOf(level, VALID_CONFIDENCE_LEVELS)) {
|
|
246
|
+
errors.push({ path: `${path}.level`, message: `Must be one of: ${VALID_CONFIDENCE_LEVELS.join(', ')}`, expected: VALID_CONFIDENCE_LEVELS.join('|'), actual: level });
|
|
247
|
+
valid = false;
|
|
248
|
+
}
|
|
249
|
+
return valid;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Validate a DetectorConfig object
|
|
253
|
+
*/
|
|
254
|
+
function validateDetectorConfig(detector, path, errors) {
|
|
255
|
+
if (!isObject(detector)) {
|
|
256
|
+
errors.push({ path, message: 'Must be an object', expected: 'object', actual: typeof detector });
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
let valid = true;
|
|
260
|
+
const type = get(detector, 'type');
|
|
261
|
+
const config = get(detector, 'config');
|
|
262
|
+
const ast = get(detector, 'ast');
|
|
263
|
+
const regex = get(detector, 'regex');
|
|
264
|
+
const structural = get(detector, 'structural');
|
|
265
|
+
const custom = get(detector, 'custom');
|
|
266
|
+
if (!isOneOf(type, VALID_DETECTOR_TYPES)) {
|
|
267
|
+
errors.push({ path: `${path}.type`, message: `Must be one of: ${VALID_DETECTOR_TYPES.join(', ')}`, expected: VALID_DETECTOR_TYPES.join('|'), actual: type });
|
|
268
|
+
valid = false;
|
|
269
|
+
}
|
|
270
|
+
if (!isObject(config)) {
|
|
271
|
+
errors.push({ path: `${path}.config`, message: 'Must be an object', expected: 'object', actual: typeof config });
|
|
272
|
+
valid = false;
|
|
273
|
+
}
|
|
274
|
+
// Type-specific validation is optional - just ensure the objects exist if provided
|
|
275
|
+
if (ast !== undefined && !isObject(ast)) {
|
|
276
|
+
errors.push({ path: `${path}.ast`, message: 'Must be an object if provided', expected: 'object', actual: typeof ast });
|
|
277
|
+
valid = false;
|
|
278
|
+
}
|
|
279
|
+
if (regex !== undefined && !isObject(regex)) {
|
|
280
|
+
errors.push({ path: `${path}.regex`, message: 'Must be an object if provided', expected: 'object', actual: typeof regex });
|
|
281
|
+
valid = false;
|
|
282
|
+
}
|
|
283
|
+
if (structural !== undefined && !isObject(structural)) {
|
|
284
|
+
errors.push({ path: `${path}.structural`, message: 'Must be an object if provided', expected: 'object', actual: typeof structural });
|
|
285
|
+
valid = false;
|
|
286
|
+
}
|
|
287
|
+
if (custom !== undefined && !isObject(custom)) {
|
|
288
|
+
errors.push({ path: `${path}.custom`, message: 'Must be an object if provided', expected: 'object', actual: typeof custom });
|
|
289
|
+
valid = false;
|
|
290
|
+
}
|
|
291
|
+
return valid;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Validate a PatternMetadata object
|
|
295
|
+
*/
|
|
296
|
+
function validatePatternMetadata(metadata, path, errors) {
|
|
297
|
+
if (!isObject(metadata)) {
|
|
298
|
+
errors.push({ path, message: 'Must be an object', expected: 'object', actual: typeof metadata });
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
let valid = true;
|
|
302
|
+
const firstSeen = get(metadata, 'firstSeen');
|
|
303
|
+
const lastSeen = get(metadata, 'lastSeen');
|
|
304
|
+
const approvedAt = get(metadata, 'approvedAt');
|
|
305
|
+
const approvedBy = get(metadata, 'approvedBy');
|
|
306
|
+
const version = get(metadata, 'version');
|
|
307
|
+
const tags = get(metadata, 'tags');
|
|
308
|
+
const relatedPatterns = get(metadata, 'relatedPatterns');
|
|
309
|
+
const source = get(metadata, 'source');
|
|
310
|
+
const custom = get(metadata, 'custom');
|
|
311
|
+
if (!isISODateString(firstSeen)) {
|
|
312
|
+
errors.push({ path: `${path}.firstSeen`, message: 'Must be a valid ISO date string', expected: 'ISO date string', actual: firstSeen });
|
|
313
|
+
valid = false;
|
|
314
|
+
}
|
|
315
|
+
if (!isISODateString(lastSeen)) {
|
|
316
|
+
errors.push({ path: `${path}.lastSeen`, message: 'Must be a valid ISO date string', expected: 'ISO date string', actual: lastSeen });
|
|
317
|
+
valid = false;
|
|
318
|
+
}
|
|
319
|
+
// Optional fields
|
|
320
|
+
if (approvedAt !== undefined && !isISODateString(approvedAt)) {
|
|
321
|
+
errors.push({ path: `${path}.approvedAt`, message: 'Must be a valid ISO date string', expected: 'ISO date string', actual: approvedAt });
|
|
322
|
+
valid = false;
|
|
323
|
+
}
|
|
324
|
+
if (approvedBy !== undefined && typeof approvedBy !== 'string') {
|
|
325
|
+
errors.push({ path: `${path}.approvedBy`, message: 'Must be a string', expected: 'string', actual: typeof approvedBy });
|
|
326
|
+
valid = false;
|
|
327
|
+
}
|
|
328
|
+
if (version !== undefined && typeof version !== 'string') {
|
|
329
|
+
errors.push({ path: `${path}.version`, message: 'Must be a string', expected: 'string', actual: typeof version });
|
|
330
|
+
valid = false;
|
|
331
|
+
}
|
|
332
|
+
if (tags !== undefined) {
|
|
333
|
+
if (!Array.isArray(tags)) {
|
|
334
|
+
errors.push({ path: `${path}.tags`, message: 'Must be an array', expected: 'array', actual: typeof tags });
|
|
335
|
+
valid = false;
|
|
336
|
+
}
|
|
337
|
+
else if (!tags.every((t) => typeof t === 'string')) {
|
|
338
|
+
errors.push({ path: `${path}.tags`, message: 'All tags must be strings', expected: 'string[]', actual: tags });
|
|
339
|
+
valid = false;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (relatedPatterns !== undefined) {
|
|
343
|
+
if (!Array.isArray(relatedPatterns)) {
|
|
344
|
+
errors.push({ path: `${path}.relatedPatterns`, message: 'Must be an array', expected: 'array', actual: typeof relatedPatterns });
|
|
345
|
+
valid = false;
|
|
346
|
+
}
|
|
347
|
+
else if (!relatedPatterns.every((p) => typeof p === 'string')) {
|
|
348
|
+
errors.push({ path: `${path}.relatedPatterns`, message: 'All related patterns must be strings', expected: 'string[]', actual: relatedPatterns });
|
|
349
|
+
valid = false;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (source !== undefined && typeof source !== 'string') {
|
|
353
|
+
errors.push({ path: `${path}.source`, message: 'Must be a string', expected: 'string', actual: typeof source });
|
|
354
|
+
valid = false;
|
|
355
|
+
}
|
|
356
|
+
if (custom !== undefined && !isObject(custom)) {
|
|
357
|
+
errors.push({ path: `${path}.custom`, message: 'Must be an object', expected: 'object', actual: typeof custom });
|
|
358
|
+
valid = false;
|
|
359
|
+
}
|
|
360
|
+
return valid;
|
|
361
|
+
}
|
|
362
|
+
// ============================================================================
|
|
363
|
+
// Pattern Validators
|
|
364
|
+
// ============================================================================
|
|
365
|
+
/**
|
|
366
|
+
* Validate a StoredPattern object
|
|
367
|
+
*/
|
|
368
|
+
function validateStoredPattern(pattern, path, errors) {
|
|
369
|
+
if (!isObject(pattern)) {
|
|
370
|
+
errors.push({ path, message: 'Must be an object', expected: 'object', actual: typeof pattern });
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
let valid = true;
|
|
374
|
+
const id = get(pattern, 'id');
|
|
375
|
+
const subcategory = get(pattern, 'subcategory');
|
|
376
|
+
const name = get(pattern, 'name');
|
|
377
|
+
const description = get(pattern, 'description');
|
|
378
|
+
const severity = get(pattern, 'severity');
|
|
379
|
+
const autoFixable = get(pattern, 'autoFixable');
|
|
380
|
+
const detector = get(pattern, 'detector');
|
|
381
|
+
const confidence = get(pattern, 'confidence');
|
|
382
|
+
const metadata = get(pattern, 'metadata');
|
|
383
|
+
const locations = get(pattern, 'locations');
|
|
384
|
+
const outliers = get(pattern, 'outliers');
|
|
385
|
+
// Required string fields
|
|
386
|
+
if (!isNonEmptyString(id)) {
|
|
387
|
+
errors.push({ path: `${path}.id`, message: 'Must be a non-empty string', expected: 'string', actual: id });
|
|
388
|
+
valid = false;
|
|
389
|
+
}
|
|
390
|
+
if (!isNonEmptyString(subcategory)) {
|
|
391
|
+
errors.push({ path: `${path}.subcategory`, message: 'Must be a non-empty string', expected: 'string', actual: subcategory });
|
|
392
|
+
valid = false;
|
|
393
|
+
}
|
|
394
|
+
if (!isNonEmptyString(name)) {
|
|
395
|
+
errors.push({ path: `${path}.name`, message: 'Must be a non-empty string', expected: 'string', actual: name });
|
|
396
|
+
valid = false;
|
|
397
|
+
}
|
|
398
|
+
if (!isNonEmptyString(description)) {
|
|
399
|
+
errors.push({ path: `${path}.description`, message: 'Must be a non-empty string', expected: 'string', actual: description });
|
|
400
|
+
valid = false;
|
|
401
|
+
}
|
|
402
|
+
// Enum fields
|
|
403
|
+
if (!isOneOf(severity, VALID_SEVERITIES)) {
|
|
404
|
+
errors.push({ path: `${path}.severity`, message: `Must be one of: ${VALID_SEVERITIES.join(', ')}`, expected: VALID_SEVERITIES.join('|'), actual: severity });
|
|
405
|
+
valid = false;
|
|
406
|
+
}
|
|
407
|
+
// Boolean field
|
|
408
|
+
if (typeof autoFixable !== 'boolean') {
|
|
409
|
+
errors.push({ path: `${path}.autoFixable`, message: 'Must be a boolean', expected: 'boolean', actual: typeof autoFixable });
|
|
410
|
+
valid = false;
|
|
411
|
+
}
|
|
412
|
+
// Complex fields
|
|
413
|
+
if (!validateDetectorConfig(detector, `${path}.detector`, errors)) {
|
|
414
|
+
valid = false;
|
|
415
|
+
}
|
|
416
|
+
if (!validateConfidenceInfo(confidence, `${path}.confidence`, errors)) {
|
|
417
|
+
valid = false;
|
|
418
|
+
}
|
|
419
|
+
if (!validatePatternMetadata(metadata, `${path}.metadata`, errors)) {
|
|
420
|
+
valid = false;
|
|
421
|
+
}
|
|
422
|
+
// Array fields
|
|
423
|
+
if (!Array.isArray(locations)) {
|
|
424
|
+
errors.push({ path: `${path}.locations`, message: 'Must be an array', expected: 'array', actual: typeof locations });
|
|
425
|
+
valid = false;
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
locations.forEach((loc, i) => {
|
|
429
|
+
if (!validatePatternLocation(loc, `${path}.locations[${i}]`, errors)) {
|
|
430
|
+
valid = false;
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
if (!Array.isArray(outliers)) {
|
|
435
|
+
errors.push({ path: `${path}.outliers`, message: 'Must be an array', expected: 'array', actual: typeof outliers });
|
|
436
|
+
valid = false;
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
outliers.forEach((loc, i) => {
|
|
440
|
+
if (!validateOutlierLocation(loc, `${path}.outliers[${i}]`, errors)) {
|
|
441
|
+
valid = false;
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
return valid;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Validate a full Pattern object (includes status and category)
|
|
449
|
+
*/
|
|
450
|
+
function validatePattern(pattern, path, errors) {
|
|
451
|
+
if (!isObject(pattern)) {
|
|
452
|
+
errors.push({ path, message: 'Must be an object', expected: 'object', actual: typeof pattern });
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
let valid = true;
|
|
456
|
+
const category = get(pattern, 'category');
|
|
457
|
+
const status = get(pattern, 'status');
|
|
458
|
+
// Validate category (not in StoredPattern)
|
|
459
|
+
if (!isOneOf(category, VALID_PATTERN_CATEGORIES)) {
|
|
460
|
+
errors.push({ path: `${path}.category`, message: `Must be one of: ${VALID_PATTERN_CATEGORIES.join(', ')}`, expected: VALID_PATTERN_CATEGORIES.join('|'), actual: category });
|
|
461
|
+
valid = false;
|
|
462
|
+
}
|
|
463
|
+
// Validate status (not in StoredPattern)
|
|
464
|
+
if (!isOneOf(status, VALID_PATTERN_STATUSES)) {
|
|
465
|
+
errors.push({ path: `${path}.status`, message: `Must be one of: ${VALID_PATTERN_STATUSES.join(', ')}`, expected: VALID_PATTERN_STATUSES.join('|'), actual: status });
|
|
466
|
+
valid = false;
|
|
467
|
+
}
|
|
468
|
+
// Validate the rest using StoredPattern validator
|
|
469
|
+
// Create a copy without category and status for StoredPattern validation
|
|
470
|
+
const storedPatternFields = { ...pattern };
|
|
471
|
+
delete storedPatternFields['category'];
|
|
472
|
+
delete storedPatternFields['status'];
|
|
473
|
+
if (!validateStoredPattern(storedPatternFields, path, errors)) {
|
|
474
|
+
valid = false;
|
|
475
|
+
}
|
|
476
|
+
return valid;
|
|
477
|
+
}
|
|
478
|
+
// ============================================================================
|
|
479
|
+
// File Validators
|
|
480
|
+
// ============================================================================
|
|
481
|
+
/**
|
|
482
|
+
* Validate a PatternFile object
|
|
483
|
+
*
|
|
484
|
+
* @requirements 4.5 - Pattern schema SHALL be validated on load/save
|
|
485
|
+
*/
|
|
486
|
+
export function validatePatternFile(data) {
|
|
487
|
+
const errors = [];
|
|
488
|
+
if (!isObject(data)) {
|
|
489
|
+
errors.push({ path: '', message: 'Pattern file must be an object', expected: 'object', actual: typeof data });
|
|
490
|
+
return { valid: false, errors };
|
|
491
|
+
}
|
|
492
|
+
const version = get(data, 'version');
|
|
493
|
+
const category = get(data, 'category');
|
|
494
|
+
const patterns = get(data, 'patterns');
|
|
495
|
+
const lastUpdated = get(data, 'lastUpdated');
|
|
496
|
+
const checksum = get(data, 'checksum');
|
|
497
|
+
// Version validation
|
|
498
|
+
if (!isSemverVersion(version)) {
|
|
499
|
+
errors.push({ path: 'version', message: 'Must be a valid semver version string', expected: 'semver', actual: version });
|
|
500
|
+
}
|
|
501
|
+
else if (!SUPPORTED_VERSIONS.patternFile.includes(version)) {
|
|
502
|
+
errors.push({ path: 'version', message: `Unsupported version. Supported: ${SUPPORTED_VERSIONS.patternFile.join(', ')}`, expected: SUPPORTED_VERSIONS.patternFile.join('|'), actual: version });
|
|
503
|
+
}
|
|
504
|
+
// Category validation
|
|
505
|
+
if (!isOneOf(category, VALID_PATTERN_CATEGORIES)) {
|
|
506
|
+
errors.push({ path: 'category', message: `Must be one of: ${VALID_PATTERN_CATEGORIES.join(', ')}`, expected: VALID_PATTERN_CATEGORIES.join('|'), actual: category });
|
|
507
|
+
}
|
|
508
|
+
// Patterns array validation
|
|
509
|
+
if (!Array.isArray(patterns)) {
|
|
510
|
+
errors.push({ path: 'patterns', message: 'Must be an array', expected: 'array', actual: typeof patterns });
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
patterns.forEach((pattern, i) => {
|
|
514
|
+
validateStoredPattern(pattern, `patterns[${i}]`, errors);
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
// Last updated validation
|
|
518
|
+
if (!isISODateString(lastUpdated)) {
|
|
519
|
+
errors.push({ path: 'lastUpdated', message: 'Must be a valid ISO date string', expected: 'ISO date string', actual: lastUpdated });
|
|
520
|
+
}
|
|
521
|
+
// Optional checksum
|
|
522
|
+
if (checksum !== undefined && typeof checksum !== 'string') {
|
|
523
|
+
errors.push({ path: 'checksum', message: 'Must be a string if provided', expected: 'string', actual: typeof checksum });
|
|
524
|
+
}
|
|
525
|
+
if (errors.length > 0) {
|
|
526
|
+
return { valid: false, errors };
|
|
527
|
+
}
|
|
528
|
+
return { valid: true, data: data };
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Validate a PatternHistoryEvent object
|
|
532
|
+
*/
|
|
533
|
+
function validatePatternHistoryEvent(event, path, errors) {
|
|
534
|
+
if (!isObject(event)) {
|
|
535
|
+
errors.push({ path, message: 'Must be an object', expected: 'object', actual: typeof event });
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
let valid = true;
|
|
539
|
+
const timestamp = get(event, 'timestamp');
|
|
540
|
+
const type = get(event, 'type');
|
|
541
|
+
const patternId = get(event, 'patternId');
|
|
542
|
+
const user = get(event, 'user');
|
|
543
|
+
const details = get(event, 'details');
|
|
544
|
+
if (!isISODateString(timestamp)) {
|
|
545
|
+
errors.push({ path: `${path}.timestamp`, message: 'Must be a valid ISO date string', expected: 'ISO date string', actual: timestamp });
|
|
546
|
+
valid = false;
|
|
547
|
+
}
|
|
548
|
+
if (!isOneOf(type, VALID_HISTORY_EVENT_TYPES)) {
|
|
549
|
+
errors.push({ path: `${path}.type`, message: `Must be one of: ${VALID_HISTORY_EVENT_TYPES.join(', ')}`, expected: VALID_HISTORY_EVENT_TYPES.join('|'), actual: type });
|
|
550
|
+
valid = false;
|
|
551
|
+
}
|
|
552
|
+
if (!isNonEmptyString(patternId)) {
|
|
553
|
+
errors.push({ path: `${path}.patternId`, message: 'Must be a non-empty string', expected: 'string', actual: patternId });
|
|
554
|
+
valid = false;
|
|
555
|
+
}
|
|
556
|
+
// Optional fields
|
|
557
|
+
if (user !== undefined && typeof user !== 'string') {
|
|
558
|
+
errors.push({ path: `${path}.user`, message: 'Must be a string if provided', expected: 'string', actual: typeof user });
|
|
559
|
+
valid = false;
|
|
560
|
+
}
|
|
561
|
+
if (details !== undefined && !isObject(details)) {
|
|
562
|
+
errors.push({ path: `${path}.details`, message: 'Must be an object if provided', expected: 'object', actual: typeof details });
|
|
563
|
+
valid = false;
|
|
564
|
+
}
|
|
565
|
+
return valid;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Validate a PatternHistory object
|
|
569
|
+
*/
|
|
570
|
+
function validatePatternHistory(history, path, errors) {
|
|
571
|
+
if (!isObject(history)) {
|
|
572
|
+
errors.push({ path, message: 'Must be an object', expected: 'object', actual: typeof history });
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
575
|
+
let valid = true;
|
|
576
|
+
const patternId = get(history, 'patternId');
|
|
577
|
+
const category = get(history, 'category');
|
|
578
|
+
const events = get(history, 'events');
|
|
579
|
+
const createdAt = get(history, 'createdAt');
|
|
580
|
+
const lastModified = get(history, 'lastModified');
|
|
581
|
+
if (!isNonEmptyString(patternId)) {
|
|
582
|
+
errors.push({ path: `${path}.patternId`, message: 'Must be a non-empty string', expected: 'string', actual: patternId });
|
|
583
|
+
valid = false;
|
|
584
|
+
}
|
|
585
|
+
if (!isOneOf(category, VALID_PATTERN_CATEGORIES)) {
|
|
586
|
+
errors.push({ path: `${path}.category`, message: `Must be one of: ${VALID_PATTERN_CATEGORIES.join(', ')}`, expected: VALID_PATTERN_CATEGORIES.join('|'), actual: category });
|
|
587
|
+
valid = false;
|
|
588
|
+
}
|
|
589
|
+
if (!Array.isArray(events)) {
|
|
590
|
+
errors.push({ path: `${path}.events`, message: 'Must be an array', expected: 'array', actual: typeof events });
|
|
591
|
+
valid = false;
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
events.forEach((event, i) => {
|
|
595
|
+
if (!validatePatternHistoryEvent(event, `${path}.events[${i}]`, errors)) {
|
|
596
|
+
valid = false;
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
if (!isISODateString(createdAt)) {
|
|
601
|
+
errors.push({ path: `${path}.createdAt`, message: 'Must be a valid ISO date string', expected: 'ISO date string', actual: createdAt });
|
|
602
|
+
valid = false;
|
|
603
|
+
}
|
|
604
|
+
if (!isISODateString(lastModified)) {
|
|
605
|
+
errors.push({ path: `${path}.lastModified`, message: 'Must be a valid ISO date string', expected: 'ISO date string', actual: lastModified });
|
|
606
|
+
valid = false;
|
|
607
|
+
}
|
|
608
|
+
return valid;
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Validate a HistoryFile object
|
|
612
|
+
*
|
|
613
|
+
* @requirements 4.5 - Pattern schema SHALL be validated on load/save
|
|
614
|
+
*/
|
|
615
|
+
export function validateHistoryFile(data) {
|
|
616
|
+
const errors = [];
|
|
617
|
+
if (!isObject(data)) {
|
|
618
|
+
errors.push({ path: '', message: 'History file must be an object', expected: 'object', actual: typeof data });
|
|
619
|
+
return { valid: false, errors };
|
|
620
|
+
}
|
|
621
|
+
const version = get(data, 'version');
|
|
622
|
+
const patterns = get(data, 'patterns');
|
|
623
|
+
const lastUpdated = get(data, 'lastUpdated');
|
|
624
|
+
// Version validation
|
|
625
|
+
if (!isSemverVersion(version)) {
|
|
626
|
+
errors.push({ path: 'version', message: 'Must be a valid semver version string', expected: 'semver', actual: version });
|
|
627
|
+
}
|
|
628
|
+
else if (!SUPPORTED_VERSIONS.historyFile.includes(version)) {
|
|
629
|
+
errors.push({ path: 'version', message: `Unsupported version. Supported: ${SUPPORTED_VERSIONS.historyFile.join(', ')}`, expected: SUPPORTED_VERSIONS.historyFile.join('|'), actual: version });
|
|
630
|
+
}
|
|
631
|
+
// Patterns array validation
|
|
632
|
+
if (!Array.isArray(patterns)) {
|
|
633
|
+
errors.push({ path: 'patterns', message: 'Must be an array', expected: 'array', actual: typeof patterns });
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
patterns.forEach((history, i) => {
|
|
637
|
+
validatePatternHistory(history, `patterns[${i}]`, errors);
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
// Last updated validation
|
|
641
|
+
if (!isISODateString(lastUpdated)) {
|
|
642
|
+
errors.push({ path: 'lastUpdated', message: 'Must be a valid ISO date string', expected: 'ISO date string', actual: lastUpdated });
|
|
643
|
+
}
|
|
644
|
+
if (errors.length > 0) {
|
|
645
|
+
return { valid: false, errors };
|
|
646
|
+
}
|
|
647
|
+
return { valid: true, data: data };
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Validate a LockedPattern object
|
|
651
|
+
*/
|
|
652
|
+
function validateLockedPattern(pattern, path, errors) {
|
|
653
|
+
if (!isObject(pattern)) {
|
|
654
|
+
errors.push({ path, message: 'Must be an object', expected: 'object', actual: typeof pattern });
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
let valid = true;
|
|
658
|
+
const id = get(pattern, 'id');
|
|
659
|
+
const category = get(pattern, 'category');
|
|
660
|
+
const name = get(pattern, 'name');
|
|
661
|
+
const confidenceScore = get(pattern, 'confidenceScore');
|
|
662
|
+
const severity = get(pattern, 'severity');
|
|
663
|
+
const definitionHash = get(pattern, 'definitionHash');
|
|
664
|
+
const lockedAt = get(pattern, 'lockedAt');
|
|
665
|
+
if (!isNonEmptyString(id)) {
|
|
666
|
+
errors.push({ path: `${path}.id`, message: 'Must be a non-empty string', expected: 'string', actual: id });
|
|
667
|
+
valid = false;
|
|
668
|
+
}
|
|
669
|
+
if (!isOneOf(category, VALID_PATTERN_CATEGORIES)) {
|
|
670
|
+
errors.push({ path: `${path}.category`, message: `Must be one of: ${VALID_PATTERN_CATEGORIES.join(', ')}`, expected: VALID_PATTERN_CATEGORIES.join('|'), actual: category });
|
|
671
|
+
valid = false;
|
|
672
|
+
}
|
|
673
|
+
if (!isNonEmptyString(name)) {
|
|
674
|
+
errors.push({ path: `${path}.name`, message: 'Must be a non-empty string', expected: 'string', actual: name });
|
|
675
|
+
valid = false;
|
|
676
|
+
}
|
|
677
|
+
if (!isNumberInRange(confidenceScore, 0, 1)) {
|
|
678
|
+
errors.push({ path: `${path}.confidenceScore`, message: 'Must be a number between 0 and 1', expected: '0-1', actual: confidenceScore });
|
|
679
|
+
valid = false;
|
|
680
|
+
}
|
|
681
|
+
if (!isOneOf(severity, VALID_SEVERITIES)) {
|
|
682
|
+
errors.push({ path: `${path}.severity`, message: `Must be one of: ${VALID_SEVERITIES.join(', ')}`, expected: VALID_SEVERITIES.join('|'), actual: severity });
|
|
683
|
+
valid = false;
|
|
684
|
+
}
|
|
685
|
+
if (!isNonEmptyString(definitionHash)) {
|
|
686
|
+
errors.push({ path: `${path}.definitionHash`, message: 'Must be a non-empty string', expected: 'string', actual: definitionHash });
|
|
687
|
+
valid = false;
|
|
688
|
+
}
|
|
689
|
+
if (!isISODateString(lockedAt)) {
|
|
690
|
+
errors.push({ path: `${path}.lockedAt`, message: 'Must be a valid ISO date string', expected: 'ISO date string', actual: lockedAt });
|
|
691
|
+
valid = false;
|
|
692
|
+
}
|
|
693
|
+
return valid;
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Validate a LockFile object
|
|
697
|
+
*
|
|
698
|
+
* @requirements 4.5 - Pattern schema SHALL be validated on load/save
|
|
699
|
+
*/
|
|
700
|
+
export function validateLockFile(data) {
|
|
701
|
+
const errors = [];
|
|
702
|
+
if (!isObject(data)) {
|
|
703
|
+
errors.push({ path: '', message: 'Lock file must be an object', expected: 'object', actual: typeof data });
|
|
704
|
+
return { valid: false, errors };
|
|
705
|
+
}
|
|
706
|
+
const version = get(data, 'version');
|
|
707
|
+
const patterns = get(data, 'patterns');
|
|
708
|
+
const generatedAt = get(data, 'generatedAt');
|
|
709
|
+
const checksum = get(data, 'checksum');
|
|
710
|
+
// Version validation
|
|
711
|
+
if (!isSemverVersion(version)) {
|
|
712
|
+
errors.push({ path: 'version', message: 'Must be a valid semver version string', expected: 'semver', actual: version });
|
|
713
|
+
}
|
|
714
|
+
else if (!SUPPORTED_VERSIONS.lockFile.includes(version)) {
|
|
715
|
+
errors.push({ path: 'version', message: `Unsupported version. Supported: ${SUPPORTED_VERSIONS.lockFile.join(', ')}`, expected: SUPPORTED_VERSIONS.lockFile.join('|'), actual: version });
|
|
716
|
+
}
|
|
717
|
+
// Patterns array validation
|
|
718
|
+
if (!Array.isArray(patterns)) {
|
|
719
|
+
errors.push({ path: 'patterns', message: 'Must be an array', expected: 'array', actual: typeof patterns });
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
patterns.forEach((pattern, i) => {
|
|
723
|
+
validateLockedPattern(pattern, `patterns[${i}]`, errors);
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
// Generated at validation
|
|
727
|
+
if (!isISODateString(generatedAt)) {
|
|
728
|
+
errors.push({ path: 'generatedAt', message: 'Must be a valid ISO date string', expected: 'ISO date string', actual: generatedAt });
|
|
729
|
+
}
|
|
730
|
+
// Checksum validation
|
|
731
|
+
if (!isNonEmptyString(checksum)) {
|
|
732
|
+
errors.push({ path: 'checksum', message: 'Must be a non-empty string', expected: 'string', actual: checksum });
|
|
733
|
+
}
|
|
734
|
+
if (errors.length > 0) {
|
|
735
|
+
return { valid: false, errors };
|
|
736
|
+
}
|
|
737
|
+
return { valid: true, data: data };
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Validate a PatternVariant object
|
|
741
|
+
*/
|
|
742
|
+
function validatePatternVariant(variant, path, errors) {
|
|
743
|
+
if (!isObject(variant)) {
|
|
744
|
+
errors.push({ path, message: 'Must be an object', expected: 'object', actual: typeof variant });
|
|
745
|
+
return false;
|
|
746
|
+
}
|
|
747
|
+
let valid = true;
|
|
748
|
+
const id = get(variant, 'id');
|
|
749
|
+
const patternId = get(variant, 'patternId');
|
|
750
|
+
const name = get(variant, 'name');
|
|
751
|
+
const reason = get(variant, 'reason');
|
|
752
|
+
const scope = get(variant, 'scope');
|
|
753
|
+
const scopeValue = get(variant, 'scopeValue');
|
|
754
|
+
const locations = get(variant, 'locations');
|
|
755
|
+
const createdAt = get(variant, 'createdAt');
|
|
756
|
+
const createdBy = get(variant, 'createdBy');
|
|
757
|
+
const active = get(variant, 'active');
|
|
758
|
+
if (!isNonEmptyString(id)) {
|
|
759
|
+
errors.push({ path: `${path}.id`, message: 'Must be a non-empty string', expected: 'string', actual: id });
|
|
760
|
+
valid = false;
|
|
761
|
+
}
|
|
762
|
+
if (!isNonEmptyString(patternId)) {
|
|
763
|
+
errors.push({ path: `${path}.patternId`, message: 'Must be a non-empty string', expected: 'string', actual: patternId });
|
|
764
|
+
valid = false;
|
|
765
|
+
}
|
|
766
|
+
if (!isNonEmptyString(name)) {
|
|
767
|
+
errors.push({ path: `${path}.name`, message: 'Must be a non-empty string', expected: 'string', actual: name });
|
|
768
|
+
valid = false;
|
|
769
|
+
}
|
|
770
|
+
if (!isNonEmptyString(reason)) {
|
|
771
|
+
errors.push({ path: `${path}.reason`, message: 'Must be a non-empty string', expected: 'string', actual: reason });
|
|
772
|
+
valid = false;
|
|
773
|
+
}
|
|
774
|
+
if (!isOneOf(scope, VALID_VARIANT_SCOPES)) {
|
|
775
|
+
errors.push({ path: `${path}.scope`, message: `Must be one of: ${VALID_VARIANT_SCOPES.join(', ')}`, expected: VALID_VARIANT_SCOPES.join('|'), actual: scope });
|
|
776
|
+
valid = false;
|
|
777
|
+
}
|
|
778
|
+
// scopeValue is optional but must be string if provided
|
|
779
|
+
if (scopeValue !== undefined && typeof scopeValue !== 'string') {
|
|
780
|
+
errors.push({ path: `${path}.scopeValue`, message: 'Must be a string if provided', expected: 'string', actual: typeof scopeValue });
|
|
781
|
+
valid = false;
|
|
782
|
+
}
|
|
783
|
+
if (!Array.isArray(locations)) {
|
|
784
|
+
errors.push({ path: `${path}.locations`, message: 'Must be an array', expected: 'array', actual: typeof locations });
|
|
785
|
+
valid = false;
|
|
786
|
+
}
|
|
787
|
+
else {
|
|
788
|
+
locations.forEach((loc, i) => {
|
|
789
|
+
if (!validatePatternLocation(loc, `${path}.locations[${i}]`, errors)) {
|
|
790
|
+
valid = false;
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
if (!isISODateString(createdAt)) {
|
|
795
|
+
errors.push({ path: `${path}.createdAt`, message: 'Must be a valid ISO date string', expected: 'ISO date string', actual: createdAt });
|
|
796
|
+
valid = false;
|
|
797
|
+
}
|
|
798
|
+
if (createdBy !== undefined && typeof createdBy !== 'string') {
|
|
799
|
+
errors.push({ path: `${path}.createdBy`, message: 'Must be a string if provided', expected: 'string', actual: typeof createdBy });
|
|
800
|
+
valid = false;
|
|
801
|
+
}
|
|
802
|
+
if (typeof active !== 'boolean') {
|
|
803
|
+
errors.push({ path: `${path}.active`, message: 'Must be a boolean', expected: 'boolean', actual: typeof active });
|
|
804
|
+
valid = false;
|
|
805
|
+
}
|
|
806
|
+
return valid;
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Validate a VariantsFile object
|
|
810
|
+
*
|
|
811
|
+
* @requirements 4.5 - Pattern schema SHALL be validated on load/save
|
|
812
|
+
*/
|
|
813
|
+
export function validateVariantsFile(data) {
|
|
814
|
+
const errors = [];
|
|
815
|
+
if (!isObject(data)) {
|
|
816
|
+
errors.push({ path: '', message: 'Variants file must be an object', expected: 'object', actual: typeof data });
|
|
817
|
+
return { valid: false, errors };
|
|
818
|
+
}
|
|
819
|
+
const version = get(data, 'version');
|
|
820
|
+
const variants = get(data, 'variants');
|
|
821
|
+
const lastUpdated = get(data, 'lastUpdated');
|
|
822
|
+
// Version validation
|
|
823
|
+
if (!isSemverVersion(version)) {
|
|
824
|
+
errors.push({ path: 'version', message: 'Must be a valid semver version string', expected: 'semver', actual: version });
|
|
825
|
+
}
|
|
826
|
+
else if (!SUPPORTED_VERSIONS.variantsFile.includes(version)) {
|
|
827
|
+
errors.push({ path: 'version', message: `Unsupported version. Supported: ${SUPPORTED_VERSIONS.variantsFile.join(', ')}`, expected: SUPPORTED_VERSIONS.variantsFile.join('|'), actual: version });
|
|
828
|
+
}
|
|
829
|
+
// Variants array validation
|
|
830
|
+
if (!Array.isArray(variants)) {
|
|
831
|
+
errors.push({ path: 'variants', message: 'Must be an array', expected: 'array', actual: typeof variants });
|
|
832
|
+
}
|
|
833
|
+
else {
|
|
834
|
+
variants.forEach((variant, i) => {
|
|
835
|
+
validatePatternVariant(variant, `variants[${i}]`, errors);
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
// Last updated validation
|
|
839
|
+
if (!isISODateString(lastUpdated)) {
|
|
840
|
+
errors.push({ path: 'lastUpdated', message: 'Must be a valid ISO date string', expected: 'ISO date string', actual: lastUpdated });
|
|
841
|
+
}
|
|
842
|
+
if (errors.length > 0) {
|
|
843
|
+
return { valid: false, errors };
|
|
844
|
+
}
|
|
845
|
+
return { valid: true, data: data };
|
|
846
|
+
}
|
|
847
|
+
// ============================================================================
|
|
848
|
+
// Config Validator
|
|
849
|
+
// ============================================================================
|
|
850
|
+
/**
|
|
851
|
+
* Validate a DriftConfig object
|
|
852
|
+
*
|
|
853
|
+
* @requirements 4.5 - Pattern schema SHALL be validated on load/save
|
|
854
|
+
*/
|
|
855
|
+
export function validateConfig(data) {
|
|
856
|
+
const errors = [];
|
|
857
|
+
if (!isObject(data)) {
|
|
858
|
+
errors.push({ path: '', message: 'Config must be an object', expected: 'object', actual: typeof data });
|
|
859
|
+
return { valid: false, errors };
|
|
860
|
+
}
|
|
861
|
+
const severity = get(data, 'severity');
|
|
862
|
+
const ignore = get(data, 'ignore');
|
|
863
|
+
const ai = get(data, 'ai');
|
|
864
|
+
const ci = get(data, 'ci');
|
|
865
|
+
const learning = get(data, 'learning');
|
|
866
|
+
const performance = get(data, 'performance');
|
|
867
|
+
// Severity overrides (optional)
|
|
868
|
+
if (severity !== undefined) {
|
|
869
|
+
if (!isObject(severity)) {
|
|
870
|
+
errors.push({ path: 'severity', message: 'Must be an object', expected: 'object', actual: typeof severity });
|
|
871
|
+
}
|
|
872
|
+
else {
|
|
873
|
+
Object.entries(severity).forEach(([key, value]) => {
|
|
874
|
+
if (!isOneOf(value, VALID_SEVERITIES)) {
|
|
875
|
+
errors.push({ path: `severity.${key}`, message: `Must be one of: ${VALID_SEVERITIES.join(', ')}`, expected: VALID_SEVERITIES.join('|'), actual: value });
|
|
876
|
+
}
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
// Ignore patterns (optional)
|
|
881
|
+
if (ignore !== undefined) {
|
|
882
|
+
if (!Array.isArray(ignore)) {
|
|
883
|
+
errors.push({ path: 'ignore', message: 'Must be an array', expected: 'array', actual: typeof ignore });
|
|
884
|
+
}
|
|
885
|
+
else if (!ignore.every((p) => typeof p === 'string')) {
|
|
886
|
+
errors.push({ path: 'ignore', message: 'All ignore patterns must be strings', expected: 'string[]', actual: ignore });
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
// AI config (optional)
|
|
890
|
+
if (ai !== undefined) {
|
|
891
|
+
if (!isObject(ai)) {
|
|
892
|
+
errors.push({ path: 'ai', message: 'Must be an object', expected: 'object', actual: typeof ai });
|
|
893
|
+
}
|
|
894
|
+
else {
|
|
895
|
+
const aiProvider = get(ai, 'provider');
|
|
896
|
+
const aiModel = get(ai, 'model');
|
|
897
|
+
if (!isOneOf(aiProvider, VALID_AI_PROVIDERS)) {
|
|
898
|
+
errors.push({ path: 'ai.provider', message: `Must be one of: ${VALID_AI_PROVIDERS.join(', ')}`, expected: VALID_AI_PROVIDERS.join('|'), actual: aiProvider });
|
|
899
|
+
}
|
|
900
|
+
if (aiModel !== undefined && typeof aiModel !== 'string') {
|
|
901
|
+
errors.push({ path: 'ai.model', message: 'Must be a string if provided', expected: 'string', actual: typeof aiModel });
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
// CI config (optional)
|
|
906
|
+
if (ci !== undefined) {
|
|
907
|
+
if (!isObject(ci)) {
|
|
908
|
+
errors.push({ path: 'ci', message: 'Must be an object', expected: 'object', actual: typeof ci });
|
|
909
|
+
}
|
|
910
|
+
else {
|
|
911
|
+
const ciFailOn = get(ci, 'failOn');
|
|
912
|
+
const ciReportFormat = get(ci, 'reportFormat');
|
|
913
|
+
if (!isOneOf(ciFailOn, VALID_CI_FAIL_ON)) {
|
|
914
|
+
errors.push({ path: 'ci.failOn', message: `Must be one of: ${VALID_CI_FAIL_ON.join(', ')}`, expected: VALID_CI_FAIL_ON.join('|'), actual: ciFailOn });
|
|
915
|
+
}
|
|
916
|
+
if (!isOneOf(ciReportFormat, VALID_REPORT_FORMATS)) {
|
|
917
|
+
errors.push({ path: 'ci.reportFormat', message: `Must be one of: ${VALID_REPORT_FORMATS.join(', ')}`, expected: VALID_REPORT_FORMATS.join('|'), actual: ciReportFormat });
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
// Learning config (optional)
|
|
922
|
+
if (learning !== undefined) {
|
|
923
|
+
if (!isObject(learning)) {
|
|
924
|
+
errors.push({ path: 'learning', message: 'Must be an object', expected: 'object', actual: typeof learning });
|
|
925
|
+
}
|
|
926
|
+
else {
|
|
927
|
+
const autoApproveThreshold = get(learning, 'autoApproveThreshold');
|
|
928
|
+
const minOccurrences = get(learning, 'minOccurrences');
|
|
929
|
+
if (!isNumberInRange(autoApproveThreshold, 0, 1)) {
|
|
930
|
+
errors.push({ path: 'learning.autoApproveThreshold', message: 'Must be a number between 0 and 1', expected: '0-1', actual: autoApproveThreshold });
|
|
931
|
+
}
|
|
932
|
+
if (!isPositiveInteger(minOccurrences)) {
|
|
933
|
+
errors.push({ path: 'learning.minOccurrences', message: 'Must be a non-negative integer', expected: 'integer >= 0', actual: minOccurrences });
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
// Performance config (optional)
|
|
938
|
+
if (performance !== undefined) {
|
|
939
|
+
if (!isObject(performance)) {
|
|
940
|
+
errors.push({ path: 'performance', message: 'Must be an object', expected: 'object', actual: typeof performance });
|
|
941
|
+
}
|
|
942
|
+
else {
|
|
943
|
+
const maxWorkers = get(performance, 'maxWorkers');
|
|
944
|
+
const cacheEnabled = get(performance, 'cacheEnabled');
|
|
945
|
+
const incrementalAnalysis = get(performance, 'incrementalAnalysis');
|
|
946
|
+
if (!isPositiveInteger(maxWorkers) || maxWorkers < 1) {
|
|
947
|
+
errors.push({ path: 'performance.maxWorkers', message: 'Must be a positive integer', expected: 'integer >= 1', actual: maxWorkers });
|
|
948
|
+
}
|
|
949
|
+
if (typeof cacheEnabled !== 'boolean') {
|
|
950
|
+
errors.push({ path: 'performance.cacheEnabled', message: 'Must be a boolean', expected: 'boolean', actual: typeof cacheEnabled });
|
|
951
|
+
}
|
|
952
|
+
if (typeof incrementalAnalysis !== 'boolean') {
|
|
953
|
+
errors.push({ path: 'performance.incrementalAnalysis', message: 'Must be a boolean', expected: 'boolean', actual: typeof incrementalAnalysis });
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
if (errors.length > 0) {
|
|
958
|
+
return { valid: false, errors };
|
|
959
|
+
}
|
|
960
|
+
return { valid: true, data: data };
|
|
961
|
+
}
|
|
962
|
+
// ============================================================================
|
|
963
|
+
// Single Pattern Validator (Public API)
|
|
964
|
+
// ============================================================================
|
|
965
|
+
/**
|
|
966
|
+
* Validate a single Pattern object
|
|
967
|
+
*
|
|
968
|
+
* @requirements 4.5 - Pattern schema SHALL be validated on load/save
|
|
969
|
+
*/
|
|
970
|
+
export function validateSinglePattern(data) {
|
|
971
|
+
const errors = [];
|
|
972
|
+
if (!validatePattern(data, '', errors)) {
|
|
973
|
+
return { valid: false, errors };
|
|
974
|
+
}
|
|
975
|
+
return { valid: true, data: data };
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Validate a single StoredPattern object
|
|
979
|
+
*
|
|
980
|
+
* @requirements 4.5 - Pattern schema SHALL be validated on load/save
|
|
981
|
+
*/
|
|
982
|
+
export function validateSingleStoredPattern(data) {
|
|
983
|
+
const errors = [];
|
|
984
|
+
if (!validateStoredPattern(data, '', errors)) {
|
|
985
|
+
return { valid: false, errors };
|
|
986
|
+
}
|
|
987
|
+
return { valid: true, data: data };
|
|
988
|
+
}
|
|
989
|
+
// ============================================================================
|
|
990
|
+
// Convenience Functions
|
|
991
|
+
// ============================================================================
|
|
992
|
+
/**
|
|
993
|
+
* Validate and throw if invalid
|
|
994
|
+
*
|
|
995
|
+
* @throws SchemaValidationError if validation fails
|
|
996
|
+
*/
|
|
997
|
+
export function assertValidPatternFile(data) {
|
|
998
|
+
const result = validatePatternFile(data);
|
|
999
|
+
if (!result.valid) {
|
|
1000
|
+
throw new SchemaValidationError(`Invalid pattern file: ${result.errors.length} validation error(s)`, result.errors, 'PatternFile');
|
|
1001
|
+
}
|
|
1002
|
+
return result.data;
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Validate and throw if invalid
|
|
1006
|
+
*
|
|
1007
|
+
* @throws SchemaValidationError if validation fails
|
|
1008
|
+
*/
|
|
1009
|
+
export function assertValidHistoryFile(data) {
|
|
1010
|
+
const result = validateHistoryFile(data);
|
|
1011
|
+
if (!result.valid) {
|
|
1012
|
+
throw new SchemaValidationError(`Invalid history file: ${result.errors.length} validation error(s)`, result.errors, 'HistoryFile');
|
|
1013
|
+
}
|
|
1014
|
+
return result.data;
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Validate and throw if invalid
|
|
1018
|
+
*
|
|
1019
|
+
* @throws SchemaValidationError if validation fails
|
|
1020
|
+
*/
|
|
1021
|
+
export function assertValidLockFile(data) {
|
|
1022
|
+
const result = validateLockFile(data);
|
|
1023
|
+
if (!result.valid) {
|
|
1024
|
+
throw new SchemaValidationError(`Invalid lock file: ${result.errors.length} validation error(s)`, result.errors, 'LockFile');
|
|
1025
|
+
}
|
|
1026
|
+
return result.data;
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Validate and throw if invalid
|
|
1030
|
+
*
|
|
1031
|
+
* @throws SchemaValidationError if validation fails
|
|
1032
|
+
*/
|
|
1033
|
+
export function assertValidVariantsFile(data) {
|
|
1034
|
+
const result = validateVariantsFile(data);
|
|
1035
|
+
if (!result.valid) {
|
|
1036
|
+
throw new SchemaValidationError(`Invalid variants file: ${result.errors.length} validation error(s)`, result.errors, 'VariantsFile');
|
|
1037
|
+
}
|
|
1038
|
+
return result.data;
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Validate and throw if invalid
|
|
1042
|
+
*
|
|
1043
|
+
* @throws SchemaValidationError if validation fails
|
|
1044
|
+
*/
|
|
1045
|
+
export function assertValidConfig(data) {
|
|
1046
|
+
const result = validateConfig(data);
|
|
1047
|
+
if (!result.valid) {
|
|
1048
|
+
throw new SchemaValidationError(`Invalid config: ${result.errors.length} validation error(s)`, result.errors, 'DriftConfig');
|
|
1049
|
+
}
|
|
1050
|
+
return result.data;
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* Validate and throw if invalid
|
|
1054
|
+
*
|
|
1055
|
+
* @throws SchemaValidationError if validation fails
|
|
1056
|
+
*/
|
|
1057
|
+
export function assertValidPattern(data) {
|
|
1058
|
+
const result = validateSinglePattern(data);
|
|
1059
|
+
if (!result.valid) {
|
|
1060
|
+
throw new SchemaValidationError(`Invalid pattern: ${result.errors.length} validation error(s)`, result.errors, 'Pattern');
|
|
1061
|
+
}
|
|
1062
|
+
return result.data;
|
|
1063
|
+
}
|
|
1064
|
+
// ============================================================================
|
|
1065
|
+
// Version Checking
|
|
1066
|
+
// ============================================================================
|
|
1067
|
+
/**
|
|
1068
|
+
* Check if a schema version is supported
|
|
1069
|
+
*/
|
|
1070
|
+
export function isVersionSupported(schemaType, version) {
|
|
1071
|
+
return SUPPORTED_VERSIONS[schemaType].includes(version);
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Get the current schema version for a type
|
|
1075
|
+
*/
|
|
1076
|
+
export function getCurrentVersion(schemaType) {
|
|
1077
|
+
return SCHEMA_VERSIONS[schemaType];
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Format validation errors as a human-readable string
|
|
1081
|
+
*/
|
|
1082
|
+
export function formatValidationErrors(errors) {
|
|
1083
|
+
if (errors.length === 0)
|
|
1084
|
+
return 'No errors';
|
|
1085
|
+
return errors
|
|
1086
|
+
.map((e) => {
|
|
1087
|
+
let msg = e.path ? `${e.path}: ${e.message}` : e.message;
|
|
1088
|
+
if (e.expected)
|
|
1089
|
+
msg += ` (expected: ${e.expected})`;
|
|
1090
|
+
if (e.actual !== undefined)
|
|
1091
|
+
msg += ` (got: ${JSON.stringify(e.actual)})`;
|
|
1092
|
+
return ` - ${msg}`;
|
|
1093
|
+
})
|
|
1094
|
+
.join('\n');
|
|
1095
|
+
}
|
|
1096
|
+
//# sourceMappingURL=schema-validator.js.map
|