UNPKG

@sanity/client

Version:

Client for retrieving, creating and patching data from Sanity.io

132 lines (113 loc) 3.98 kB
import type {ActionError, Any, ErrorProps, MutationError} from '../types' const MAX_ITEMS_IN_ERROR_MESSAGE = 5 /** @public */ export class ClientError extends Error { response: ErrorProps['response'] statusCode: ErrorProps['statusCode'] = 400 responseBody: ErrorProps['responseBody'] details: ErrorProps['details'] constructor(res: Any) { const props = extractErrorProps(res) super(props.message) Object.assign(this, props) } } /** @public */ export class ServerError extends Error { response: ErrorProps['response'] statusCode: ErrorProps['statusCode'] = 500 responseBody: ErrorProps['responseBody'] details: ErrorProps['details'] constructor(res: Any) { const props = extractErrorProps(res) super(props.message) Object.assign(this, props) } } function extractErrorProps(res: Any): ErrorProps { const body = res.body const props = { response: res, statusCode: res.statusCode, responseBody: stringifyBody(body, res), message: '', details: undefined as Any, } // API/Boom style errors ({statusCode, error, message}) if (body.error && body.message) { props.message = `${body.error} - ${body.message}` return props } // Mutation errors (specifically) if (isMutationError(body) || isActionError(body)) { const allItems = body.error.items || [] const items = allItems .slice(0, MAX_ITEMS_IN_ERROR_MESSAGE) .map((item) => item.error?.description) .filter(Boolean) let itemsStr = items.length ? `:\n- ${items.join('\n- ')}` : '' if (allItems.length > MAX_ITEMS_IN_ERROR_MESSAGE) { itemsStr += `\n...and ${allItems.length - MAX_ITEMS_IN_ERROR_MESSAGE} more` } props.message = `${body.error.description}${itemsStr}` props.details = body.error return props } // Query/database errors ({error: {description, other, arb, props}}) if (body.error && body.error.description) { props.message = body.error.description props.details = body.error return props } // Other, more arbitrary errors props.message = body.error || body.message || httpErrorMessage(res) return props } function isMutationError(body: Any): body is MutationError { return ( isPlainObject(body) && isPlainObject(body.error) && body.error.type === 'mutationError' && typeof body.error.description === 'string' ) } function isActionError(body: Any): body is ActionError { return ( isPlainObject(body) && isPlainObject(body.error) && body.error.type === 'actionError' && typeof body.error.description === 'string' ) } function isPlainObject(obj: Any): obj is Record<string, unknown> { return typeof obj === 'object' && obj !== null && !Array.isArray(obj) } function httpErrorMessage(res: Any) { const statusMessage = res.statusMessage ? ` ${res.statusMessage}` : '' return `${res.method}-request to ${res.url} resulted in HTTP ${res.statusCode}${statusMessage}` } function stringifyBody(body: Any, res: Any) { const contentType = (res.headers['content-type'] || '').toLowerCase() const isJson = contentType.indexOf('application/json') !== -1 return isJson ? JSON.stringify(body, null, 2) : body } /** @public */ export class CorsOriginError extends Error { projectId: string addOriginUrl?: URL constructor({projectId}: {projectId: string}) { super('CorsOriginError') this.name = 'CorsOriginError' this.projectId = projectId const url = new URL(`https://sanity.io/manage/project/${projectId}/api`) if (typeof location !== 'undefined') { const {origin} = location url.searchParams.set('cors', 'add') url.searchParams.set('origin', origin) this.addOriginUrl = url this.message = `The current origin is not allowed to connect to the Live Content API. Add it here: ${url}` } else { this.message = `The current origin is not allowed to connect to the Live Content API. Change your configuration here: ${url}` } } }