Error Handling

All API calls may throw errors. This guide covers best practices for handling errors gracefully and providing meaningful feedback to users.

Basic Error Handling

Always wrap API calls in try-catch blocks to handle potential errors:

try {
  const account = await dataverseAPI.retrieve('account', accountId)
  // Process account
} catch (error) {
  console.error('Failed to retrieve account:', error)

  await toolboxAPI.utils.showNotification({
    title: 'Error',
    body: error.message,
    type: 'error',
    duration: 0, // Persistent
  })
}

Handling Multiple Operations

When performing multiple operations, decide whether to stop on first error or continue:

// Stop on first error
async function importRecords(records) {
  try {
    for (const record of records) {
      await dataverseAPI.create('account', record)
    }
    
    await toolboxAPI.utils.showNotification({
      title: 'Success',
      body: `${records.length} records imported`,
      type: 'success'
    })
  } catch (error) {
    console.error('Import failed:', error)
    
    await toolboxAPI.utils.showNotification({
      title: 'Import Failed',
      body: error.message,
      type: 'error'
    })
  }
}
// Continue on errors and collect results
async function importRecordsWithReport(records) {
  const results = {
    success: [],
    failed: []
  }
  
  for (const record of records) {
    try {
      const id = await dataverseAPI.create('account', record)
      results.success.push({ record, id })
    } catch (error) {
      results.failed.push({ record, error: error.message })
    }
  }
  
  // Show summary
  await toolboxAPI.utils.showNotification({
    title: 'Import Complete',
    body: `${results.success.length} succeeded, ${results.failed.length} failed`,
    type: results.failed.length > 0 ? 'warning' : 'success'
  })
  
  return results
}

API-Specific Errors

Dataverse API Errors

Dataverse API errors typically include HTTP status codes and detailed messages:

try {
  await dataverseAPI.retrieve('account', invalidId)
} catch (error) {
  console.error('Dataverse error:', error)
  
  // Error object structure
  // {
  //   message: "Error message",
  //   status: 404,
  //   statusText: "Not Found"
  // }
  
  let userMessage = 'Failed to retrieve record'
  
  if (error.status === 404) {
    userMessage = 'Record not found'
  } else if (error.status === 403) {
    userMessage = 'You do not have permission to access this record'
  } else if (error.status === 401) {
    userMessage = 'Authentication failed. Please reconnect.'
  }
  
  await toolboxAPI.utils.showNotification({
    title: 'Error',
    body: userMessage,
    type: 'error'
  })
}

Connection Errors

Handle cases where no connection is available:

async function fetchData() {
  try {
    const connection = await toolboxAPI.connections.getActiveConnection()
    
    if (!connection) {
      await toolboxAPI.utils.showNotification({
        title: 'No Connection',
        body: 'Please connect to a Dataverse environment',
        type: 'warning'
      })
      return
    }
    
    // Proceed with data fetch
    const data = await dataverseAPI.queryData('accounts?$top=10')
    return data
  } catch (error) {
    console.error('Failed to fetch data:', error)
    
    await toolboxAPI.utils.showNotification({
      title: 'Error',
      body: 'Failed to fetch data from Dataverse',
      type: 'error'
    })
  }
}

File System Errors

Handle file system operations with specific error messages:

async function loadConfiguration(filePath) {
  try {
    // Check if file exists
    const exists = await toolboxAPI.fileSystem.exists(filePath)
    
    if (!exists) {
      await toolboxAPI.utils.showNotification({
        title: 'File Not Found',
        body: `Configuration file not found at ${filePath}`,
        type: 'warning'
      })
      return null
    }
    
    // Read and parse file
    const content = await toolboxAPI.fileSystem.readText(filePath)
    const config = JSON.parse(content)
    
    return config
  } catch (error) {
    console.error('Failed to load configuration:', error)
    
    let message = 'Failed to load configuration file'
    
    if (error.name === 'SyntaxError') {
      message = 'Configuration file contains invalid JSON'
    } else if (error.message.includes('permission')) {
      message = 'Permission denied. Check file permissions.'
    }
    
    await toolboxAPI.utils.showNotification({
      title: 'Error',
      body: message,
      type: 'error'
    })
    
    return null
  }
}

