Skip to content
Snippets Groups Projects
api-helper.js 3.48 KiB
Newer Older
import { has } from '@/utilities'

Konrad Mohrfeldt's avatar
Konrad Mohrfeldt committed
function isErrorObject(obj) {
  return has(obj, 'message') && has(obj, 'code')
}

/**
 * Parses the variety of error formats that are emitted by
 * Django REST framework and yields error objects.
 */
Konrad Mohrfeldt's avatar
Konrad Mohrfeldt committed
function* extractResponseErrors(responseData) {
  if (isErrorObject(responseData)) {
    yield responseData
  } else if (has(responseData, 'detail') && isErrorObject(responseData.detail)) {
    yield responseData.detail
  } else if (Array.isArray(responseData)) {
    for (const item of responseData) {
      if (isErrorObject(item)) {
        yield item
      }
}

export class APIError extends Error {
Konrad Mohrfeldt's avatar
Konrad Mohrfeldt committed
  constructor(message, response) {
    super(message ?? response?.statusText)
    this.response = response
    this.errors = Array.from(extractResponseErrors(response?.data))
  }
Konrad Mohrfeldt's avatar
Konrad Mohrfeldt committed
  /**
   * Handle API response errors by either calling the provided callbacks
   * or throwing exceptions that can be processed in promise handlers.
   */
  static handle(error, message, context, vm) {
    if (typeof context?.callback === 'function' || typeof context?.callbackCancel === 'function') {
      // This API request was made by a callback-style caller,
      // so we do error handling like we used to do it.
      handleApiError(vm, error, message)
      context?.callbackCancel?.()
    } else {
      // This API request was made by a caller that did not define any callback handlers.
      // We take this as an indication, that the caller wants to use
      // traditional flow-control with try-catch statements.
      throw new APIError(message, error.response)
}

export function handleApiError(vm, err, message) {
Konrad Mohrfeldt's avatar
Konrad Mohrfeldt committed
  if (err.response) {
    vm.$log.error(err.response.status + ' ' + err.response.statusText)
    vm.$log.error(err.response)
    let msg = 'Error: '
    if (message) {
      msg += message
    }
    msg += '\n\n'
    msg += 'Status code: ' + err.response.status + ' ' + err.response.statusText
    alert(msg)
  } else {
    vm.$log.error(err)
    if (message) {
      alert('Error: ' + message + '\nInspect the console for more details.')
Konrad Mohrfeldt's avatar
Konrad Mohrfeldt committed
      alert('Unspecified error. Inspect the console for details.')
}

/**
 * Like APIError.handle this is a helper to allow us to migrate from the
 * callback-oriented to promise-based response handling.
 *
 * When a callback is provided it mimics the old callback behaviour and
 * calls the callback with the response and returns undefined.
 * If no callback is provided we assume the caller wants to handle
 * the response promise.
 */
Konrad Mohrfeldt's avatar
Konrad Mohrfeldt committed
export function callOrReturn(response, callback) {
  if (typeof callback === 'function') {
    callback(response)
    return undefined
  } else {
    return response
  }

function createURLBuilder(basepath, useTrailingSlash = true) {
  // Strip all trailing slashes from the basepath.
  // We handle slashes when building URLs.
  basepath = basepath.replace(/\/*$/, '')

  return function buildURL(...subPaths) {
    let params
    if (subPaths.at(-1) instanceof URLSearchParams) {
      params = subPaths.pop()
    if (subPaths.some((path) => String(path).includes('/'))) {
      throw new Error('Subpaths must not contain slashes')
    }
    const subPath = '/' + subPaths.join('/')
    const url = basepath + subPath + (useTrailingSlash ? '/' : '')
    return params ? url + `?${params}` : url
  }
}

export const createTankURL = createURLBuilder(import.meta.env.VUE_APP_API_TANK, false)
export const createSteeringURL = createURLBuilder(import.meta.env.VUE_APP_API_STEERING)