@aifabrix/builder 2.4.0 → 2.5.0

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.
@@ -11,7 +11,49 @@
11
11
  */
12
12
 
13
13
  const chalk = require('chalk');
14
-
14
+ /**
15
+ * Extracts missing permissions from error data
16
+ * @param {Object} errorData - Error response data
17
+ * @returns {Array<string>} Array of missing permissions
18
+ */
19
+ function extractMissingPermissions(errorData) {
20
+ if (errorData.missingPermissions && Array.isArray(errorData.missingPermissions)) {
21
+ return errorData.missingPermissions;
22
+ }
23
+ if (errorData.missing && errorData.missing.permissions && Array.isArray(errorData.missing.permissions)) {
24
+ return errorData.missing.permissions;
25
+ }
26
+ return [];
27
+ }
28
+ /**
29
+ * Extracts required permissions from error data
30
+ * @param {Object} errorData - Error response data
31
+ * @returns {Array<string>} Array of required permissions
32
+ */
33
+ function extractRequiredPermissions(errorData) {
34
+ if (errorData.requiredPermissions && Array.isArray(errorData.requiredPermissions)) {
35
+ return errorData.requiredPermissions;
36
+ }
37
+ if (errorData.required && errorData.required.permissions && Array.isArray(errorData.required.permissions)) {
38
+ return errorData.required.permissions;
39
+ }
40
+ return [];
41
+ }
42
+ /**
43
+ * Adds permission list to lines array
44
+ * @param {Array<string>} lines - Lines array to append to
45
+ * @param {Array<string>} perms - Permissions array
46
+ * @param {string} label - Label for the permissions list
47
+ */
48
+ function addPermissionList(lines, perms, label) {
49
+ if (perms.length > 0) {
50
+ lines.push(chalk.yellow(`${label}:`));
51
+ perms.forEach(perm => {
52
+ lines.push(chalk.gray(` - ${perm}`));
53
+ });
54
+ lines.push('');
55
+ }
56
+ }
15
57
  /**
16
58
  * Formats permission error with missing and required permissions
17
59
  * @param {Object} errorData - Error response data
@@ -21,45 +63,17 @@ function formatPermissionError(errorData) {
21
63
  const lines = [];
22
64
  lines.push(chalk.red('❌ Permission Denied\n'));
23
65
 
24
- // Handle detail message if present
25
66
  if (errorData.detail) {
26
67
  lines.push(chalk.yellow(errorData.detail));
27
68
  lines.push('');
28
69
  }
29
70
 
30
- // Extract missing permissions (support both flat and nested structures)
31
- let missingPerms = [];
32
- if (errorData.missingPermissions && Array.isArray(errorData.missingPermissions)) {
33
- missingPerms = errorData.missingPermissions;
34
- } else if (errorData.missing && errorData.missing.permissions && Array.isArray(errorData.missing.permissions)) {
35
- missingPerms = errorData.missing.permissions;
36
- }
37
-
38
- if (missingPerms.length > 0) {
39
- lines.push(chalk.yellow('Missing permissions:'));
40
- missingPerms.forEach(perm => {
41
- lines.push(chalk.gray(` - ${perm}`));
42
- });
43
- lines.push('');
44
- }
45
-
46
- // Extract required permissions (support both flat and nested structures)
47
- let requiredPerms = [];
48
- if (errorData.requiredPermissions && Array.isArray(errorData.requiredPermissions)) {
49
- requiredPerms = errorData.requiredPermissions;
50
- } else if (errorData.required && errorData.required.permissions && Array.isArray(errorData.required.permissions)) {
51
- requiredPerms = errorData.required.permissions;
52
- }
71
+ const missingPerms = extractMissingPermissions(errorData);
72
+ addPermissionList(lines, missingPerms, 'Missing permissions');
53
73
 
54
- if (requiredPerms.length > 0) {
55
- lines.push(chalk.yellow('Required permissions:'));
56
- requiredPerms.forEach(perm => {
57
- lines.push(chalk.gray(` - ${perm}`));
58
- });
59
- lines.push('');
60
- }
74
+ const requiredPerms = extractRequiredPermissions(errorData);
75
+ addPermissionList(lines, requiredPerms, 'Required permissions');
61
76
 
62
- // Use instance (from RFC 7807) or url field
63
77
  const requestUrl = errorData.instance || errorData.url;
64
78
  const method = errorData.method || 'POST';
65
79
  if (requestUrl) {
@@ -72,7 +86,6 @@ function formatPermissionError(errorData) {
72
86
 
73
87
  return lines.join('\n');
74
88
  }
75
-
76
89
  /**
77
90
  * Formats validation error with field-level details
78
91
  * Handles unified error API response format (RFC 7807 Problem Details)
@@ -124,7 +137,6 @@ function formatValidationError(errorData) {
124
137
 
125
138
  return lines.join('\n');
126
139
  }
127
-
128
140
  /**
129
141
  * Formats authentication error
130
142
  * @param {Object} errorData - Error response data
@@ -148,7 +160,6 @@ function formatAuthenticationError(errorData) {
148
160
 
149
161
  return lines.join('\n');
150
162
  }
151
-
152
163
  /**
153
164
  * Formats network error
154
165
  * @param {string} errorMessage - Error message
@@ -241,6 +252,34 @@ function formatConflictError(errorData) {
241
252
  return lines.join('\n');
242
253
  }
243
254
 
255
+ /**
256
+ * Gets actionable guidance options based on error detail
257
+ * @param {string} detail - Error detail message
258
+ * @returns {Array<string>} Array of guidance options
259
+ */
260
+ function getNotFoundGuidance(detail) {
261
+ const lowerDetail = detail.toLowerCase();
262
+ if (lowerDetail.includes('environment')) {
263
+ return [
264
+ ' • Check the environment key spelling',
265
+ ' • List available environments: aifabrix app list -e <environment>',
266
+ ' • Verify you have access to this environment'
267
+ ];
268
+ }
269
+ if (lowerDetail.includes('application')) {
270
+ return [
271
+ ' • Check the application key spelling',
272
+ ' • List applications: aifabrix app list -e <environment>',
273
+ ' • Verify the application exists in this environment'
274
+ ];
275
+ }
276
+ return [
277
+ ' • Check the resource identifier',
278
+ ' • Verify the resource exists',
279
+ ' • Check your permissions'
280
+ ];
281
+ }
282
+
244
283
  /**
245
284
  * Formats not found error (404)
246
285
  * @param {Object} errorData - Error response data
@@ -250,31 +289,17 @@ function formatNotFoundError(errorData) {
250
289
  const lines = [];
251
290
  lines.push(chalk.red('❌ Not Found\n'));
252
291
 
253
- // Extract detail from RFC 7807 Problem Details format
254
292
  const detail = errorData.detail || errorData.message || '';
255
-
256
293
  if (detail) {
257
294
  lines.push(chalk.yellow(detail));
258
295
  lines.push('');
259
296
  }
260
297
 
261
- // Provide actionable guidance based on error context
262
- if (detail.toLowerCase().includes('environment')) {
263
- lines.push(chalk.gray('Options:'));
264
- lines.push(chalk.gray(' • Check the environment key spelling'));
265
- lines.push(chalk.gray(' • List available environments: aifabrix app list -e <environment>'));
266
- lines.push(chalk.gray(' • Verify you have access to this environment'));
267
- } else if (detail.toLowerCase().includes('application')) {
268
- lines.push(chalk.gray('Options:'));
269
- lines.push(chalk.gray(' • Check the application key spelling'));
270
- lines.push(chalk.gray(' • List applications: aifabrix app list -e <environment>'));
271
- lines.push(chalk.gray(' • Verify the application exists in this environment'));
272
- } else {
273
- lines.push(chalk.gray('Options:'));
274
- lines.push(chalk.gray(' • Check the resource identifier'));
275
- lines.push(chalk.gray(' • Verify the resource exists'));
276
- lines.push(chalk.gray(' • Check your permissions'));
277
- }
298
+ lines.push(chalk.gray('Options:'));
299
+ const guidance = getNotFoundGuidance(detail);
300
+ guidance.forEach(option => {
301
+ lines.push(chalk.gray(option));
302
+ });
278
303
 
279
304
  if (errorData.correlationId) {
280
305
  lines.push('');
@@ -314,31 +339,109 @@ function formatGenericError(errorData, statusCode) {
314
339
  }
315
340
 
316
341
  /**
317
- * Parses error response and determines error type
342
+ * Parses error response into error data object
318
343
  * @param {string|Object} errorResponse - Error response (string or parsed JSON)
319
- * @param {number} statusCode - HTTP status code
320
- * @param {boolean} isNetworkError - Whether this is a network error
321
- * @returns {Object} Parsed error object with type, message, and formatted output
344
+ * @returns {Object} Parsed error data object
322
345
  */
