@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.
Files changed (67) hide show
  1. package/README.md +114 -0
  2. package/dist/auth/AuthUtils.d.ts +5 -5
  3. package/dist/auth/AuthUtils.d.ts.map +1 -1
  4. package/dist/auth/AuthUtils.js +53 -25
  5. package/dist/auth/AuthUtils.js.map +1 -1
  6. package/dist/auth/AuthUtils.test.js +62 -117
  7. package/dist/auth/AuthUtils.test.js.map +1 -1
  8. package/dist/function/GlobalToolFunction.d.ts +2 -1
  9. package/dist/function/GlobalToolFunction.d.ts.map +1 -1
  10. package/dist/function/GlobalToolFunction.js +25 -4
  11. package/dist/function/GlobalToolFunction.js.map +1 -1
  12. package/dist/function/GlobalToolFunction.test.js +57 -8
  13. package/dist/function/GlobalToolFunction.test.js.map +1 -1
  14. package/dist/function/ToolFunction.d.ts +8 -2
  15. package/dist/function/ToolFunction.d.ts.map +1 -1
  16. package/dist/function/ToolFunction.js +31 -5
  17. package/dist/function/ToolFunction.js.map +1 -1
  18. package/dist/function/ToolFunction.test.js +57 -8
  19. package/dist/function/ToolFunction.test.js.map +1 -1
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +1 -0
  23. package/dist/index.js.map +1 -1
  24. package/dist/logging/ToolLogger.d.ts +34 -0
  25. package/dist/logging/ToolLogger.d.ts.map +1 -0
  26. package/dist/logging/ToolLogger.js +153 -0
  27. package/dist/logging/ToolLogger.js.map +1 -0
  28. package/dist/logging/ToolLogger.test.d.ts +2 -0
  29. package/dist/logging/ToolLogger.test.d.ts.map +1 -0
  30. package/dist/logging/ToolLogger.test.js +646 -0
  31. package/dist/logging/ToolLogger.test.js.map +1 -0
  32. package/dist/service/Service.d.ts +15 -2
  33. package/dist/service/Service.d.ts.map +1 -1
  34. package/dist/service/Service.js +43 -17
  35. package/dist/service/Service.js.map +1 -1
  36. package/dist/service/Service.test.js +84 -2
  37. package/dist/service/Service.test.js.map +1 -1
  38. package/dist/types/ToolError.d.ts +59 -0
  39. package/dist/types/ToolError.d.ts.map +1 -0
  40. package/dist/types/ToolError.js +79 -0
  41. package/dist/types/ToolError.js.map +1 -0
  42. package/dist/types/ToolError.test.d.ts +2 -0
  43. package/dist/types/ToolError.test.d.ts.map +1 -0
  44. package/dist/types/ToolError.test.js +161 -0
  45. package/dist/types/ToolError.test.js.map +1 -0
  46. package/dist/validation/ParameterValidator.d.ts +5 -16
  47. package/dist/validation/ParameterValidator.d.ts.map +1 -1
  48. package/dist/validation/ParameterValidator.js +10 -3
  49. package/dist/validation/ParameterValidator.js.map +1 -1
  50. package/dist/validation/ParameterValidator.test.js +186 -146
  51. package/dist/validation/ParameterValidator.test.js.map +1 -1
  52. package/package.json +1 -1
  53. package/src/auth/AuthUtils.test.ts +62 -157
  54. package/src/auth/AuthUtils.ts +66 -32
  55. package/src/function/GlobalToolFunction.test.ts +57 -8
  56. package/src/function/GlobalToolFunction.ts +37 -6
  57. package/src/function/ToolFunction.test.ts +57 -8
  58. package/src/function/ToolFunction.ts +45 -7
  59. package/src/index.ts +1 -0
  60. package/src/logging/ToolLogger.test.ts +753 -0
  61. package/src/logging/ToolLogger.ts +177 -0
  62. package/src/service/Service.test.ts +103 -2
  63. package/src/service/Service.ts +45 -17
  64. package/src/types/ToolError.test.ts +192 -0
  65. package/src/types/ToolError.ts +95 -0
  66. package/src/validation/ParameterValidator.test.ts +185 -158
  67. 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
