@optimizely-opal/opal-tool-ocp-sdk 1.0.0-beta.6 → 1.0.0-beta.7
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 +13 -3
- package/dist/auth/AuthUtils.d.ts +0 -7
- package/dist/auth/AuthUtils.d.ts.map +1 -1
- package/dist/auth/AuthUtils.js +0 -27
- package/dist/auth/AuthUtils.js.map +1 -1
- package/dist/auth/AuthUtils.test.js +0 -99
- package/dist/auth/AuthUtils.test.js.map +1 -1
- package/dist/function/GlobalToolFunction.d.ts +3 -2
- package/dist/function/GlobalToolFunction.d.ts.map +1 -1
- package/dist/function/GlobalToolFunction.js +4 -4
- package/dist/function/GlobalToolFunction.js.map +1 -1
- package/dist/function/GlobalToolFunction.test.js +16 -4
- package/dist/function/GlobalToolFunction.test.js.map +1 -1
- package/dist/function/ToolFunction.d.ts +3 -2
- package/dist/function/ToolFunction.d.ts.map +1 -1
- package/dist/function/ToolFunction.js +5 -12
- package/dist/function/ToolFunction.js.map +1 -1
- package/dist/function/ToolFunction.test.js +15 -209
- package/dist/function/ToolFunction.test.js.map +1 -1
- package/dist/logging/ToolLogger.d.ts.map +1 -1
- package/dist/logging/ToolLogger.js +1 -2
- package/dist/logging/ToolLogger.js.map +1 -1
- package/dist/logging/ToolLogger.test.js +2 -114
- package/dist/logging/ToolLogger.test.js.map +1 -1
- package/dist/service/Service.d.ts +0 -73
- package/dist/service/Service.d.ts.map +1 -1
- package/dist/service/Service.js +42 -188
- package/dist/service/Service.js.map +1 -1
- package/dist/service/Service.test.js +34 -380
- package/dist/service/Service.test.js.map +1 -1
- package/dist/types/Models.d.ts +4 -0
- package/dist/types/Models.d.ts.map +1 -1
- package/dist/types/ToolError.d.ts +0 -13
- package/dist/types/ToolError.d.ts.map +1 -1
- package/dist/types/ToolError.js +2 -30
- package/dist/types/ToolError.js.map +1 -1
- package/dist/types/ToolError.test.js +3 -27
- package/dist/types/ToolError.test.js.map +1 -1
- package/dist/validation/ParameterValidator.test.js +1 -2
- package/dist/validation/ParameterValidator.test.js.map +1 -1
- package/package.json +1 -1
- package/src/auth/AuthUtils.test.ts +1 -115
- package/src/auth/AuthUtils.ts +1 -30
- package/src/function/GlobalToolFunction.test.ts +21 -6
- package/src/function/GlobalToolFunction.ts +6 -5
- package/src/function/ToolFunction.test.ts +21 -225
- package/src/function/ToolFunction.ts +8 -13
- package/src/logging/ToolLogger.test.ts +2 -118
- package/src/logging/ToolLogger.ts +1 -2
- package/src/service/Service.test.ts +32 -474
- package/src/service/Service.ts +41 -245
- package/src/types/Models.ts +5 -0
- package/src/types/ToolError.test.ts +3 -33
- package/src/types/ToolError.ts +2 -32
- package/src/validation/ParameterValidator.test.ts +1 -4
package/src/service/Service.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { AuthRequirement, IslandResponse, Parameter } from '../types/Models';
|
|
3
3
|
import { ToolError } from '../types/ToolError';
|
|
4
4
|
import * as App from '@zaiusinc/app-sdk';
|
|
5
|
-
import { logger, Headers
|
|
5
|
+
import { logger, Headers } from '@zaiusinc/app-sdk';
|
|
6
6
|
import { ToolFunction } from '../function/ToolFunction';
|
|
7
7
|
import { GlobalToolFunction } from '../function/GlobalToolFunction';
|
|
8
8
|
import { ParameterValidator } from '../validation/ParameterValidator';
|
|
@@ -17,27 +17,6 @@ export class NestedInteractions {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
/**
|
|
21
|
-
* Interface for tool description override data (compatible with KV store)
|
|
22
|
-
*/
|
|
23
|
-
interface ToolOverride extends App.ValueHash {
|
|
24
|
-
name: string;
|
|
25
|
-
description: string;
|
|
26
|
-
parameters: ParameterOverride[];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
interface ParameterOverride extends App.ValueHash {
|
|
30
|
-
name: string;
|
|
31
|
-
description: string;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Interface for stored override data in KV store
|
|
36
|
-
*/
|
|
37
|
-
interface StoredOverrides extends App.ValueHash {
|
|
38
|
-
[tool_name: string]: ToolOverride;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
20
|
/**
|
|
42
21
|
* Result type for interaction handlers
|
|
43
22
|
*/
|
|
@@ -117,107 +96,6 @@ export class ToolsService {
|
|
|
117
96
|
private functions: Map<string, Tool<any>> = new Map();
|
|
118
97
|
private interactions: Map<string, Interaction<any>> = new Map();
|
|
119
98
|
|
|
120
|
-
/**
|
|
121
|
-
* Generate KV store key for tool overrides
|
|
122
|
-
* @param appVersionId App version ID
|
|
123
|
-
* @param functionName Function name
|
|
124
|
-
* @returns KV store key
|
|
125
|
-
*/
|
|
126
|
-
private getOverrideKey(appVersionId: string, functionName: string): string {
|
|
127
|
-
return `${appVersionId}:${functionName}:opal-tools-overrides`;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Get tool overrides from KV store
|
|
132
|
-
* @param appVersionId App version ID
|
|
133
|
-
* @param functionName Function name
|
|
134
|
-
* @returns Stored overrides or null if not found
|
|
135
|
-
*/
|
|
136
|
-
private async getOverrides(appVersionId: string, functionName: string): Promise<StoredOverrides> {
|
|
137
|
-
const key = this.getOverrideKey(appVersionId, functionName);
|
|
138
|
-
return await App.storage.kvStore.get<StoredOverrides>(key);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Apply overrides to tool definitions
|
|
143
|
-
* @param tools Original tool definitions
|
|
144
|
-
* @param overrides Override data
|
|
145
|
-
* @returns Tools with overrides applied
|
|
146
|
-
*/
|
|
147
|
-
private applyOverrides(tools: Array<Tool<any>>, overrides: StoredOverrides): Array<Tool<any>> {
|
|
148
|
-
return tools.map((tool) => {
|
|
149
|
-
const override = overrides[tool.name];
|
|
150
|
-
if (!override) {
|
|
151
|
-
return tool;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Clone the tool and apply overrides
|
|
155
|
-
const overriddenTool = new Tool(
|
|
156
|
-
tool.name,
|
|
157
|
-
override.description,
|
|
158
|
-
tool.parameters.map((param) => {
|
|
159
|
-
const paramOverride = override.parameters?.find((p) => p.name === param.name);
|
|
160
|
-
if (paramOverride) {
|
|
161
|
-
return new Parameter(
|
|
162
|
-
param.name,
|
|
163
|
-
param.type,
|
|
164
|
-
paramOverride.description,
|
|
165
|
-
param.required
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
return param;
|
|
169
|
-
}),
|
|
170
|
-
tool.endpoint,
|
|
171
|
-
tool.handler,
|
|
172
|
-
tool.authRequirements
|
|
173
|
-
);
|
|
174
|
-
overriddenTool.httpMethod = tool.httpMethod;
|
|
175
|
-
return overriddenTool;
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Save tool description overrides
|
|
181
|
-
* @param appVersionId App version ID
|
|
182
|
-
* @param functionName Function name
|
|
183
|
-
* @param overrideData Override data from request
|
|
184
|
-
*/
|
|
185
|
-
public async saveToolOverrides(
|
|
186
|
-
appVersionId: string,
|
|
187
|
-
functionName: string,
|
|
188
|
-
overrides: StoredOverrides
|
|
189
|
-
): Promise<void> {
|
|
190
|
-
const key = this.getOverrideKey(appVersionId, functionName);
|
|
191
|
-
await App.storage.kvStore.patch(key, overrides);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Delete tool description overrides
|
|
196
|
-
* @param appVersionId App version ID
|
|
197
|
-
* @param functionName Function name
|
|
198
|
-
*/
|
|
199
|
-
public async deleteToolOverrides(appVersionId: string, functionName: string): Promise<void> {
|
|
200
|
-
const key = this.getOverrideKey(appVersionId, functionName);
|
|
201
|
-
await App.storage.kvStore.delete(key);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Get tool definitions with overrides applied
|
|
206
|
-
* @param appVersionId App version ID
|
|
207
|
-
* @param functionName Function name
|
|
208
|
-
* @returns Tool definitions with overrides applied
|
|
209
|
-
*/
|
|
210
|
-
public async getToolsWithOverrides(appVersionId: string, functionName: string): Promise<Array<Tool<any>>> {
|
|
211
|
-
const tools = Array.from(this.functions.values());
|
|
212
|
-
const overrides = await this.getOverrides(appVersionId, functionName);
|
|
213
|
-
|
|
214
|
-
if (overrides) {
|
|
215
|
-
return this.applyOverrides(tools, overrides);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return tools;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
99
|
/**
|
|
222
100
|
* Enforce OptiID authentication for tools by ensuring OptiID auth requirement is present
|
|
223
101
|
* @param authRequirements Original authentication requirements
|
|
@@ -322,141 +200,59 @@ export class ToolsService {
|
|
|
322
200
|
req: App.Request,
|
|
323
201
|
functionContext: ToolFunction | GlobalToolFunction
|
|
324
202
|
): Promise<App.Response> {
|
|
325
|
-
// Handle discovery endpoint with overrides
|
|
326
203
|
if (req.path === '/discovery') {
|
|
327
|
-
return
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
try {
|
|
339
|
-
let params;
|
|
340
|
-
if (req.bodyJSON && req.bodyJSON.parameters) {
|
|
341
|
-
params = req.bodyJSON.parameters;
|
|
342
|
-
} else {
|
|
343
|
-
params = req.bodyJSON;
|
|
344
|
-
}
|
|
204
|
+
return new App.Response(200, { functions: Array.from(this.functions.values()).map((f) => f.toJSON()) });
|
|
205
|
+
} else {
|
|
206
|
+
const func = this.functions.get(req.path);
|
|
207
|
+
if (func) {
|
|
208
|
+
try {
|
|
209
|
+
let params;
|
|
210
|
+
if (req.bodyJSON && req.bodyJSON.parameters) {
|
|
211
|
+
params = req.bodyJSON.parameters;
|
|
212
|
+
} else {
|
|
213
|
+
params = req.bodyJSON;
|
|
214
|
+
}
|
|
345
215
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
216
|
+
// Validate parameters before calling the handler (only if tool has parameter definitions)
|
|
217
|
+
// ParameterValidator.validate() throws ToolError if validation fails
|
|
218
|
+
if (func.parameters && func.parameters.length > 0) {
|
|
219
|
+
ParameterValidator.validate(params, func.parameters, func.endpoint);
|
|
220
|
+
}
|
|
351
221
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
const result = await func.handler(functionContext, params, authData);
|
|
355
|
-
return new App.Response(200, result);
|
|
356
|
-
} catch (error: any) {
|
|
357
|
-
logger.error(`Error in function ${func.name}:`, error);
|
|
358
|
-
return this.formatErrorResponse(error, func.endpoint);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
222
|
+
// Extract auth data from body JSON
|
|
223
|
+
const authData = req.bodyJSON ? req.bodyJSON.auth : undefined;
|
|
361
224
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (req.bodyJSON && req.bodyJSON.data) {
|
|
368
|
-
params = req.bodyJSON.data;
|
|
369
|
-
} else {
|
|
370
|
-
params = req.bodyJSON;
|
|
225
|
+
const result = await func.handler(functionContext, params, authData);
|
|
226
|
+
return new App.Response(200, result);
|
|
227
|
+
} catch (error: any) {
|
|
228
|
+
logger.error(`Error in function ${func.name}:`, error);
|
|
229
|
+
return this.formatErrorResponse(error, func.endpoint);
|
|
371
230
|
}
|
|
372
|
-
|
|
373
|
-
// Extract auth data from body JSON
|
|
374
|
-
const authData = req.bodyJSON ? req.bodyJSON.auth : undefined;
|
|
375
|
-
|
|
376
|
-
const result = await interaction.handler(functionContext, params, authData);
|
|
377
|
-
return new App.Response(200, result);
|
|
378
|
-
} catch (error: any) {
|
|
379
|
-
logger.error(`Error in function ${interaction.name}:`, error);
|
|
380
|
-
return this.formatErrorResponse(error, interaction.endpoint);
|
|
381
|
-
|
|
382
231
|
}
|
|
383
|
-
}
|
|
384
|
-
return new App.Response(404, 'Function not found');
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Handle discovery endpoint with overrides applied
|
|
389
|
-
* @param functionContext The function context to get function name from
|
|
390
|
-
* @returns Response with tool definitions
|
|
391
|
-
*/
|
|
392
|
-
private async handleDiscoveryRequest(functionContext: ToolFunction | GlobalToolFunction): Promise<App.Response> {
|
|
393
|
-
try {
|
|
394
|
-
// Get app version from app context
|
|
395
|
-
const appVersionId = getAppContext().manifest.meta.version;
|
|
396
|
-
// Get function name from function context
|
|
397
|
-
const functionName = functionContext.constructor.name;
|
|
398
232
|
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
233
|
+
const interaction = this.interactions.get(req.path);
|
|
234
|
+
if (interaction) {
|
|
235
|
+
try {
|
|
236
|
+
let params;
|
|
237
|
+
if (req.bodyJSON && req.bodyJSON.data) {
|
|
238
|
+
params = req.bodyJSON.data;
|
|
239
|
+
} else {
|
|
240
|
+
params = req.bodyJSON;
|
|
241
|
+
}
|
|
407
242
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
* @param req The request object
|
|
411
|
-
* @param functionContext The function context to get function name from
|
|
412
|
-
* @returns Response indicating success or failure
|
|
413
|
-
*/
|
|
414
|
-
private async handleOverridesRequest(
|
|
415
|
-
req: App.Request,
|
|
416
|
-
functionContext: ToolFunction | GlobalToolFunction
|
|
417
|
-
): Promise<App.Response> {
|
|
418
|
-
if (req.method === 'PATCH') {
|
|
419
|
-
try {
|
|
420
|
-
// Get app version from app context
|
|
421
|
-
const appVersionId = getAppContext().manifest.meta.version;
|
|
422
|
-
// Get function name from function context
|
|
423
|
-
const functionName = functionContext.constructor.name;
|
|
243
|
+
// Extract auth data from body JSON
|
|
244
|
+
const authData = req.bodyJSON ? req.bodyJSON.auth : undefined;
|
|
424
245
|
|
|
425
|
-
|
|
426
|
-
return new App.Response(
|
|
246
|
+
const result = await interaction.handler(functionContext, params, authData);
|
|
247
|
+
return new App.Response(200, result);
|
|
248
|
+
} catch (error: any) {
|
|
249
|
+
logger.error(`Error in function ${interaction.name}:`, error);
|
|
250
|
+
return this.formatErrorResponse(error, interaction.endpoint);
|
|
427
251
|
}
|
|
428
|
-
|
|
429
|
-
// Convert array format to map format for storage
|
|
430
|
-
const overrideData: StoredOverrides = (req.bodyJSON.functions as ToolOverride[]).reduce(
|
|
431
|
-
(map: StoredOverrides, tool: ToolOverride) => {
|
|
432
|
-
map[tool.name] = tool;
|
|
433
|
-
return map;
|
|
434
|
-
},
|
|
435
|
-
{}
|
|
436
|
-
);
|
|
437
|
-
|
|
438
|
-
await this.saveToolOverrides(appVersionId, functionName, overrideData);
|
|
439
|
-
return new App.Response(200, { success: true });
|
|
440
|
-
} catch (error: any) {
|
|
441
|
-
logger.error('Error saving tool overrides:', error);
|
|
442
|
-
return new App.Response(500, { error: error.message || 'Unknown error' });
|
|
443
252
|
}
|
|
444
|
-
} else if (req.method === 'DELETE') {
|
|
445
|
-
try {
|
|
446
|
-
// Get app version from app context
|
|
447
|
-
const appVersionId = getAppContext().manifest.meta.version;
|
|
448
|
-
// Get function name from function context
|
|
449
|
-
const functionName = functionContext.constructor.name;
|
|
450
253
|
|
|
451
|
-
|
|
452
|
-
return new App.Response(200, { success: true });
|
|
453
|
-
} catch (error: any) {
|
|
454
|
-
logger.error('Error deleting tool overrides:', error);
|
|
455
|
-
return new App.Response(500, { error: error.message || 'Unknown error' });
|
|
456
|
-
}
|
|
254
|
+
return new App.Response(404, 'Function not found');
|
|
457
255
|
}
|
|
458
|
-
|
|
459
|
-
return new App.Response(405, { error: 'Method not allowed' });
|
|
460
256
|
}
|
|
461
257
|
}
|
|
462
258
|
|
package/src/types/Models.ts
CHANGED
|
@@ -5,8 +5,7 @@ describe('ToolError', () => {
|
|
|
5
5
|
it('should create error with message and default status 500', () => {
|
|
6
6
|
const error = new ToolError('Something went wrong');
|
|
7
7
|
|
|
8
|
-
expect(error.message).toBe('
|
|
9
|
-
expect(error.title).toBe('Something went wrong');
|
|
8
|
+
expect(error.message).toBe('Something went wrong');
|
|
10
9
|
expect(error.status).toBe(500);
|
|
11
10
|
expect(error.detail).toBeUndefined();
|
|
12
11
|
expect(error.name).toBe('ToolError');
|
|
@@ -15,8 +14,7 @@ describe('ToolError', () => {
|
|
|
15
14
|
it('should create error with custom status code', () => {
|
|
16
15
|
const error = new ToolError('Not found', 404);
|
|
17
16
|
|
|
18
|
-
expect(error.message).toBe('
|
|
19
|
-
expect(error.title).toBe('Not found');
|
|
17
|
+
expect(error.message).toBe('Not found');
|
|
20
18
|
expect(error.status).toBe(404);
|
|
21
19
|
expect(error.detail).toBeUndefined();
|
|
22
20
|
});
|
|
@@ -24,39 +22,11 @@ describe('ToolError', () => {
|
|
|
24
22
|
it('should create error with message, status, and detail', () => {
|
|
25
23
|
const error = new ToolError('Validation failed', 400, 'Email format is invalid');
|
|
26
24
|
|
|
27
|
-
expect(error.message).toBe('
|
|
28
|
-
expect(error.title).toBe('Validation failed');
|
|
25
|
+
expect(error.message).toBe('Validation failed');
|
|
29
26
|
expect(error.status).toBe(400);
|
|
30
27
|
expect(error.detail).toBe('Email format is invalid');
|
|
31
28
|
});
|
|
32
29
|
|
|
33
|
-
it('should create error with errors array in message', () => {
|
|
34
|
-
const errors = [
|
|
35
|
-
{ field: 'email', message: 'Invalid email format' },
|
|
36
|
-
{ field: 'age', message: 'Must be a positive number' }
|
|
37
|
-
];
|
|
38
|
-
const error = new ToolError('Validation failed', 400, "See 'errors' field for details.", errors);
|
|
39
|
-
|
|
40
|
-
expect(error.message).toBe(
|
|
41
|
-
'[400] Validation failed: email (Invalid email format); age (Must be a positive number)'
|
|
42
|
-
);
|
|
43
|
-
expect(error.title).toBe('Validation failed');
|
|
44
|
-
expect(error.status).toBe(400);
|
|
45
|
-
expect(error.detail).toBe("See 'errors' field for details.");
|
|
46
|
-
expect(error.errors).toEqual(errors);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('should create error with single error in message', () => {
|
|
50
|
-
const errors = [{ field: 'username', message: 'Required field is missing' }];
|
|
51
|
-
const error = new ToolError('Validation failed', 400, undefined, errors);
|
|
52
|
-
|
|
53
|
-
expect(error.message).toBe('[400] Validation failed: username (Required field is missing)');
|
|
54
|
-
expect(error.title).toBe('Validation failed');
|
|
55
|
-
expect(error.status).toBe(400);
|
|
56
|
-
expect(error.detail).toBeUndefined();
|
|
57
|
-
expect(error.errors).toEqual(errors);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
30
|
it('should be an instance of Error', () => {
|
|
61
31
|
const error = new ToolError('Test error');
|
|
62
32
|
|
package/src/types/ToolError.ts
CHANGED
|
@@ -9,27 +9,18 @@ export interface ValidationError {
|
|
|
9
9
|
/**
|
|
10
10
|
* Custom error class for tool functions that supports RFC 9457 Problem Details format
|
|
11
11
|
*
|
|
12
|
-
* The error message includes status code and details for better logging:
|
|
13
|
-
* - No detail: "[status] message"
|
|
14
|
-
* - With detail: "[status] message: detail"
|
|
15
|
-
* - With errors: "[status] message: field1 (error1); field2 (error2)"
|
|
16
|
-
*
|
|
17
12
|
* @example
|
|
18
13
|
* ```typescript
|
|
19
14
|
* // Throw a 404 error
|
|
20
|
-
* // Message: "[404] Resource not found: The requested task does not exist"
|
|
21
15
|
* throw new ToolError('Resource not found', 404, 'The requested task does not exist');
|
|
22
16
|
*
|
|
23
17
|
* // Throw a 400 error
|
|
24
|
-
* // Message: "[400] Invalid input: The priority must be "low", "medium", or "high""
|
|
25
18
|
* throw new ToolError('Invalid input', 400, 'The priority must be "low", "medium", or "high"');
|
|
26
19
|
*
|
|
27
20
|
* // Throw a 500 error (default)
|
|
28
|
-
* // Message: "[500] Database connection failed"
|
|
29
21
|
* throw new ToolError('Database connection failed');
|
|
30
22
|
*
|
|
31
23
|
* // Throw a validation error with multiple field errors
|
|
32
|
-
* // Message: "[400] Validation failed: email (Invalid email format); age (Must be a positive number)"
|
|
33
24
|
* throw new ToolError('Validation failed', 400, "See 'errors' field for details.", [
|
|
34
25
|
* { field: 'email', message: 'Invalid email format' },
|
|
35
26
|
* { field: 'age', message: 'Must be a positive number' }
|
|
@@ -52,11 +43,6 @@ export class ToolError extends Error {
|
|
|
52
43
|
*/
|
|
53
44
|
public readonly errors?: ValidationError[];
|
|
54
45
|
|
|
55
|
-
/**
|
|
56
|
-
* The title field for RFC 9457 format (same as message when no detail is provided)
|
|
57
|
-
*/
|
|
58
|
-
public readonly title: string;
|
|
59
|
-
|
|
60
46
|
/**
|
|
61
47
|
* Create a new ToolError
|
|
62
48
|
*
|
|
@@ -71,24 +57,8 @@ export class ToolError extends Error {
|
|
|
71
57
|
detail?: string,
|
|
72
58
|
errors?: ValidationError[]
|
|
73
59
|
) {
|
|
74
|
-
|
|
75
|
-
// Format: [status] message: details
|
|
76
|
-
let fullMessage = `[${status}] ${message}`;
|
|
77
|
-
|
|
78
|
-
if (errors && errors.length > 0) {
|
|
79
|
-
// Errors take precedence: field1 (message1); field2 (message2)
|
|
80
|
-
const errorDetails = errors
|
|
81
|
-
.map((err) => `${err.field} (${err.message})`)
|
|
82
|
-
.join('; ');
|
|
83
|
-
fullMessage += `: ${errorDetails}`;
|
|
84
|
-
} else if (detail) {
|
|
85
|
-
// Include detail if no errors
|
|
86
|
-
fullMessage += `: ${detail}`;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
super(fullMessage);
|
|
60
|
+
super(message);
|
|
90
61
|
this.name = 'ToolError';
|
|
91
|
-
this.title = message;
|
|
92
62
|
this.status = status;
|
|
93
63
|
this.detail = detail;
|
|
94
64
|
this.errors = errors;
|
|
@@ -107,7 +77,7 @@ export class ToolError extends Error {
|
|
|
107
77
|
*/
|
|
108
78
|
public toProblemDetails(instance: string): Record<string, unknown> {
|
|
109
79
|
const problemDetails: Record<string, unknown> = {
|
|
110
|
-
title: this.
|
|
80
|
+
title: this.message,
|
|
111
81
|
status: this.status,
|
|
112
82
|
instance
|
|
113
83
|
};
|
|
@@ -49,10 +49,7 @@ describe('ParameterValidator', () => {
|
|
|
49
49
|
expect(error).toBeInstanceOf(ToolError);
|
|
50
50
|
const toolError = error as ToolError;
|
|
51
51
|
expect(toolError.status).toBe(400);
|
|
52
|
-
expect(toolError.message).toBe(
|
|
53
|
-
"[400] One or more validation errors occurred.: email (Required parameter 'email' is missing)"
|
|
54
|
-
);
|
|
55
|
-
expect(toolError.title).toBe('One or more validation errors occurred.');
|
|
52
|
+
expect(toolError.message).toBe('One or more validation errors occurred.');
|
|
56
53
|
expect(toolError.detail).toBe("See 'errors' field for details.");
|
|
57
54
|
expect(toolError.errors).toHaveLength(1);
|
|
58
55
|
expect(toolError.errors).toEqual([{
|