323
- function parseErrorResponse(errorResponse, statusCode, isNetworkError) {
324
- let errorData = {};
325
-
326
- // Handle undefined or null error response
346
+ function parseErrorData(errorResponse) {
327
347
  if (errorResponse === undefined || errorResponse === null) {
328
- errorData = { message: 'Unknown error occurred' };
329
- } else if (typeof errorResponse === 'string') {
330
- // Parse error response
348
+ return { message: 'Unknown error occurred' };
349
+ }
350
+ if (typeof errorResponse === 'string') {
331
351
  try {
332
- errorData = JSON.parse(errorResponse);
352
+ return JSON.parse(errorResponse);
333
353
  } catch {
334
- errorData = { message: errorResponse };
354
+ return { message: errorResponse };
335
355
  }
336
- } else if (typeof errorResponse === 'object') {
337
- errorData = errorResponse;
338
- } else {
339
- // Fallback for unexpected types
340
- errorData = { message: String(errorResponse) };
341
356
  }
357
+ if (typeof errorResponse === 'object') {
358
+ return errorResponse;
359
+ }
360
+ return { message: String(errorResponse) };
361
+ }
362
+
363
+ /**
364
+ * Creates error result object
365
+ * @param {string} type - Error type
366
+ * @param {string} message - Error message
367
+ * @param {string} formatted - Formatted error message
368
+ * @param {Object} data - Error data
369
+ * @returns {Object} Error result object
370
+ */
371
+ function createErrorResult(type, message, formatted, data) {
372
+ return { type, message, formatted, data };
373
+ }
374
+
375
+ /**
376
+ * Gets error message from error data
377
+ * @param {Object} errorData - Error data object
378
+ * @param {string} defaultMessage - Default message if not found
379
+ * @returns {string} Error message
380
+ */
381
+ function getErrorMessage(errorData, defaultMessage) {
382
+ return errorData.detail || errorData.title || errorData.message || defaultMessage;
383
+ }
384
+
385
+ /**
386
+ * Handles 400 validation error
387
+ * @param {Object} errorData - Error data object
388
+ * @returns {Object} Error result object
389
+ */
390
+ function handleValidationError(errorData) {
391
+ const errorMessage = getErrorMessage(errorData, 'Validation error');
392
+ return createErrorResult('validation', errorMessage, formatValidationError(errorData), errorData);
393
+ }
394
+
395
+ /**
396
+ * Handles specific 4xx client error codes
397
+ * @param {number} statusCode - HTTP status code
398
+ * @param {Object} errorData - Error data object
399
+ * @returns {Object|null} Error result object or null if not handled
400
+ */
401
+ function handleSpecificClientErrors(statusCode, errorData) {
402
+ switch (statusCode) {
403
+ case 403:
404
+ return createErrorResult('permission', 'Permission denied', formatPermissionError(errorData), errorData);
405
+ case 401:
406
+ return createErrorResult('authentication', 'Authentication failed', formatAuthenticationError(errorData), errorData);
407
+ case 400:
408
+ return handleValidationError(errorData);
409
+ case 404:
410
+ return createErrorResult('notfound', getErrorMessage(errorData, 'Not found'), formatNotFoundError(errorData), errorData);
411
+ case 409:
412
+ return createErrorResult('conflict', getErrorMessage(errorData, 'Conflict'), formatConflictError(errorData), errorData);
413
+ default:
414
+ return null;
415
+ }
416
+ }
417
+
418
+ /**
419
+ * Handles HTTP status code errors
420
+ * @param {number} statusCode - HTTP status code
421
+ * @param {Object} errorData - Error data object
422
+ * @returns {Object|null} Error result object or null if not handled
423
+ */
424
+ function handleStatusCodeError(statusCode, errorData) {
425
+ // Handle 4xx client errors
426
+ if (statusCode >= 400 && statusCode < 500) {
427
+ return handleSpecificClientErrors(statusCode, errorData);
428
+ }
429
+ // Handle 5xx server errors
430
+ if (statusCode >= 500) {
431
+ return createErrorResult('server', 'Server error', formatServerError(errorData), errorData);
432
+ }
433
+ return null;
434
+ }
435
+
436
+ /**
437
+ * Parses error response and determines error type
438
+ * @param {string|Object} errorResponse - Error response (string or parsed JSON)
439
+ * @param {number} statusCode - HTTP status code
440
+ * @param {boolean} isNetworkError - Whether this is a network error
441
+ * @returns {Object} Parsed error object with type, message, and formatted output
442
+ */
443
+ function parseErrorResponse(errorResponse, statusCode, isNetworkError) {
444
+ let errorData = parseErrorData(errorResponse);
342
445
 
343
446
  // Handle nested response structure (some APIs wrap errors in data field)
344
447
  if (errorData.data && typeof errorData.data === 'object') {
@@ -348,84 +451,17 @@ function parseErrorResponse(errorResponse, statusCode, isNetworkError) {
348
451
  // Handle network errors
349
452
  if (isNetworkError) {
350
453
  const errorMessage = errorData.message || errorResponse || 'Network error';
351
- return {
352
- type: 'network',
353
- message: errorMessage,
354
- formatted: formatNetworkError(errorMessage, errorData),
355
- data: errorData
356
- };
357
- }
358
-
359
- // Handle different HTTP status codes
360
- if (statusCode === 403) {
361
- // Permission error
362
- return {
363
- type: 'permission',
364
- message: 'Permission denied',
365
- formatted: formatPermissionError(errorData),
366
- data: errorData
367
- };
368
- }
369
-
370
- if (statusCode === 401) {
371
- // Authentication error
372
- return {
373
- type: 'authentication',
374
- message: 'Authentication failed',
375
- formatted: formatAuthenticationError(errorData),
376
- data: errorData
377
- };
378
- }
379
-
380
- if (statusCode === 400) {
381
- // Validation error
382
- // Extract message from unified error format (RFC 7807)
383
- const errorMessage = errorData.detail || errorData.title || errorData.message || 'Validation error';
384
- return {
385
- type: 'validation',
386
- message: errorMessage,
387
- formatted: formatValidationError(errorData),
388
- data: errorData
389
- };
390
- }
391
-
392
- if (statusCode === 404) {
393
- // Not found error
394
- return {
395
- type: 'notfound',
396
- message: errorData.detail || errorData.message || 'Not found',
397
- formatted: formatNotFoundError(errorData),
398
- data: errorData
399
- };
400
- }
401
-
402
- if (statusCode === 409) {
403
- // Conflict error
404
- return {
405
- type: 'conflict',
406
- message: errorData.detail || errorData.message || 'Conflict',
407
- formatted: formatConflictError(errorData),
408
- data: errorData
409
- };
454
+ return createErrorResult('network', errorMessage, formatNetworkError(errorMessage, errorData), errorData);
410
455
  }
411
456
 
412
- if (statusCode >= 500) {
413
- // Server error
414
- return {
415
- type: 'server',
416
- message: 'Server error',
417
- formatted: formatServerError(errorData),
418
- data: errorData
419
- };
457
+ // Handle HTTP status codes
458
+ const statusError = handleStatusCodeError(statusCode, errorData);
459
+ if (statusError) {
460
+ return statusError;
420
461
  }
421
462
 
422
463
  // Generic error
423
- return {
424
- type: 'generic',
425
- message: errorData.message || errorData.error || 'Unknown error',
426
- formatted: formatGenericError(errorData, statusCode),
427
- data: errorData
428
- };
464
+ return createErrorResult('generic', errorData.message || errorData.error || 'Unknown error', formatGenericError(errorData, statusCode), errorData);
429
465
  }
430
466
 
431
467
  /**
@@ -462,4 +498,3 @@ module.exports = {
462
498
  formatServerError,
463
499
  formatGenericError
464
500
  };
465
-