UNPKG

mockttp

Version:

Mock HTTP server for testing HTTP clients and stubbing webservices

156 lines (134 loc) 5.81 kB
import { Buffer } from 'buffer'; import * as _ from 'lodash'; import { encode as encodeBase64 } from 'base64-arraybuffer'; import { MaybePromise, UnreachableCheck } from '@httptoolkit/util'; import { CompletedBody, Headers } from "../types"; import { asBuffer } from "../util/buffer-utils"; import { buildBodyReader, isMockttpBody } from "../util/request-utils"; import { Replace } from "../util/type-utils"; import { deserializeBuffer, serializeBuffer } from "./serialization"; export type SerializedBody = // Base64 string of encoded body, from 'none' body decoding option, or old servers: | string // Was encoded, now decoded successfully: | { encoded: string, decoded: string, decodingError: undefined } // Trivially known that no decoding was used: | { encoded: string, decoded: undefined, decodingError: undefined } // Was encoded, but decoding failed: | { encoded: string, decodingError: string, decoded: undefined }; // Server-side: serialize a body, so it can become a CompletedBody on the client side export async function withSerializedBodyReader<T extends { headers: Headers, body: CompletedBody }>( input: T, bodySerializer: BodySerializer ): Promise<Replace<T, { body: SerializedBody }>> { return { ...input, body: await bodySerializer(input.body, input.headers) }; } export type BodySerializer = (body: CompletedBody, headers: Headers) => MaybePromise<SerializedBody>; // Client-side: turn a serialized body back into a CompletedBody (body to be exposed for convenient access) export function withDeserializedBodyReader<T extends { headers: Headers, body: CompletedBody }>( input: Replace<T, { body: SerializedBody }> ): T { let encodedBodyString: string; let decodedBodyString: string | undefined; let decodedBodyError: string | undefined; // We don't need to know the expected serialization format: we can detect it, and just // use what we get sensibly regardless: if (typeof input.body === 'string') { // If the body is a string, it is a base64-encoded string encodedBodyString = input.body; } else if (typeof input.body === 'object') { encodedBodyString = input.body.encoded; decodedBodyString = input.body.decoded; decodedBodyError = input.body.decodingError; } else { throw new UnreachableCheck(input.body); } return { ...input, body: deserializeBodyReader(encodedBodyString, decodedBodyString, decodedBodyError, input.headers), } as T; } export function deserializeBodyReader( encodedBodyString: string, decodedBodyString: string | undefined, decodingError: string | undefined, headers: Headers ): CompletedBody { const encodedBody = deserializeBuffer(encodedBodyString); const decodedBody = decodedBodyString ? deserializeBuffer(decodedBodyString) : undefined; const decoder = !!decodedBody // If the server provides a pre-decoded body, we use it. ? async () => decodedBody // If not, all encoded bodies are non-decodeable on the client side. This should // only happen with messageBodyDecoding = 'none' (or with v4+ clients + <v4 servers). : failIfDecodingRequired.bind(null, decodingError); return buildBodyReader(encodedBody, headers, decoder); } function failIfDecodingRequired(errorMessage: string | undefined, buffer: Buffer, headers: Headers) { if (!headers['content-encoding'] || headers['content-encoding'] === 'identity') { return buffer; } const error = errorMessage ? new Error(`Decoding error (${headers['content-encoding']}): ${errorMessage}`) : new Error("Can't read encoded message body without server-side decoding"); console.warn(error.message); throw error; } /** * Serialize a callback result (callback handlers, BeforeRequest/Response etc) * to transform all the many possible buffer formats into either base64-encoded * buffer data, or undefined. */ export function withSerializedCallbackBuffers<T extends { body?: CompletedBody | Buffer | Uint8Array | ArrayBuffer | string, rawBody?: Buffer | Uint8Array }>(input: T): Replace<T, { body: string | undefined }> { let serializedBody: string | undefined; if (!input.body) { serializedBody = undefined; } else if (_.isString(input.body)) { serializedBody = serializeBuffer(asBuffer(input.body)); } else if (_.isBuffer(input.body)) { serializedBody = serializeBuffer(input.body as Buffer); } else if (_.isArrayBuffer(input.body) || _.isTypedArray(input.body)) { serializedBody = encodeBase64(input.body as ArrayBuffer); } else if (isMockttpBody(input.body)) { serializedBody = serializeBuffer(asBuffer(input.body.buffer)); } return { ...input, body: serializedBody, rawBody: input.rawBody ? serializeBuffer(asBuffer(input.rawBody)) : undefined }; } export type WithSerializedCallbackBuffers<T extends { body?: any }> = Replace<T, { body?: string, rawBody?: string }>; /** * Deserialize a callback result (callback handlers, BeforeRequest/Response etc) * to build buffer data (or undefined) from the base64-serialized data * produced by withSerializedCallbackBuffers */ export function withDeserializedCallbackBuffers<T extends { body?: Buffer | Uint8Array | string, rawBody?: Buffer | Uint8Array }>( input: Replace<T, { body?: string, rawBody?: string }> ): T { return { ...input, body: input.body !== undefined ? Buffer.from(input.body, 'base64') : undefined, rawBody: input.rawBody !== undefined ? Buffer.from(input.rawBody, 'base64') : undefined } as T; }