@optimizely-opal/opal-tool-ocp-sdk 1.0.0-beta.3 → 1.0.0-beta.5
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 +1 -1
- package/dist/function/GlobalToolFunction.d.ts.map +1 -1
- package/dist/function/GlobalToolFunction.js +17 -4
- package/dist/function/GlobalToolFunction.js.map +1 -1
- package/dist/function/GlobalToolFunction.test.js +54 -8
- package/dist/function/GlobalToolFunction.test.js.map +1 -1
- package/dist/function/ToolFunction.d.ts +1 -1
- package/dist/function/ToolFunction.d.ts.map +1 -1
- package/dist/function/ToolFunction.js +17 -4
- package/dist/function/ToolFunction.js.map +1 -1
- package/dist/function/ToolFunction.test.js +54 -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/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 +72 -0
- package/dist/types/ToolError.d.ts.map +1 -0
- package/dist/types/ToolError.js +107 -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 +185 -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 +187 -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 +54 -8
- package/src/function/GlobalToolFunction.ts +26 -6
- package/src/function/ToolFunction.test.ts +54 -8
- package/src/function/ToolFunction.ts +26 -6
- package/src/index.ts +1 -0
- package/src/service/Service.test.ts +103 -2
- package/src/service/Service.ts +45 -17
- package/src/types/ToolError.test.ts +222 -0
- package/src/types/ToolError.ts +125 -0
- package/src/validation/ParameterValidator.test.ts +188 -158
- package/src/validation/ParameterValidator.ts +17 -20
package/src/service/Service.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* eslint-disable max-classes-per-file */
|
|
2
|
-
import { AuthRequirement, Parameter } from '../types/Models';
|
|
2
|
+
import { AuthRequirement, IslandResponse, Parameter } from '../types/Models';
|
|
3
|
+
import { ToolError } from '../types/ToolError';
|
|
3
4
|
import * as App from '@zaiusinc/app-sdk';
|
|
4
5
|
import { logger, Headers } from '@zaiusinc/app-sdk';
|
|
5
6
|
import { ToolFunction } from '../function/ToolFunction';
|
|
@@ -11,13 +12,20 @@ import { ParameterValidator } from '../validation/ParameterValidator';
|
|
|
11
12
|
*/
|
|
12
13
|
const DEFAULT_OPTIID_AUTH = new AuthRequirement('OptiID', 'default', true);
|
|
13
14
|
|
|
15
|
+
export class NestedInteractions {
|
|
16
|
+
public constructor(public response: IslandResponse) {
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
14
20
|
/**
|
|
15
21
|
* Result type for interaction handlers
|
|
16
22
|
*/
|
|
17
23
|
export class InteractionResult {
|
|
18
24
|
public constructor(
|
|
19
25
|
public message: string,
|
|
20
|
-
public link?: string
|
|
26
|
+
public link?: string,
|
|
27
|
+
public dispatch_event?: boolean,
|
|
28
|
+
public interactions?: NestedInteractions
|
|
21
29
|
) {}
|
|
22
30
|
}
|
|
23
31
|
|
|
@@ -104,6 +112,37 @@ export class ToolsService {
|
|
|
104
112
|
return [...(authRequirements || []), DEFAULT_OPTIID_AUTH];
|
|
105
113
|
}
|
|
106
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Format an error as RFC 9457 Problem Details response
|
|
117
|
+
* @param error The error to format
|
|
118
|
+
* @param instance URI reference identifying the specific occurrence
|
|
119
|
+
* @returns RFC 9457 compliant Response
|
|
120
|
+
*/
|
|
121
|
+
private formatErrorResponse(error: any, instance: string): App.Response {
|
|
122
|
+
let status = 500;
|
|
123
|
+
let problemDetails: Record<string, unknown>;
|
|
124
|
+
|
|
125
|
+
if (error instanceof ToolError) {
|
|
126
|
+
// Use ToolError's status and format
|
|
127
|
+
status = error.status;
|
|
128
|
+
problemDetails = error.toProblemDetails(instance);
|
|
129
|
+
} else {
|
|
130
|
+
// Convert regular errors to RFC 9457 format with 500 status
|
|
131
|
+
problemDetails = {
|
|
132
|
+
title: 'Internal Server Error',
|
|
133
|
+
status: 500,
|
|
134
|
+
detail: error.message || 'An unexpected error occurred',
|
|
135
|
+
instance
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return new App.Response(
|
|
140
|
+
status,
|
|
141
|
+
problemDetails,
|
|
142
|
+
new Headers([['content-type', 'application/problem+json']])
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
107
146
|
/**
|
|
108
147
|
* Register a tool function with generic auth data
|
|
109
148
|
* @param name Tool name
|
|
@@ -175,20 +214,9 @@ export class ToolsService {
|
|
|
175
214
|
}
|
|
176
215
|
|
|
177
216
|
// Validate parameters before calling the handler (only if tool has parameter definitions)
|
|
217
|
+
// ParameterValidator.validate() throws ToolError if validation fails
|
|
178
218
|
if (func.parameters && func.parameters.length > 0) {
|
|
179
|
-
|
|
180
|
-
if (!validationResult.isValid) {
|
|
181
|
-
return new App.Response(400, {
|
|
182
|
-
title: 'One or more validation errors occurred.',
|
|
183
|
-
status: 400,
|
|
184
|
-
detail: "See 'errors' field for details.",
|
|
185
|
-
instance: func.endpoint,
|
|
186
|
-
errors: validationResult.errors.map((error) => ({
|
|
187
|
-
field: error.field,
|
|
188
|
-
message: error.message
|
|
189
|
-
}))
|
|
190
|
-
}, new Headers([['content-type', 'application/problem+json']]));
|
|
191
|
-
}
|
|
219
|
+
ParameterValidator.validate(params, func.parameters, func.endpoint);
|
|
192
220
|
}
|
|
193
221
|
|
|
194
222
|
// Extract auth data from body JSON
|
|
@@ -198,7 +226,7 @@ export class ToolsService {
|
|
|
198
226
|
return new App.Response(200, result);
|
|
199
227
|
} catch (error: any) {
|
|
200
228
|
logger.error(`Error in function ${func.name}:`, error);
|
|
201
|
-
return
|
|
229
|
+
return this.formatErrorResponse(error, func.endpoint);
|
|
202
230
|
}
|
|
203
231
|
}
|
|
204
232
|
|
|
@@ -219,7 +247,7 @@ export class ToolsService {
|
|
|
219
247
|
return new App.Response(200, result);
|
|
220
248
|
} catch (error: any) {
|
|
221
249
|
logger.error(`Error in function ${interaction.name}:`, error);
|
|
222
|
-
return
|
|
250
|
+
return this.formatErrorResponse(error, interaction.endpoint);
|
|
223
251
|
}
|
|
224
252
|
}
|
|
225
253
|
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { ToolError } from './ToolError';
|
|
2
|
+
|
|
3
|
+
describe('ToolError', () => {
|
|
4
|
+
describe('constructor', () => {
|
|
5
|
+
it('should create error with message and default status 500', () => {
|
|
6
|
+
const error = new ToolError('Something went wrong');
|
|
7
|
+
|
|
8
|
+
expect(error.message).toBe('[500] Something went wrong');
|
|
9
|
+
expect(error.title).toBe('Something went wrong');
|
|
10
|
+
expect(error.status).toBe(500);
|
|
11
|
+
expect(error.detail).toBeUndefined();
|
|
12
|
+
expect(error.name).toBe('ToolError');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should create error with custom status code', () => {
|
|
16
|
+
const error = new ToolError('Not found', 404);
|
|
17
|
+
|
|
18
|
+
expect(error.message).toBe('[404] Not found');
|
|
19
|
+
expect(error.title).toBe('Not found');
|
|
20
|
+
expect(error.status).toBe(404);
|
|
21
|
+
expect(error.detail).toBeUndefined();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should create error with message, status, and detail', () => {
|
|
25
|
+
const error = new ToolError('Validation failed', 400, 'Email format is invalid');
|
|
26
|
+
|
|
27
|
+
expect(error.message).toBe('[400] Validation failed: Email format is invalid');
|
|
28
|
+
expect(error.title).toBe('Validation failed');
|
|
29
|
+
expect(error.status).toBe(400);
|
|
30
|
+
expect(error.detail).toBe('Email format is invalid');
|
|
31
|
+
});
|
|
32
|
+
|
|
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
|
+
it('should be an instance of Error', () => {
|
|
61
|
+
const error = new ToolError('Test error');
|
|
62
|
+
|
|
63
|
+
expect(error).toBeInstanceOf(Error);
|
|
64
|
+
expect(error).toBeInstanceOf(ToolError);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should have a stack trace', () => {
|
|
68
|
+
const error = new ToolError('Test error');
|
|
69
|
+
|
|
70
|
+
expect(error.stack).toBeDefined();
|
|
71
|
+
expect(error.stack).toContain('ToolError');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('toProblemDetails', () => {
|
|
76
|
+
it('should convert to RFC 9457 format with all fields', () => {
|
|
77
|
+
const error = new ToolError('Resource not found', 404, 'The task with ID 123 does not exist');
|
|
78
|
+
const problemDetails = error.toProblemDetails('/api/tasks/123');
|
|
79
|
+
|
|
80
|
+
expect(problemDetails).toEqual({
|
|
81
|
+
title: 'Resource not found',
|
|
82
|
+
status: 404,
|
|
83
|
+
detail: 'The task with ID 123 does not exist',
|
|
84
|
+
instance: '/api/tasks/123'
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should omit detail field when not provided', () => {
|
|
89
|
+
const error = new ToolError('Bad request', 400);
|
|
90
|
+
const problemDetails = error.toProblemDetails('/api/tasks');
|
|
91
|
+
|
|
92
|
+
expect(problemDetails).toEqual({
|
|
93
|
+
title: 'Bad request',
|
|
94
|
+
status: 400,
|
|
95
|
+
instance: '/api/tasks'
|
|
96
|
+
});
|
|
97
|
+
expect(problemDetails).not.toHaveProperty('detail');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should include default status 500', () => {
|
|
101
|
+
const error = new ToolError('Internal error');
|
|
102
|
+
const problemDetails = error.toProblemDetails('/api/endpoint');
|
|
103
|
+
|
|
104
|
+
expect(problemDetails).toEqual({
|
|
105
|
+
title: 'Internal error',
|
|
106
|
+
status: 500,
|
|
107
|
+
instance: '/api/endpoint'
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should handle different instance paths', () => {
|
|
112
|
+
const error = new ToolError('Error', 500, 'Details');
|
|
113
|
+
const problemDetails1 = error.toProblemDetails('/api/v1/resource');
|
|
114
|
+
const problemDetails2 = error.toProblemDetails('/webhook/callback');
|
|
115
|
+
|
|
116
|
+
expect(problemDetails1.instance).toBe('/api/v1/resource');
|
|
117
|
+
expect(problemDetails2.instance).toBe('/webhook/callback');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should include errors array when provided', () => {
|
|
121
|
+
const errors = [
|
|
122
|
+
{ field: 'email', message: 'Invalid email format' },
|
|
123
|
+
{ field: 'age', message: 'Must be a positive number' }
|
|
124
|
+
];
|
|
125
|
+
const error = new ToolError('Validation failed', 400, "See 'errors' field for details.", errors);
|
|
126
|
+
const problemDetails = error.toProblemDetails('/api/users');
|
|
127
|
+
|
|
128
|
+
expect(problemDetails).toEqual({
|
|
129
|
+
title: 'Validation failed',
|
|
130
|
+
status: 400,
|
|
131
|
+
detail: "See 'errors' field for details.",
|
|
132
|
+
instance: '/api/users',
|
|
133
|
+
errors: [
|
|
134
|
+
{ field: 'email', message: 'Invalid email format' },
|
|
135
|
+
{ field: 'age', message: 'Must be a positive number' }
|
|
136
|
+
]
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should omit errors field when empty array', () => {
|
|
141
|
+
const error = new ToolError('Error', 400, 'Detail', []);
|
|
142
|
+
const problemDetails = error.toProblemDetails('/api/endpoint');
|
|
143
|
+
|
|
144
|
+
expect(problemDetails).toEqual({
|
|
145
|
+
title: 'Error',
|
|
146
|
+
status: 400,
|
|
147
|
+
detail: 'Detail',
|
|
148
|
+
instance: '/api/endpoint'
|
|
149
|
+
});
|
|
150
|
+
expect(problemDetails).not.toHaveProperty('errors');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should omit errors field when not provided', () => {
|
|
154
|
+
const error = new ToolError('Error', 400, 'Detail');
|
|
155
|
+
const problemDetails = error.toProblemDetails('/api/endpoint');
|
|
156
|
+
|
|
157
|
+
expect(problemDetails).toEqual({
|
|
158
|
+
title: 'Error',
|
|
159
|
+
status: 400,
|
|
160
|
+
detail: 'Detail',
|
|
161
|
+
instance: '/api/endpoint'
|
|
162
|
+
});
|
|
163
|
+
expect(problemDetails).not.toHaveProperty('errors');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should support errors without detail field', () => {
|
|
167
|
+
const errors = [{ field: 'name', message: 'Required' }];
|
|
168
|
+
const error = new ToolError('Validation failed', 400, undefined, errors);
|
|
169
|
+
const problemDetails = error.toProblemDetails('/api/endpoint');
|
|
170
|
+
|
|
171
|
+
expect(problemDetails).toEqual({
|
|
172
|
+
title: 'Validation failed',
|
|
173
|
+
status: 400,
|
|
174
|
+
instance: '/api/endpoint',
|
|
175
|
+
errors: [{ field: 'name', message: 'Required' }]
|
|
176
|
+
});
|
|
177
|
+
expect(problemDetails).not.toHaveProperty('detail');
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('common HTTP status codes', () => {
|
|
182
|
+
it('should support 400 Bad Request', () => {
|
|
183
|
+
const error = new ToolError('Bad Request', 400);
|
|
184
|
+
expect(error.status).toBe(400);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should support 401 Unauthorized', () => {
|
|
188
|
+
const error = new ToolError('Unauthorized', 401);
|
|
189
|
+
expect(error.status).toBe(401);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should support 403 Forbidden', () => {
|
|
193
|
+
const error = new ToolError('Forbidden', 403);
|
|
194
|
+
expect(error.status).toBe(403);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should support 404 Not Found', () => {
|
|
198
|
+
const error = new ToolError('Not Found', 404);
|
|
199
|
+
expect(error.status).toBe(404);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should support 409 Conflict', () => {
|
|
203
|
+
const error = new ToolError('Conflict', 409);
|
|
204
|
+
expect(error.status).toBe(409);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('should support 422 Unprocessable Entity', () => {
|
|
208
|
+
const error = new ToolError('Unprocessable Entity', 422);
|
|
209
|
+
expect(error.status).toBe(422);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should support 500 Internal Server Error', () => {
|
|
213
|
+
const error = new ToolError('Internal Server Error', 500);
|
|
214
|
+
expect(error.status).toBe(500);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('should support 503 Service Unavailable', () => {
|
|
218
|
+
const error = new ToolError('Service Unavailable', 503);
|
|
219
|
+
expect(error.status).toBe(503);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
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
|
+
* 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
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* // Throw a 404 error
|
|
20
|
+
* // Message: "[404] Resource not found: The requested task does not exist"
|
|
21
|
+
* throw new ToolError('Resource not found', 404, 'The requested task does not exist');
|
|
22
|
+
*
|
|
23
|
+
* // Throw a 400 error
|
|
24
|
+
* // Message: "[400] Invalid input: The priority must be "low", "medium", or "high""
|
|
25
|
+
* throw new ToolError('Invalid input', 400, 'The priority must be "low", "medium", or "high"');
|
|
26
|
+
*
|
|
27
|
+
* // Throw a 500 error (default)
|
|
28
|
+
* // Message: "[500] Database connection failed"
|
|
29
|
+
* throw new ToolError('Database connection failed');
|
|
30
|
+
*
|
|
31
|
+
* // Throw a validation error with multiple field errors
|
|
32
|
+
* // Message: "[400] Validation failed: email (Invalid email format); age (Must be a positive number)"
|
|
33
|
+
* throw new ToolError('Validation failed', 400, "See 'errors' field for details.", [
|
|
34
|
+
* { field: 'email', message: 'Invalid email format' },
|
|
35
|
+
* { field: 'age', message: 'Must be a positive number' }
|
|
36
|
+
* ]);
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export class ToolError extends Error {
|
|
40
|
+
/**
|
|
41
|
+
* HTTP status code for the error response
|
|
42
|
+
*/
|
|
43
|
+
public readonly status: number;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Detailed error description (optional)
|
|
47
|
+
*/
|
|
48
|
+
public readonly detail?: string;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Array of validation errors (optional)
|
|
52
|
+
*/
|
|
53
|
+
public readonly errors?: ValidationError[];
|
|
54
|
+
|
|
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
|
+
/**
|
|
61
|
+
* Create a new ToolError
|
|
62
|
+
*
|
|
63
|
+
* @param message - Error message (used as RFC 9457 "title" field)
|
|
64
|
+
* @param status - HTTP status code (default: 500)
|
|
65
|
+
* @param detail - Detailed error description (optional)
|
|
66
|
+
* @param errors - Array of validation errors (optional)
|
|
67
|
+
*/
|
|
68
|
+
public constructor(
|
|
69
|
+
message: string,
|
|
70
|
+
status: number = 500,
|
|
71
|
+
detail?: string,
|
|
72
|
+
errors?: ValidationError[]
|
|
73
|
+
) {
|
|
74
|
+
// Build comprehensive error message for logging/debugging
|
|
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);
|
|
90
|
+
this.name = 'ToolError';
|
|
91
|
+
this.title = message;
|
|
92
|
+
this.status = status;
|
|
93
|
+
this.detail = detail;
|
|
94
|
+
this.errors = errors;
|
|
95
|
+
|
|
96
|
+
// Maintains proper stack trace for where error was thrown (V8 engines only)
|
|
97
|
+
if (Error.captureStackTrace) {
|
|
98
|
+
Error.captureStackTrace(this, ToolError);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Convert error to RFC 9457 Problem Details format
|
|
104
|
+
*
|
|
105
|
+
* @param instance - URI reference identifying the specific occurrence of the problem
|
|
106
|
+
* @returns RFC 9457 compliant error object
|
|
107
|
+
*/
|
|
108
|
+
public toProblemDetails(instance: string): Record<string, unknown> {
|
|
109
|
+
const problemDetails: Record<string, unknown> = {
|
|
110
|
+
title: this.title,
|
|
111
|
+
status: this.status,
|
|
112
|
+
instance
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
if (this.detail) {
|
|
116
|
+
problemDetails.detail = this.detail;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (this.errors && this.errors.length > 0) {
|
|
120
|
+
problemDetails.errors = this.errors;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return problemDetails;
|
|
124
|
+
}
|
|
125
|
+
}
|