import { has } from '@/utilities' 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. */ 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 { constructor(message, response) { super(message ?? response?.statusText) this.response = response this.errors = Array.from(extractResponseErrors(response?.data)) } /** * 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) { 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.') } else { 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. */ 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)