@optimizely-opal/opal-tool-ocp-sdk 1.0.0-beta.2 → 1.0.0-beta.4
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/README.md +114 -0
- package/dist/auth/AuthUtils.d.ts +5 -5
- package/dist/auth/AuthUtils.d.ts.map +1 -1
- package/dist/auth/AuthUtils.js +53 -25
- package/dist/auth/AuthUtils.js.map +1 -1
- package/dist/auth/AuthUtils.test.js +62 -117
- package/dist/auth/AuthUtils.test.js.map +1 -1
- package/dist/function/GlobalToolFunction.d.ts +2 -1
- package/dist/function/GlobalToolFunction.d.ts.map +1 -1
- package/dist/function/GlobalToolFunction.js +25 -4
- package/dist/function/GlobalToolFunction.js.map +1 -1
- package/dist/function/GlobalToolFunction.test.js +57 -8
- package/dist/function/GlobalToolFunction.test.js.map +1 -1
- package/dist/function/ToolFunction.d.ts +8 -2
- package/dist/function/ToolFunction.d.ts.map +1 -1
- package/dist/function/ToolFunction.js +31 -5
- package/dist/function/ToolFunction.js.map +1 -1
- package/dist/function/ToolFunction.test.js +57 -8
- package/dist/function/ToolFunction.test.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/logging/ToolLogger.d.ts +34 -0
- package/dist/logging/ToolLogger.d.ts.map +1 -0
- package/dist/logging/ToolLogger.js +153 -0
- package/dist/logging/ToolLogger.js.map +1 -0
- package/dist/logging/ToolLogger.test.d.ts +2 -0
- package/dist/logging/ToolLogger.test.d.ts.map +1 -0
- package/dist/logging/ToolLogger.test.js +646 -0
- package/dist/logging/ToolLogger.test.js.map +1 -0
- package/dist/service/Service.d.ts +15 -2
- package/dist/service/Service.d.ts.map +1 -1
- package/dist/service/Service.js +43 -17
- package/dist/service/Service.js.map +1 -1
- package/dist/service/Service.test.js +84 -2
- package/dist/service/Service.test.js.map +1 -1
- package/dist/types/ToolError.d.ts +59 -0
- package/dist/types/ToolError.d.ts.map +1 -0
- package/dist/types/ToolError.js +79 -0
- package/dist/types/ToolError.js.map +1 -0
- package/dist/types/ToolError.test.d.ts +2 -0
- package/dist/types/ToolError.test.d.ts.map +1 -0
- package/dist/types/ToolError.test.js +161 -0
- package/dist/types/ToolError.test.js.map +1 -0
- package/dist/validation/ParameterValidator.d.ts +5 -16
- package/dist/validation/ParameterValidator.d.ts.map +1 -1
- package/dist/validation/ParameterValidator.js +10 -3
- package/dist/validation/ParameterValidator.js.map +1 -1
- package/dist/validation/ParameterValidator.test.js +186 -146
- package/dist/validation/ParameterValidator.test.js.map +1 -1
- package/package.json +1 -1
- package/src/auth/AuthUtils.test.ts +62 -157
- package/src/auth/AuthUtils.ts +66 -32
- package/src/function/GlobalToolFunction.test.ts +57 -8
- package/src/function/GlobalToolFunction.ts +37 -6
- package/src/function/ToolFunction.test.ts +57 -8
- package/src/function/ToolFunction.ts +45 -7
- package/src/index.ts +1 -0
- package/src/logging/ToolLogger.test.ts +753 -0
- package/src/logging/ToolLogger.ts +177 -0
- package/src/service/Service.test.ts +103 -2
- package/src/service/Service.ts +45 -17
- package/src/types/ToolError.test.ts +192 -0
- package/src/types/ToolError.ts +95 -0
- package/src/validation/ParameterValidator.test.ts +185 -158
- package/src/validation/ParameterValidator.ts +17 -20
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation error details
|
|
3
|
+
*/
|
|
4
|
+
export interface ValidationError {
|
|
5
|
+
field: string;
|
|
6
|
+
message: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Custom error class for tool functions that supports RFC 9457 Problem Details format
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // Throw a 404 error
|
|
15
|
+
* throw new ToolError('Resource not found', 404, 'The requested task does not exist');
|
|
16
|
+
*
|
|
17
|
+
* // Throw a 400 error
|
|
18
|
+
* throw new ToolError('Invalid input', 400, 'The priority must be "low", "medium", or "high"');
|
|
19
|
+
*
|
|
20
|
+
* // Throw a 500 error (default)
|
|
21
|
+
* throw new ToolError('Database connection failed');
|
|
22
|
+
*
|
|
23
|
+
* // Throw a validation error with multiple field errors
|
|
24
|
+
* throw new ToolError('Validation failed', 400, "See 'errors' field for details.", [
|
|
25
|
+
* { field: 'email', message: 'Invalid email format' },
|
|
26
|
+
* { field: 'age', message: 'Must be a positive number' }
|
|
27
|
+
* ]);
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export class ToolError extends Error {
|
|
31
|
+
/**
|
|
32
|
+
* HTTP status code for the error response
|
|
33
|
+
*/
|
|
34
|
+
public readonly status: number;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Detailed error description (optional)
|
|
38
|
+
*/
|
|
39
|
+
public readonly detail?: string;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Array of validation errors (optional)
|
|
43
|
+
*/
|
|
44
|
+
public readonly errors?: ValidationError[];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Create a new ToolError
|
|
48
|
+
*
|
|
49
|
+
* @param message - Error message (used as RFC 9457 "title" field)
|
|
50
|
+
* @param status - HTTP status code (default: 500)
|
|
51
|
+
* @param detail - Detailed error description (optional)
|
|
52
|
+
* @param errors - Array of validation errors (optional)
|
|
53
|
+
*/
|
|
54
|
+
public constructor(
|
|
55
|
+
message: string,
|
|
56
|
+
status: number = 500,
|
|
57
|
+
detail?: string,
|
|
58
|
+
errors?: ValidationError[]
|
|
59
|
+
) {
|
|
60
|
+
super(message);
|
|
61
|
+
this.name = 'ToolError';
|
|
62
|
+
this.status = status;
|
|
63
|
+
this.detail = detail;
|
|
64
|
+
this.errors = errors;
|
|
65
|
+
|
|
66
|
+
// Maintains proper stack trace for where error was thrown (V8 engines only)
|
|
67
|
+
if (Error.captureStackTrace) {
|
|
68
|
+
Error.captureStackTrace(this, ToolError);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Convert error to RFC 9457 Problem Details format
|
|
74
|
+
*
|
|
75
|
+
* @param instance - URI reference identifying the specific occurrence of the problem
|
|
76
|
+
* @returns RFC 9457 compliant error object
|
|
77
|
+
*/
|
|
78
|
+
public toProblemDetails(instance: string): Record<string, unknown> {
|
|
79
|
+
const problemDetails: Record<string, unknown> = {
|
|
80
|
+
title: this.message,
|
|
81
|
+
status: this.status,
|
|
82
|
+
instance
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
if (this.detail) {
|
|
86
|
+
problemDetails.detail = this.detail;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (this.errors && this.errors.length > 0) {
|
|
90
|
+
problemDetails.errors = this.errors;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return problemDetails;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ParameterValidator } from './ParameterValidator';
|
|
2
2
|
import { Parameter, ParameterType } from '../types/Models';
|
|
3
|
+
import { ToolError } from '../types/ToolError';
|
|
3
4
|
|
|
4
5
|
describe('ParameterValidator', () => {
|
|
5
6
|
describe('validate', () => {
|
|
@@ -22,13 +23,12 @@ describe('ParameterValidator', () => {
|
|
|
22
23
|
config: { theme: 'dark', language: 'en' }
|
|
23
24
|
};
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
expect(result.errors).toHaveLength(0);
|
|
26
|
+
expect(() => {
|
|
27
|
+
ParameterValidator.validate(validParams, paramDefs, '/test-endpoint');
|
|
28
|
+
}).not.toThrow();
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
it('should
|
|
31
|
+
it('should throw ToolError for missing required parameters', () => {
|
|
32
32
|
const paramDefs = [
|
|
33
33
|
new Parameter('name', ParameterType.String, 'User name', true),
|
|
34
34
|
new Parameter('email', ParameterType.String, 'User email', true)
|
|
@@ -39,17 +39,27 @@ describe('ParameterValidator', () => {
|
|
|
39
39
|
// email is missing
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
expect(() => {
|
|
43
|
+
ParameterValidator.validate(params, paramDefs, '/test-endpoint');
|
|
44
|
+
}).toThrow(ToolError);
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
ParameterValidator.validate(params, paramDefs, '/test-endpoint');
|
|
48
|
+
} catch (error) {
|
|
49
|
+
expect(error).toBeInstanceOf(ToolError);
|
|
50
|
+
const toolError = error as ToolError;
|
|
51
|
+
expect(toolError.status).toBe(400);
|
|
52
|
+
expect(toolError.message).toBe('One or more validation errors occurred.');
|
|
53
|
+
expect(toolError.detail).toBe("See 'errors' field for details.");
|
|
54
|
+
expect(toolError.errors).toHaveLength(1);
|
|
55
|
+
expect(toolError.errors).toEqual([{
|
|
56
|
+
field: 'email',
|
|
57
|
+
message: "Required parameter 'email' is missing"
|
|
58
|
+
}]);
|
|
59
|
+
}
|
|
50
60
|
});
|
|
51
61
|
|
|
52
|
-
it('should
|
|
62
|
+
it('should throw ToolError for wrong parameter types', () => {
|
|
53
63
|
const paramDefs = [
|
|
54
64
|
new Parameter('name', ParameterType.String, 'User name', true),
|
|
55
65
|
new Parameter('age', ParameterType.Integer, 'User age', true),
|
|
@@ -66,42 +76,64 @@ describe('ParameterValidator', () => {
|
|
|
66
76
|
config: 'invalid' // should be object
|
|
67
77
|
};
|
|
68
78
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
try {
|
|
80
|
+
ParameterValidator.validate(invalidParams, paramDefs, '/test-endpoint');
|
|
81
|
+
fail('Should have thrown ToolError');
|
|
82
|
+
} catch (error) {
|
|
83
|
+
expect(error).toBeInstanceOf(ToolError);
|
|
84
|
+
const toolError = error as ToolError;
|
|
85
|
+
expect(toolError.status).toBe(400);
|
|
86
|
+
expect(toolError.errors).toHaveLength(5);
|
|
73
87
|
|
|
74
|
-
|
|
75
|
-
|
|
88
|
+
expect(toolError.errors![0].field).toBe('name');
|
|
89
|
+
expect(toolError.errors![0].message).toContain('must be a string');
|
|
76
90
|
|
|
77
|
-
|
|
78
|
-
|
|
91
|
+
expect(toolError.errors![1].field).toBe('age');
|
|
92
|
+
expect(toolError.errors![1].message).toContain('must be an integer');
|
|
79
93
|
|
|
80
|
-
|
|
81
|
-
|
|
94
|
+
expect(toolError.errors![2].field).toBe('active');
|
|
95
|
+
expect(toolError.errors![2].message).toContain('must be a boolean');
|
|
82
96
|
|
|
83
|
-
|
|
84
|
-
|
|
97
|
+
expect(toolError.errors![3].field).toBe('tags');
|
|
98
|
+
expect(toolError.errors![3].message).toContain('must be an array');
|
|
85
99
|
|
|
86
|
-
|
|
87
|
-
|
|
100
|
+
expect(toolError.errors![4].field).toBe('config');
|
|
101
|
+
expect(toolError.errors![4].message).toContain('must be an object');
|
|
102
|
+
}
|
|
88
103
|
});
|
|
89
104
|
|
|
90
|
-
it('should
|
|
105
|
+
it('should throw ToolError when params is null with required parameters', () => {
|
|
91
106
|
const paramDefs = [
|
|
92
107
|
new Parameter('required', ParameterType.String, 'Required param', true),
|
|
93
108
|
new Parameter('optional', ParameterType.String, 'Optional param', false)
|
|
94
109
|
];
|
|
95
110
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
111
|
+
try {
|
|
112
|
+
ParameterValidator.validate(null, paramDefs, '/test-endpoint');
|
|
113
|
+
fail('Should have thrown ToolError');
|
|
114
|
+
} catch (error) {
|
|
115
|
+
expect(error).toBeInstanceOf(ToolError);
|
|
116
|
+
const toolError = error as ToolError;
|
|
117
|
+
expect(toolError.errors).toHaveLength(1);
|
|
118
|
+
expect(toolError.errors![0].field).toBe('required');
|
|
119
|
+
}
|
|
120
|
+
});
|
|
100
121
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
122
|
+
it('should throw ToolError when params is undefined with required parameters', () => {
|
|
123
|
+
const paramDefs = [
|
|
124
|
+
new Parameter('required', ParameterType.String, 'Required param', true),
|
|
125
|
+
new Parameter('optional', ParameterType.String, 'Optional param', false)
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
ParameterValidator.validate(undefined, paramDefs, '/test-endpoint');
|
|
130
|
+
fail('Should have thrown ToolError');
|
|
131
|
+
} catch (error) {
|
|
132
|
+
expect(error).toBeInstanceOf(ToolError);
|
|
133
|
+
const toolError = error as ToolError;
|
|
134
|
+
expect(toolError.errors).toHaveLength(1);
|
|
135
|
+
expect(toolError.errors![0].field).toBe('required');
|
|
136
|
+
}
|
|
105
137
|
});
|
|
106
138
|
|
|
107
139
|
it('should allow optional parameters to be missing', () => {
|
|
@@ -115,10 +147,9 @@ describe('ParameterValidator', () => {
|
|
|
115
147
|
// age is optional and missing
|
|
116
148
|
};
|
|
117
149
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
expect(result.errors).toHaveLength(0);
|
|
150
|
+
expect(() => {
|
|
151
|
+
ParameterValidator.validate(params, paramDefs, '/test-endpoint');
|
|
152
|
+
}).not.toThrow();
|
|
122
153
|
});
|
|
123
154
|
|
|
124
155
|
it('should distinguish between integer and number types', () => {
|
|
@@ -132,210 +163,206 @@ describe('ParameterValidator', () => {
|
|
|
132
163
|
score: 85.5 // number is fine
|
|
133
164
|
};
|
|
134
165
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
166
|
+
try {
|
|
167
|
+
ParameterValidator.validate(params1, paramDefs, '/test-endpoint');
|
|
168
|
+
fail('Should have thrown ToolError');
|
|
169
|
+
} catch (error) {
|
|
170
|
+
expect(error).toBeInstanceOf(ToolError);
|
|
171
|
+
const toolError = error as ToolError;
|
|
172
|
+
expect(toolError.errors).toHaveLength(1);
|
|
173
|
+
expect(toolError.errors![0].field).toBe('count');
|
|
174
|
+
expect(toolError.errors![0].message).toContain('must be an integer');
|
|
175
|
+
}
|
|
140
176
|
|
|
141
177
|
const params2 = {
|
|
142
178
|
count: 25, // integer is fine
|
|
143
179
|
score: 85.5 // number is fine
|
|
144
180
|
};
|
|
145
181
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
182
|
+
expect(() => {
|
|
183
|
+
ParameterValidator.validate(params2, paramDefs, '/test-endpoint');
|
|
184
|
+
}).not.toThrow();
|
|
149
185
|
});
|
|
150
186
|
|
|
151
187
|
it('should handle array vs object distinction', () => {
|
|
152
188
|
const paramDefs = [
|
|
153
|
-
new Parameter('tags', ParameterType.List, '
|
|
154
|
-
new Parameter('config', ParameterType.Dictionary, 'Config
|
|
189
|
+
new Parameter('tags', ParameterType.List, 'Tags', true),
|
|
190
|
+
new Parameter('config', ParameterType.Dictionary, 'Config', true)
|
|
155
191
|
];
|
|
156
192
|
|
|
157
193
|
const params = {
|
|
158
|
-
tags: {
|
|
159
|
-
config: ['
|
|
194
|
+
tags: { key: 'value' }, // should be array, not object
|
|
195
|
+
config: ['item1', 'item2'] // should be object, not array
|
|
160
196
|
};
|
|
161
197
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
expect(result.errors[1].message).toContain('must be an object');
|
|
198
|
+
try {
|
|
199
|
+
ParameterValidator.validate(params, paramDefs, '/test-endpoint');
|
|
200
|
+
fail('Should have thrown ToolError');
|
|
201
|
+
} catch (error) {
|
|
202
|
+
expect(error).toBeInstanceOf(ToolError);
|
|
203
|
+
const toolError = error as ToolError;
|
|
204
|
+
expect(toolError.errors).toHaveLength(2);
|
|
205
|
+
}
|
|
171
206
|
});
|
|
172
207
|
|
|
173
208
|
it('should handle null values for optional parameters', () => {
|
|
174
209
|
const paramDefs = [
|
|
175
210
|
new Parameter('name', ParameterType.String, 'User name', true),
|
|
176
|
-
new Parameter('
|
|
211
|
+
new Parameter('nickname', ParameterType.String, 'Nickname', false)
|
|
177
212
|
];
|
|
178
213
|
|
|
179
214
|
const params = {
|
|
180
215
|
name: 'John Doe',
|
|
181
|
-
|
|
216
|
+
nickname: null // optional param can be null
|
|
182
217
|
};
|
|
183
218
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
219
|
+
expect(() => {
|
|
220
|
+
ParameterValidator.validate(params, paramDefs, '/test-endpoint');
|
|
221
|
+
}).not.toThrow();
|
|
187
222
|
});
|
|
188
223
|
|
|
189
224
|
it('should handle edge cases for number validation', () => {
|
|
190
225
|
const paramDefs = [
|
|
191
|
-
new Parameter('
|
|
226
|
+
new Parameter('value', ParameterType.Number, 'Number value', true)
|
|
192
227
|
];
|
|
193
228
|
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
expect(
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
229
|
+
// NaN should fail
|
|
230
|
+
try {
|
|
231
|
+
ParameterValidator.validate({ value: NaN }, paramDefs, '/test-endpoint');
|
|
232
|
+
fail('Should have thrown ToolError for NaN');
|
|
233
|
+
} catch (error) {
|
|
234
|
+
expect(error).toBeInstanceOf(ToolError);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Infinity should pass (it's a number)
|
|
238
|
+
expect(() => {
|
|
239
|
+
ParameterValidator.validate({ value: Infinity }, paramDefs, '/test-endpoint');
|
|
240
|
+
}).not.toThrow();
|
|
241
|
+
|
|
242
|
+
// Negative numbers should pass
|
|
243
|
+
expect(() => {
|
|
244
|
+
ParameterValidator.validate({ value: -42.5 }, paramDefs, '/test-endpoint');
|
|
245
|
+
}).not.toThrow();
|
|
211
246
|
});
|
|
212
247
|
|
|
213
248
|
it('should handle edge cases for integer validation', () => {
|
|
214
249
|
const paramDefs = [
|
|
215
|
-
new Parameter('count', ParameterType.Integer, '
|
|
250
|
+
new Parameter('count', ParameterType.Integer, 'Count', true)
|
|
216
251
|
];
|
|
217
252
|
|
|
218
|
-
//
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
expect(result2.isValid).toBe(true);
|
|
228
|
-
expect(result2.errors).toHaveLength(0);
|
|
229
|
-
|
|
230
|
-
// Test very large integers
|
|
231
|
-
const params3 = { count: Number.MAX_SAFE_INTEGER };
|
|
232
|
-
const result3 = ParameterValidator.validate(params3, paramDefs);
|
|
233
|
-
expect(result3.isValid).toBe(true);
|
|
234
|
-
expect(result3.errors).toHaveLength(0);
|
|
253
|
+
// Zero should pass
|
|
254
|
+
expect(() => {
|
|
255
|
+
ParameterValidator.validate({ count: 0 }, paramDefs, '/test-endpoint');
|
|
256
|
+
}).not.toThrow();
|
|
257
|
+
|
|
258
|
+
// Negative integer should pass
|
|
259
|
+
expect(() => {
|
|
260
|
+
ParameterValidator.validate({ count: -10 }, paramDefs, '/test-endpoint');
|
|
261
|
+
}).not.toThrow();
|
|
235
262
|
});
|
|
236
263
|
|
|
237
264
|
it('should handle empty arrays and objects', () => {
|
|
238
265
|
const paramDefs = [
|
|
239
|
-
new Parameter('tags', ParameterType.List, '
|
|
240
|
-
new Parameter('config', ParameterType.Dictionary, 'Config
|
|
266
|
+
new Parameter('tags', ParameterType.List, 'Tags', true),
|
|
267
|
+
new Parameter('config', ParameterType.Dictionary, 'Config', true)
|
|
241
268
|
];
|
|
242
269
|
|
|
243
270
|
const params = {
|
|
244
|
-
tags: [],
|
|
245
|
-
config: {}
|
|
271
|
+
tags: [],
|
|
272
|
+
config: {}
|
|
246
273
|
};
|
|
247
274
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
275
|
+
expect(() => {
|
|
276
|
+
ParameterValidator.validate(params, paramDefs, '/test-endpoint');
|
|
277
|
+
}).not.toThrow();
|
|
251
278
|
});
|
|
252
279
|
|
|
253
280
|
it('should handle special string values', () => {
|
|
254
281
|
const paramDefs = [
|
|
255
|
-
new Parameter('
|
|
282
|
+
new Parameter('value', ParameterType.String, 'String value', true)
|
|
256
283
|
];
|
|
257
284
|
|
|
258
|
-
//
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
expect(result2.isValid).toBe(true);
|
|
268
|
-
expect(result2.errors).toHaveLength(0);
|
|
285
|
+
// Empty string should pass
|
|
286
|
+
expect(() => {
|
|
287
|
+
ParameterValidator.validate({ value: '' }, paramDefs, '/test-endpoint');
|
|
288
|
+
}).not.toThrow();
|
|
289
|
+
|
|
290
|
+
// String with only whitespace should pass
|
|
291
|
+
expect(() => {
|
|
292
|
+
ParameterValidator.validate({ value: ' ' }, paramDefs, '/test-endpoint');
|
|
293
|
+
}).not.toThrow();
|
|
269
294
|
});
|
|
270
295
|
|
|
271
|
-
it('should
|
|
296
|
+
it('should collect multiple validation errors', () => {
|
|
272
297
|
const paramDefs = [
|
|
273
|
-
new Parameter('name', ParameterType.String, '
|
|
274
|
-
new Parameter('age', ParameterType.Integer, '
|
|
275
|
-
new Parameter('email', ParameterType.String, '
|
|
298
|
+
new Parameter('name', ParameterType.String, 'Name', true),
|
|
299
|
+
new Parameter('age', ParameterType.Integer, 'Age', true),
|
|
300
|
+
new Parameter('email', ParameterType.String, 'Email', true)
|
|
276
301
|
];
|
|
277
302
|
|
|
278
303
|
const params = {
|
|
279
|
-
name
|
|
280
|
-
age: '
|
|
304
|
+
// name is missing
|
|
305
|
+
age: 'invalid' // wrong type
|
|
281
306
|
// email is missing
|
|
282
307
|
};
|
|
283
308
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
309
|
+
try {
|
|
310
|
+
ParameterValidator.validate(params, paramDefs, '/test-endpoint');
|
|
311
|
+
fail('Should have thrown ToolError');
|
|
312
|
+
} catch (error) {
|
|
313
|
+
expect(error).toBeInstanceOf(ToolError);
|
|
314
|
+
const toolError = error as ToolError;
|
|
315
|
+
expect(toolError.errors).toHaveLength(3);
|
|
316
|
+
|
|
317
|
+
expect(toolError.errors!.some((e) => e.field === 'name')).toBe(true);
|
|
318
|
+
expect(toolError.errors!.some((e) => e.field === 'age')).toBe(true);
|
|
319
|
+
expect(toolError.errors!.some((e) => e.field === 'email')).toBe(true);
|
|
320
|
+
}
|
|
291
321
|
});
|
|
292
322
|
|
|
293
323
|
it('should handle tools with no parameter definitions', () => {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
324
|
+
expect(() => {
|
|
325
|
+
ParameterValidator.validate({ anyParam: 'value' }, [], '/test-endpoint');
|
|
326
|
+
}).not.toThrow();
|
|
297
327
|
});
|
|
298
328
|
|
|
299
329
|
it('should handle extra parameters not in definition', () => {
|
|
300
330
|
const paramDefs = [
|
|
301
|
-
new Parameter('name', ParameterType.String, '
|
|
331
|
+
new Parameter('name', ParameterType.String, 'Name', true)
|
|
302
332
|
];
|
|
303
333
|
|
|
304
334
|
const params = {
|
|
305
|
-
name: 'John
|
|
306
|
-
extraParam: '
|
|
335
|
+
name: 'John',
|
|
336
|
+
extraParam: 'ignored', // not in definition
|
|
337
|
+
anotherExtra: 123
|
|
307
338
|
};
|
|
308
339
|
|
|
309
|
-
|
|
310
|
-
expect(
|
|
311
|
-
|
|
340
|
+
// Extra parameters should be ignored
|
|
341
|
+
expect(() => {
|
|
342
|
+
ParameterValidator.validate(params, paramDefs, '/test-endpoint');
|
|
343
|
+
}).not.toThrow();
|
|
312
344
|
});
|
|
313
345
|
|
|
314
346
|
it('should handle nested objects and arrays', () => {
|
|
315
347
|
const paramDefs = [
|
|
316
|
-
new Parameter('config', ParameterType.Dictionary, 'Config
|
|
317
|
-
new Parameter('
|
|
348
|
+
new Parameter('config', ParameterType.Dictionary, 'Config', true),
|
|
349
|
+
new Parameter('items', ParameterType.List, 'Items', true)
|
|
318
350
|
];
|
|
319
351
|
|
|
320
352
|
const params = {
|
|
321
353
|
config: {
|
|
322
354
|
nested: {
|
|
323
|
-
|
|
324
|
-
|
|
355
|
+
deeply: {
|
|
356
|
+
nested: 'value'
|
|
325
357
|
}
|
|
326
|
-
}
|
|
327
|
-
array: [1, 2, 3]
|
|
358
|
+
}
|
|
328
359
|
},
|
|
329
|
-
|
|
330
|
-
[1, 2, 3],
|
|
331
|
-
[4, 5, 6],
|
|
332
|
-
{ nested: 'object in array' }
|
|
333
|
-
]
|
|
360
|
+
items: [1, 2, [3, 4, [5, 6]]]
|
|
334
361
|
};
|
|
335
362
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
363
|
+
expect(() => {
|
|
364
|
+
ParameterValidator.validate(params, paramDefs, '/test-endpoint');
|
|
365
|
+
}).not.toThrow();
|
|
339
366
|
});
|
|
340
367
|
});
|
|
341
368
|
});
|
|
@@ -1,20 +1,5 @@
|
|
|
1
1
|
import { Parameter, ParameterType } from '../types/Models';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Validation error details
|
|
5
|
-
*/
|
|
6
|
-
export interface ValidationError {
|
|
7
|
-
field: string;
|
|
8
|
-
message: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Validation result
|
|
13
|
-
*/
|
|
14
|
-
export interface ValidationResult {
|
|
15
|
-
isValid: boolean;
|
|
16
|
-
errors: ValidationError[];
|
|
17
|
-
}
|
|
2
|
+
import { ToolError, ValidationError } from '../types/ToolError';
|
|
18
3
|
|
|
19
4
|
/**
|
|
20
5
|
* Parameter validator for tool inputs
|
|
@@ -22,14 +7,18 @@ export interface ValidationResult {
|
|
|
22
7
|
export class ParameterValidator {
|
|
23
8
|
/**
|
|
24
9
|
* Validate parameters against their definitions
|
|
10
|
+
* Throws ToolError with status 400 if validation fails
|
|
11
|
+
*
|
|
25
12
|
* @param params The actual parameters received
|
|
26
13
|
* @param parameterDefinitions The expected parameter definitions
|
|
27
|
-
* @
|
|
14
|
+
* @param _endpoint The endpoint being validated (reserved for future use)
|
|
15
|
+
* @throws {ToolError} When validation fails
|
|
28
16
|
*/
|
|
29
17
|
public static validate(
|
|
30
18
|
params: any,
|
|
31
|
-
parameterDefinitions: Parameter[]
|
|
32
|
-
|
|
19
|
+
parameterDefinitions: Parameter[],
|
|
20
|
+
_endpoint: string
|
|
21
|
+
): void {
|
|
33
22
|
const errors: ValidationError[] = [];
|
|
34
23
|
|
|
35
24
|
// Validate each defined parameter
|
|
@@ -57,7 +46,15 @@ export class ParameterValidator {
|
|
|
57
46
|
}
|
|
58
47
|
}
|
|
59
48
|
|
|
60
|
-
|
|
49
|
+
// Throw ToolError if validation failed
|
|
50
|
+
if (errors.length > 0) {
|
|
51
|
+
throw new ToolError(
|
|
52
|
+
'One or more validation errors occurred.',
|
|
53
|
+
400,
|
|
54
|
+
"See 'errors' field for details.",
|
|
55
|
+
errors
|
|
56
|
+
);
|
|
57
|
+
}
|
|
61
58
|
}
|
|
62
59
|
|
|
63
60
|
/**
|