User Feedback

Notification Types

Use appropriate notification types for different scenarios:

// Success - Operation completed successfully
await toolboxAPI.utils.showNotification({
  title: 'Success',
  body: 'Data exported successfully',
  type: 'success',
  duration: 3000
})

// Info - Informational message
await toolboxAPI.utils.showNotification({
  title: 'Info',
  body: 'Processing 100 records...',
  type: 'info',
  duration: 5000
})

// Warning - Non-critical issue
await toolboxAPI.utils.showNotification({
  title: 'Warning',
  body: 'Some records were skipped',
  type: 'warning',
  duration: 0 // Persistent
})

// Error - Critical failure
await toolboxAPI.utils.showNotification({
  title: 'Error',
  body: 'Failed to connect to Dataverse',
  type: 'error',
  duration: 0 // Persistent
})

Loading States

Show loading indicators for long-running operations:

async function exportLargeDataset() {
  try {
    await toolboxAPI.utils.showLoading('Exporting data...')
    
    // Fetch data
    const data = await dataverseAPI.queryData('accounts?$top=1000')
    
    // Process and save
    const filePath = await toolboxAPI.fileSystem.saveFile(
      'export.json',
      JSON.stringify(data, null, 2)
    )
    
    await toolboxAPI.utils.hideLoading()
    
    if (filePath) {
      await toolboxAPI.utils.showNotification({
        title: 'Export Complete',
        body: `Data exported to ${filePath}`,
        type: 'success'
      })
    }
  } catch (error) {
    await toolboxAPI.utils.hideLoading()
    
    console.error('Export failed:', error)
    
    await toolboxAPI.utils.showNotification({
      title: 'Export Failed',
      body: error.message,
      type: 'error'
    })
  }
}

Progress Updates

For operations with multiple steps, update the loading message:

async function complexOperation() {
  try {
    await toolboxAPI.utils.showLoading('Step 1: Fetching accounts...')
    const accounts = await dataverseAPI.queryData('accounts?$top=100')
    
    await toolboxAPI.utils.showLoading('Step 2: Fetching contacts...')
    const contacts = await dataverseAPI.queryData('contacts?$top=100')
    
    await toolboxAPI.utils.showLoading('Step 3: Processing data...')
    const processed = processData(accounts, contacts)
    
    await toolboxAPI.utils.showLoading('Step 4: Saving results...')
    await saveResults(processed)
    
    await toolboxAPI.utils.hideLoading()
    
    await toolboxAPI.utils.showNotification({
      title: 'Success',
      body: 'Operation completed successfully',
      type: 'success'
    })
  } catch (error) {
    await toolboxAPI.utils.hideLoading()
    
    console.error('Operation failed:', error)
    
    await toolboxAPI.utils.showNotification({
      title: 'Error',
      body: error.message,
      type: 'error'
    })
  }
}

Best Practices

1. Always Use Try-Catch

Never assume an API call will succeed:

// Good
try {
  const data = await dataverseAPI.queryData('accounts')
} catch (error) {
  handleError(error)
}

// Bad
const data = await dataverseAPI.queryData('accounts')

2. Log Errors for Debugging

Always log errors with context:

try {
  await dataverseAPI.create('account', record)
} catch (error) {
  // Log with context
  console.error('Failed to create account:', {
    error: error.message,
    record: record,
    timestamp: new Date().toISOString()
  })
  
  // Show user-friendly message
  await toolboxAPI.utils.showNotification({
    title: 'Error',
    body: 'Failed to create account',
    type: 'error'
  })
}

3. Provide Actionable Messages

Tell users what went wrong and what they can do:

// Good: Specific and actionable
await toolboxAPI.utils.showNotification({
  title: 'Connection Failed',
  body: 'Could not connect to Dataverse. Please check your connection and try again.',
  type: 'error'
})

// Bad: Vague
await toolboxAPI.utils.showNotification({
  title: 'Error',
  body: 'Something went wrong',
  type: 'error'
})

4. Hide Technical Details

Don't expose technical errors to users:

