Skip to content

Error Handling

The SDK provides a unified error handling pattern across REST and GraphQL APIs. All errors are normalized to a consistent SdkError structure.

SdkError Structure

typescript
interface SdkError extends Error {
  name: 'SdkError';
  message: string;                    // Human-readable error message
  statusCode?: number;                // HTTP status code (REST only)
  classification: ErrorClassification; // Error category
  requestId?: string;                 // Request tracking ID
  details?: ErrorDetails;             // Structured error details
  cause?: Error;                      // Original error (minimal)
}

type ErrorClassification =
  | 'VALIDATION_ERROR'      // 400, 422 - Invalid input
  | 'AUTHENTICATION_ERROR'  // 401 - Invalid credentials
  | 'AUTHORIZATION_ERROR'   // 403 - Insufficient permissions
  | 'NOT_FOUND'             // 404 - Resource not found
  | 'RATE_LIMIT'            // 429 - Too many requests
  | 'NETWORK_ERROR'         // No response from server
  | 'SERVER_ERROR'          // 500+ - Server issues
  | 'GRAPHQL_ERROR'         // GraphQL-specific errors
  | 'UNKNOWN_ERROR';        // Unclassified errors

interface ErrorDetails {
  errorCode?: string;           // API error code (e.g., 'error.msg.client.id.invalid')
  validationErrors?: Array<{    // Field-level errors
    field: string;
    message: string;
    code?: string;
    value?: unknown;
    args?: unknown[];
  }>;
  path?: string[];              // GraphQL field path
  graphqlCode?: string;         // GraphQL extension code
}

Basic Error Handling

typescript
import { isSdkError, SdkError } from '@mbanq/core-sdk-js';

try {
  const result = await client.request(GetClient(123));
} catch (error) {
  if (isSdkError(error)) {
    console.log('Error:', error.message);
    console.log('Classification:', error.classification);
    console.log('Status:', error.statusCode);
    console.log('Request ID:', error.requestId);
    
    // Access structured details
    if (error.details?.validationErrors) {
      error.details.validationErrors.forEach(ve => {
        console.log(`Field ${ve.field}: ${ve.message}`);
      });
    }
  }
}

Classification-Based Handling

typescript
try {
  await client.request(CreatePayment(paymentData));
} catch (error) {
  if (isSdkError(error)) {
    switch (error.classification) {
      case 'AUTHENTICATION_ERROR':
        // Refresh token or re-authenticate
        await client.connect(credentials);
        break;
      case 'AUTHORIZATION_ERROR':
        // Check 2FA or permissions
        console.log('Access denied:', error.message);
        break;
      case 'VALIDATION_ERROR':
        // Show field errors to user
        error.details?.validationErrors?.forEach(e => {
          showFieldError(e.field, e.message);
        });
        break;
      case 'NOT_FOUND':
        // Resource doesn't exist
        console.log('Resource not found');
        break;
      case 'NETWORK_ERROR':
        // Retry or show offline message
        console.log('Network issue, please retry');
        break;
      case 'SERVER_ERROR':
        // Log and show generic error
        console.error('Server error:', error.requestId);
        break;
    }
  }
}

REST Error Example

When a REST API returns an error like:

json
{
  "developerMessage": "The requested resource is not available.",
  "httpStatusCode": "404",
  "defaultUserMessage": "The requested resource is not available.",
  "userMessageGlobalisationCode": "error.msg.resource.not.found",
  "errors": [{
    "developerMessage": "Client with identifier 21994 does not exist",
    "defaultUserMessage": "Client with identifier 21994 does not exist",
    "userMessageGlobalisationCode": "error.msg.client.id.invalid",
    "parameterName": "id",
    "value": null,
    "args": [{"value": 21994}]
  }]
}

The SDK transforms it to:

typescript
{
  name: 'SdkError',
  message: 'The requested resource is not available.',
  statusCode: 404,
  classification: 'NOT_FOUND',
  details: {
    errorCode: 'error.msg.resource.not.found',
    validationErrors: [{
      field: 'id',
      message: 'Client with identifier 21994 does not exist',
      code: 'error.msg.client.id.invalid',
      value: null,
      args: [21994]
    }]
  }
}

GraphQL Error Example

GraphQL errors like 2FA/permission issues:

json
{
  "message": "Access to type 'PaymentRecipientTypeFormats' is restricted",
  "extensions": { "code": "RESTRICTED", "classification": "DataFetchingException" }
}

Are transformed to:

typescript
{
  name: 'SdkError',
  message: "Access to type 'PaymentRecipientTypeFormats' is restricted",
  classification: 'AUTHORIZATION_ERROR',
  details: {
    graphqlCode: 'RESTRICTED',
    errorCode: 'RESTRICTED'
  }
}

Type Guards

typescript
import { isSdkError } from '@mbanq/core-sdk-js';

// Check if error is an SdkError
if (isSdkError(error)) {
  // TypeScript knows error is SdkError
  console.log(error.classification);
}

Accessing Original Error

For debugging, the original error is available via the standard cause property:

typescript
if (isSdkError(error) && error.cause) {
  console.log('Original error:', error.cause.message);
  console.log('Axios code:', (error.cause as any).code);
}

Released under the MIT License.