UNPKG

mappersmith

Version:

It is a lightweight rest client for node.js and the browser

168 lines (142 loc) 4.69 kB
import { lowerCaseObjectKeys } from './utils/index' import { Request } from './request' import type { Headers } from './types' export const REGEXP_CONTENT_TYPE_JSON = /^application\/(json|.*\+json)/ export interface ResponseParams { readonly status?: number readonly rawData?: string readonly headers?: Headers readonly error?: Error } /** * @typedef Response * @param {Request} originalRequest, for auth it hides the password * @param {Integer} responseStatus * @param {String} responseData, defaults to null * @param {Object} responseHeaders, defaults to an empty object ({}) * @param {Array<Error>} errors, defaults to an empty array ([]) */ type SerializableJSON = number | string | boolean | null | Record<string, unknown> export type ParsedJSON = SerializableJSON | SerializableJSON[] export class Response<DataType extends ParsedJSON = ParsedJSON> { public readonly originalRequest: Request public readonly responseStatus: number public readonly responseData: string | null public readonly responseHeaders: Headers // eslint-disable-next-line no-use-before-define public readonly errors: Array<Error | string> public timeElapsed: number | null constructor( originalRequest: Request, responseStatus: number, responseData?: string | null, responseHeaders?: Headers, errors?: Array<Error | string> ) { const auth = originalRequest.requestParams && originalRequest.requestParams.auth if (auth) { const maskedAuth = { ...auth, password: '***' } this.originalRequest = originalRequest.enhance({ auth: maskedAuth }) } else { this.originalRequest = originalRequest } this.responseStatus = responseStatus this.responseData = responseData ?? null this.responseHeaders = responseHeaders || {} this.errors = errors || [] this.timeElapsed = null } public request() { return this.originalRequest } public status() { // IE sends 1223 instead of 204 if (this.responseStatus === 1223) { return 204 } return this.responseStatus } /** * Returns true if status is greater or equal 200 or lower than 400 */ public success() { const status = this.status() return status >= 200 && status < 400 } /** * Returns an object with the headers. Header names are converted to * lowercase */ public headers() { return lowerCaseObjectKeys(this.responseHeaders) } /** * Utility method to get a header value by name */ public header<T extends string | number | boolean>(name: string): T | undefined { const key = name.toLowerCase() if (key in this.headers()) { return this.headers()[key] as T } return undefined } /** * Returns the original response data */ public rawData() { return this.responseData } /** * Returns the response data, if "Content-Type" is "application/json" * it parses the response and returns an object. * Friendly reminder: * - JSON.parse() can return null, an Array or an object. */ public data<T = DataType>() { if (this.isContentTypeJSON() && this.responseData !== null) { try { return JSON.parse(this.responseData) as T } catch (e) {} // eslint-disable-line no-empty } return this.responseData as unknown as T } public isContentTypeJSON() { const contentType = this.header<string>('content-type') if (contentType === undefined) { return false } return REGEXP_CONTENT_TYPE_JSON.test(contentType) } /** * Returns the last error instance that caused the request to fail */ public error() { const lastError = this.errors[this.errors.length - 1] || null if (typeof lastError === 'string') { return new Error(lastError) } return lastError } /** * Enhances current Response returning a new Response * * @param {Object} extras * @param {Integer} extras.status - it will replace the current status * @param {String} extras.rawData - it will replace the current rawData * @param {Object} extras.headers - it will be merged with current headers * @param {Error} extras.error - it will be added to the list of errors */ public enhance(extras: ResponseParams) { const mergedHeaders = { ...this.headers(), ...(extras.headers || {}) } const enhancedResponse = new Response<DataType>( this.request(), extras.status || this.status(), extras.rawData || this.rawData(), mergedHeaders, extras.error ? [...this.errors, extras.error] : [...this.errors] ) enhancedResponse.timeElapsed = this.timeElapsed return enhancedResponse } } export default Response