- const result = ParameterValidator.validate(validParams, paramDefs);
26
-
27
- expect(result.isValid).toBe(true);
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 fail validation for missing required parameters', () => {
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
- const result = ParameterValidator.validate(params, paramDefs);
43
-
44
- expect(result.isValid).toBe(false);
45
- expect(result.errors).toHaveLength(1);
46
- expect(result.errors[0]).toEqual({
47
- field: 'email',
48
- message: "Required parameter 'email' is missing"
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 fail validation for wrong parameter types', () => {
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
- const result = ParameterValidator.validate(invalidParams, paramDefs);
70
-
71
- expect(result.isValid).toBe(false);
72
- expect(result.errors).toHaveLength(5);
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
- expect(result.errors[0].field).toBe('name');
75
- expect(result.errors[0].message).toContain('must be a string');
88
+ expect(toolError.errors![0].field).toBe('name');
89
+ expect(toolError.errors![0].message).toContain('must be a string');
76
90
 
77
- expect(result.errors[1].field).toBe('age');
78
- expect(result.errors[1].message).toContain('must be an integer');
91
+ expect(toolError.errors![1].field).toBe('age');
92
+ expect(toolError.errors![1].message).toContain('must be an integer');
79
93
 
80
- expect(result.errors[2].field).toBe('active');
81
- expect(result.errors[2].message).toContain('must be a boolean');
94
+ expect(toolError.errors![2].field).toBe('active');
95
+ expect(toolError.errors![2].message).toContain('must be a boolean');
82
96
 
83
- expect(result.errors[3].field).toBe('tags');
84
- expect(result.errors[3].message).toContain('must be an array');
97
+ expect(toolError.errors![3].field).toBe('tags');
98
+ expect(toolError.errors![3].message).toContain('must be an array');
85
99
 
86
- expect(result.errors[4].field).toBe('config');
87
- expect(result.errors[4].message).toContain('must be an object');
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 handle null/undefined parameters correctly', () => {
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
- const result1 = ParameterValidator.validate(null, paramDefs);
97
- expect(result1.isValid).toBe(false);
98
- expect(result1.errors).toHaveLength(1);
99
- expect(result1.errors[0].field).toBe('required');
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
- const result2 = ParameterValidator.validate(undefined, paramDefs);
102
- expect(result2.isValid).toBe(false);
103
- expect(result2.errors).toHaveLength(1);
104
- expect(result2.errors[0].field).toBe('required');
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
- const result = ParameterValidator.validate(params, paramDefs);
119
-
120
- expect(result.isValid).toBe(true);
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
- const result1 = ParameterValidator.validate(params1, paramDefs);
136
- expect(result1.isValid).toBe(false);
137
- expect(result1.errors).toHaveLength(1);
138
- expect(result1.errors[0].field).toBe('count');
139
- expect(result1.errors[0].message).toContain('must be an integer');
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
- const result2 = ParameterValidator.validate(params2, paramDefs);
147
- expect(result2.isValid).toBe(true);
148
- expect(result2.errors).toHaveLength(0);
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, 'Tag list', true),
154
- new Parameter('config', ParameterType.Dictionary, 'Config object', true)
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: { 0: 'tag1', 1: 'tag2' }, // object that looks like array
159
- config: ['key1', 'key2'] // array instead of object
194
+ tags: { key: 'value' }, // should be array, not object
195
+ config: ['item1', 'item2'] // should be object, not array
160
196
  };
161
197
 
162
- const result = ParameterValidator.validate(params, paramDefs);
163
- expect(result.isValid).toBe(false);
164
- expect(result.errors).toHaveLength(2);
165
-
166
- expect(result.errors[0].field).toBe('tags');
167
- expect(result.errors[0].message).toContain('must be an array');
168
-
169
- expect(result.errors[1].field).toBe('config');
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('age', ParameterType.Integer, 'User age', false)
211
+ new Parameter('nickname', ParameterType.String, 'Nickname', false)
177
212
  ];
178
213
 
179
214
  const params = {
180
215
  name: 'John Doe',
181
- age: null // null for optional parameter should be allowed
216
+ nickname: null // optional param can be null
182
217
  };
183
218
 
184
- const result = ParameterValidator.validate(params, paramDefs);
185
- expect(result.isValid).toBe(true);
186
- expect(result.errors).toHaveLength(0);
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('score', ParameterType.Number, 'Score value', true)
226
+ new Parameter('value', ParameterType.Number, 'Number value', true)
192
227
  ];
193
228
 
