Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/platform/configuration/common/validator.ts
13401 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { JsonSchema } from './jsonSchema';
7
8
export interface IValidator<T> {
9
validate(content: unknown): { content: T; error: undefined } | { content: undefined; error: ValidationError };
10
11
toSchema(): JsonSchema;
12
13
/**
14
* Returns true if this validator represents a required field.
15
* Used by vObj to determine which fields are required.
16
*/
17
isRequired?(): boolean;
18
}
19
20
export type ValidatorType<T> = T extends IValidator<infer U> ? U : never;
21
22
export interface ValidationError {
23
message: string;
24
}
25
26
interface TypeOfMap {
27
string: string;
28
number: number;
29
boolean: boolean;
30
object: object;
31
undefined: undefined;
32
function: Function;
33
symbol: symbol;
34
}
35
36
class TypeofValidator<TKey extends keyof TypeOfMap> implements IValidator<TypeOfMap[TKey]> {
37
constructor(private readonly type: TKey) { }
38
39
validate(content: unknown): { content: TypeOfMap[TKey]; error: undefined } | { content: undefined; error: ValidationError } {
40
if (typeof content !== this.type) {
41
return { content: undefined, error: { message: `Expected ${this.type}, but got ${typeof content}` } };
42
}
43
44
return { content: content as TypeOfMap[TKey], error: undefined };
45
}
46
47
toSchema(): JsonSchema {
48
return { type: this.type };
49
}
50
}
51
52
const vStringValidator = new TypeofValidator('string');
53
export function vString(): IValidator<string> { return vStringValidator; }
54
55
const vNumberValidator = new TypeofValidator('number');
56
export function vNumber(): IValidator<number> { return vNumberValidator; }
57
58
const vBooleanValidator = new TypeofValidator('boolean');
59
export function vBoolean(): IValidator<boolean> { return vBooleanValidator; }
60
61
const vObjAnyValidator = new TypeofValidator('object');
62
export function vObjAny(): IValidator<object> { return vObjAnyValidator; }
63
64
const vUndefinedValidator = new TypeofValidator('undefined');
65
export function vUndefined(): IValidator<undefined> { return vUndefinedValidator; }
66
67
class NullValidator implements IValidator<null> {
68
validate(content: unknown): { content: null; error: undefined } | { content: undefined; error: ValidationError } {
69
if (content !== null) {
70
return { content: undefined, error: { message: `Expected null, but got ${typeof content}` } };
71
}
72
return { content: null, error: undefined };
73
}
74
75
toSchema(): JsonSchema {
76
return { type: 'null' };
77
}
78
}
79
80
const vNullValidator = new NullValidator();
81
export function vNull(): IValidator<null> { return vNullValidator; }
82
83
export function vNullable<T>(validator: IValidator<T>): IValidator<T | null> {
84
return vUnion(validator, vNullValidator);
85
}
86
87
export function vUnchecked<T>(): IValidator<T> {
88
return {
89
validate(content: unknown): { content: T; error: undefined } {
90
return { content: content as T, error: undefined };
91
},
92
toSchema() {
93
return {
94
95
};
96
},
97
};
98
}
99
100
export function vUnknown(): IValidator<unknown> {
101
return vUnchecked();
102
}
103
104
export type ObjectProperties = Record<string, any>;
105
106
export function vRequired<T>(validator: IValidator<T>): IValidator<T> {
107
return {
108
validate(content: unknown): { content: T; error: undefined } | { content: undefined; error: ValidationError } {
109
if (content === undefined) {
110
return { content: undefined, error: { message: 'Required field is missing' } };
111
}
112
return validator.validate(content);
113
},
114
toSchema(): JsonSchema {
115
return validator.toSchema();
116
},
117
isRequired(): boolean {
118
return true;
119
}
120
};
121
}
122
123
export function vObj<T extends Record<string, IValidator<any>>>(properties: T): IValidator<{ [K in keyof T]: ValidatorType<T[K]> }> {
124
return {
125
validate(content: unknown): { content: any; error: undefined } | { content: undefined; error: ValidationError } {
126
if (typeof content !== 'object' || content === null) {
127
return { content: undefined, error: { message: 'Expected object' } };
128
}
129
130
const result: any = {};
131
for (const key in properties) {
132
const validator = properties[key];
133
const fieldValue = (content as any)[key];
134
135
// Check if field is required and missing
136
const isRequired = validator.isRequired?.() ?? false;
137
if (isRequired && fieldValue === undefined) {
138
return { content: undefined, error: { message: `Required field '${key}' is missing` } };
139
}
140
141
// If field is not required and is missing, skip validation
142
if (!isRequired && fieldValue === undefined) {
143
continue;
144
}
145
146
const { content: value, error } = validator.validate(fieldValue);
147
if (error) {
148
return { content: undefined, error: { message: `Error in property '${key}': ${error.message}` } };
149
}
150
151
result[key] = value;
152
}
153
154
return { content: result, error: undefined };
155
},
156
toSchema() {
157
const requiredFields: string[] = [];
158
const schemaProperties: Record<string, JsonSchema> = {};
159
160
for (const [key, validator] of Object.entries(properties)) {
161
schemaProperties[key] = validator.toSchema();
162
if (validator.isRequired?.()) {
163
requiredFields.push(key);
164
}
165
}
166
167
const schema: JsonSchema = {
168
type: 'object',
169
properties: schemaProperties,
170
...(requiredFields.length > 0 ? { required: requiredFields } : {})
171
};
172
173
return schema;
174
}
175
};
176
}
177
178
export function vArray<T>(validator: IValidator<T>): IValidator<T[]> {
179
return {
180
validate(content: unknown): { content: T[]; error: undefined } | { content: undefined; error: ValidationError } {
181
if (!Array.isArray(content)) {
182
return { content: undefined, error: { message: 'Expected array' } };
183
}
184
185
const result: T[] = [];
186
for (let i = 0; i < content.length; i++) {
187
const { content: value, error } = validator.validate(content[i]);
188
if (error) {
189
return { content: undefined, error: { message: `Error in element ${i}: ${error.message}` } };
190
}
191
192
result.push(value);
193
}
194
195
return { content: result, error: undefined };
196
},
197
198
toSchema(): JsonSchema {
199
return {
200
type: 'array',
201
items: validator.toSchema(),
202
};
203
}
204
};
205
}
206
207
export function vTuple<T extends IValidator<any>[]>(...validators: T): IValidator<{ [K in keyof T]: ValidatorType<T[K]> }> {
208
return {
209
validate(content: unknown): { content: any; error: undefined } | { content: undefined; error: ValidationError } {
210
if (!Array.isArray(content)) {
211
return { content: undefined, error: { message: 'Expected array' } };
212
}
213
214
if (content.length !== validators.length) {
215
return { content: undefined, error: { message: `Expected tuple of length ${validators.length}, but got ${content.length}` } };
216
}
217
218
const result: any = [];
219
for (let i = 0; i < validators.length; i++) {
220
const validator = validators[i];
221
const { content: value, error } = validator.validate(content[i]);
222
if (error) {
223
return { content: undefined, error: { message: `Error in element ${i}: ${error.message}` } };
224
}
225
226
result.push(value);
227
}
228
229
return { content: result, error: undefined };
230
},
231
232
toSchema(): JsonSchema {
233
return {
234
type: 'array',
235
items: validators.map(validator => validator.toSchema()),
236
};
237
}
238
};
239
}
240
241
export function vUnion<T extends IValidator<any>[]>(...validators: T): IValidator<ValidatorType<T[number]>> {
242
return {
243
validate(content: unknown): { content: any; error: undefined } | { content: undefined; error: ValidationError } {
244
let lastError: ValidationError | undefined;
245
for (const validator of validators) {
246
const { content: value, error } = validator.validate(content);
247
if (!error) {
248
return { content: value, error: undefined };
249
}
250
251
lastError = error;
252
}
253
254
return { content: undefined, error: lastError! };
255
},
256
257
toSchema(): JsonSchema {
258
return {
259
oneOf: validators.map(validator => validator.toSchema()),
260
};
261
}
262
};
263
}
264
265
export function vEnum<T extends string[]>(...values: T): IValidator<T[number]> {
266
return {
267
validate(content: unknown): { content: any; error: undefined } | { content: undefined; error: ValidationError } {
268
if (values.indexOf(content as any) === -1) {
269
return { content: undefined, error: { message: `Expected one of: ${values.join(', ')}` } };
270
}
271
272
return { content, error: undefined };
273
},
274
275
toSchema(): JsonSchema {
276
return {
277
enum: values,
278
};
279
}
280
};
281
}
282
283
export function vLiteral<T extends string>(value: T): IValidator<T> {
284
return {
285
validate(content: unknown): { content: any; error: undefined } | { content: undefined; error: ValidationError } {
286
if (content !== value) {
287
return { content: undefined, error: { message: `Expected: ${value}` } };
288
}
289
290
return { content, error: undefined };
291
},
292
293
toSchema(): JsonSchema {
294
return {
295
const: value,
296
};
297
}
298
};
299
}
300
301
export function vLazy<T>(fn: () => IValidator<T>): IValidator<T> {
302
return {
303
validate(content: unknown): { content: any; error: undefined } | { content: undefined; error: ValidationError } {
304
return fn().validate(content);
305
},
306
307
toSchema(): JsonSchema {
308
return fn().toSchema();
309
}
310
};
311
}
312
313