try {
  await dataverseAPI.create('account', record)
} catch (error) {
  // Good: User-friendly message
  await toolboxAPI.utils.showNotification({
    title: 'Failed to Create Record',
    body: 'Unable to create the account. Please verify your data and try again.',
    type: 'error'
  })
  
  // Technical details go to console
  console.error('Technical error:', error)
}

// Bad: Exposing technical details
await toolboxAPI.utils.showNotification({
  title: 'Error',
  body: error.stack, // Don't show stack traces to users
  type: 'error'
})

5. Clean Up Resources

Always clean up resources in finally blocks:

let terminal

try {
  terminal = await toolboxAPI.terminal.create({ name: 'Build' })
  await toolboxAPI.terminal.execute(terminal.id, 'npm install')
} catch (error) {
  console.error('Build failed:', error)
  
  await toolboxAPI.utils.showNotification({
    title: 'Build Failed',
    body: error.message,
    type: 'error'
  })
} finally {
  // Always close terminal
  if (terminal) {
    await toolboxAPI.terminal.close(terminal.id)
  }
  
  // Always hide loading
  await toolboxAPI.utils.hideLoading()
}

6. Validate Input

Validate user input before making API calls:

async function createAccount(name, email) {
  // Validate input
  if (!name || name.trim() === '') {
    await toolboxAPI.utils.showNotification({
      title: 'Validation Error',
      body: 'Account name is required',
      type: 'warning'
    })
    return
  }
  
  if (email && !isValidEmail(email)) {
    await toolboxAPI.utils.showNotification({
      title: 'Validation Error',
      body: 'Please enter a valid email address',
      type: 'warning'
    })
    return
  }
  
  // Proceed with API call
  try {
    const id = await dataverseAPI.create('account', { name, emailaddress1: email })
    
    await toolboxAPI.utils.showNotification({
      title: 'Success',
      body: 'Account created successfully',
      type: 'success'
    })
    
    return id
  } catch (error) {
    console.error('Failed to create account:', error)
    
    await toolboxAPI.utils.showNotification({
      title: 'Error',
      body: 'Failed to create account',
      type: 'error'
    })
  }
}

7. Implement Retry Logic

For transient errors, implement retry logic:

async function retryOperation(operation, maxRetries = 3, delay = 1000) {
  let lastError
  
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await operation()
    } catch (error) {
      lastError = error
      console.log(`Attempt ${i + 1} failed:`, error.message)
      
      if (i < maxRetries - 1) {
        // Wait before retrying
        await new Promise(resolve => setTimeout(resolve, delay * (i + 1)))
      }
    }
  }
  
  // All retries failed
  throw lastError
}

// Usage
try {
  const data = await retryOperation(
    () => dataverseAPI.queryData('accounts?$top=10')
  )
} catch (error) {
  await toolboxAPI.utils.showNotification({
    title: 'Error',
    body: 'Failed to fetch data after multiple attempts',
    type: 'error'
  })
}

Common Error Scenarios

HTTP Error Codes

StatusMeaningUser Message
400Bad RequestInvalid request. Please check your data.
401UnauthorizedAuthentication failed. Please reconnect.
403ForbiddenYou don't have permission to perform this action.
404Not FoundRecord not found. It may have been deleted.
429Too Many RequestsToo many requests. Please wait and try again.
500Internal Server ErrorServer error. Please try again later.
503Service UnavailableService temporarily unavailable. Please try again later.

Example Implementation

async function handleDataverseError(error) {
  const statusMessages = {
    400: 'Invalid request. Please check your data.',
    401: 'Authentication failed. Please reconnect.',
    403: "You don't have permission to perform this action.",
    404: 'Record not found. It may have been deleted.',
    429: 'Too many requests. Please wait and try again.',
    500: 'Server error. Please try again later.',
    503: 'Service temporarily unavailable. Please try again later.'
  }
  
  const message = statusMessages[error.status] || 'An unexpected error occurred'
  
  await toolboxAPI.utils.showNotification({
    title: 'Error',
    body: message,
    type: 'error',
    duration: 0
  })
}

Was this page helpful?