194
- // Test NaN
195
- const params1 = { score: NaN };
196
- const result1 = ParameterValidator.validate(params1, paramDefs);
197
- expect(result1.isValid).toBe(false);
198
- expect(result1.errors[0].message).toContain('must be a number');
199
-
200
- // Test Infinity
201
- const params2 = { score: Infinity };
202
- const result2 = ParameterValidator.validate(params2, paramDefs);
203
- expect(result2.isValid).toBe(true);
204
- expect(result2.errors).toHaveLength(0);
205
-
206
- // Test negative numbers
207
- const params3 = { score: -42.5 };
208
- const result3 = ParameterValidator.validate(params3, paramDefs);
209
- expect(result3.isValid).toBe(true);
210
- expect(result3.errors).toHaveLength(0);
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, 'Item count', true)
250
+ new Parameter('count', ParameterType.Integer, 'Count', true)
216
251
  ];
217
252
 
218
- // Test negative integers
219
- const params1 = { count: -5 };
220
- const result1 = ParameterValidator.validate(params1, paramDefs);
221
- expect(result1.isValid).toBe(true);
222
- expect(result1.errors).toHaveLength(0);
223
-
224
- // Test zero
225
- const params2 = { count: 0 };
226
- const result2 = ParameterValidator.validate(params2, paramDefs);
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, 'Tag list', true),
240
- new Parameter('config', ParameterType.Dictionary, 'Config object', true)
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: [], // empty array should be valid
245
- config: {} // empty object should be valid
271
+ tags: [],
272
+ config: {}
246
273
  };
247
274
 
248
- const result = ParameterValidator.validate(params, paramDefs);
249
- expect(result.isValid).toBe(true);
250
- expect(result.errors).toHaveLength(0);
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('text', ParameterType.String, 'Text value', true)
282
+ new Parameter('value', ParameterType.String, 'String value', true)
256
283
  ];
257
284
 
258
- // Test empty string
259
- const params1 = { text: '' };
260
- const result1 = ParameterValidator.validate(params1, paramDefs);
261
- expect(result1.isValid).toBe(true);
262
- expect(result1.errors).toHaveLength(0);
263
-
264
- // Test string with special characters
265
- const params2 = { text: 'Hello\nWorld\t!' };
266
- const result2 = ParameterValidator.validate(params2, paramDefs);
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 handle multiple validation errors', () => {
296
+ it('should collect multiple validation errors', () => {
272
297
  const paramDefs = [
273
- new Parameter('name', ParameterType.String, 'User name', true),
274
- new Parameter('age', ParameterType.Integer, 'User age', true),
275
- new Parameter('email', ParameterType.String, 'User email', true)
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: 123, // wrong type
280
- age: '25' // wrong type
304
+ // name is missing
305
+ age: 'invalid' // wrong type
281
306
  // email is missing
282
307
  };
283
308
 
284
- const result = ParameterValidator.validate(params, paramDefs);
285
- expect(result.isValid).toBe(false);
286
- expect(result.errors).toHaveLength(3);
287
-
288
- expect(result.errors.some((e) => e.field === 'name')).toBe(true);
289
- expect(result.errors.some((e) => e.field === 'age')).toBe(true);
290
- expect(result.errors.some((e) => e.field === 'email')).toBe(true);
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
- const result = ParameterValidator.validate({ someParam: 'value' }, []);
295
- expect(result.isValid).toBe(true);
296
- expect(result.errors).toHaveLength(0);
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, 'User name', true)
331
+ new Parameter('name', ParameterType.String, 'Name', true)
302
332
  ];
303
333
 
304
334
  const params = {
305
- name: 'John Doe',
306
- extraParam: 'should be ignored'
335
+ name: 'John',
336
+ extraParam: 'ignored', // not in definition
337
+ anotherExtra: 123
307
338
  };
308
339
 
309
- const result = ParameterValidator.validate(params, paramDefs);
310
- expect(result.isValid).toBe(true);
311
- expect(result.errors).toHaveLength(0);
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 object', true),
317
- new Parameter('matrix', ParameterType.List, 'Matrix data', true)
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
- deep: {
324
- value: 'test'
355
+ deeply: {
356
+ nested: 'value'
325
357
  }
326
- },
327
- array: [1, 2, 3]
358
+ }
328
359
  },
329
- matrix: [
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
- const result = ParameterValidator.validate(params, paramDefs);
337
- expect(result.isValid).toBe(true);
338
- expect(result.errors).toHaveLength(0);
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
- * @returns Validation result with any errors found
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
- ): ValidationResult {
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
- return { isValid: errors.length === 0, errors };
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
  /**