UNPKG

@whook/whook

Version:

Build strong and efficient REST web services.

114 lines 4.28 kB
import { YHTTPError } from 'yhttperror'; import FirstChunkStream from 'first-chunk-stream'; import { YError } from 'yerror'; import { ensureResolvedObject } from 'ya-open-api-types'; import { pickFirstHeaderValue } from './headers.js'; import { Readable } from 'node:stream'; /* Architecture Note #2.11.3: Request body According to the OpenAPI specification there are two kinds of requests: - **validated contents:** it implies to buffer their content and parse them to finally validate it. In that case, we provide it as a plain JS object to the routes handlers. - **streamable contents:** often used for large files, those contents must be parsed and validated into the handler itself. */ export async function getBody({ DECODERS, PARSERS, API, bufferLimit, }, operation, inputStream, bodySpec) { const bodyIsEmpty = !(bodySpec.contentType && bodySpec.contentLength); const requestBody = 'requestBody' in operation && operation.requestBody ? await ensureResolvedObject(API, operation.requestBody) : undefined; const schemaObject = (await ensureResolvedObject(API, requestBody && requestBody.content && requestBody.content[bodySpec.contentType] && requestBody.content[bodySpec.contentType].schema && requestBody.content[bodySpec.contentType] .schema)); const bodyIsBinary = typeof schemaObject === 'object' && schemaObject !== null && 'type' in schemaObject && schemaObject.type === 'string' && 'format' in schemaObject && schemaObject.format === 'binary'; if (bodyIsEmpty) { return; } if (bodyIsBinary) { return inputStream; } if (!PARSERS?.[bodySpec.contentType]) { return Promise.reject(new YHTTPError(500, 'E_PARSER_LACK', bodySpec.contentType)); } if (bodySpec.contentLength > bufferLimit) { throw new YHTTPError(400, 'E_REQUEST_CONTENT_TOO_LARGE', bodySpec.contentLength, bufferLimit); } const body = await new Promise((resolve, reject) => { const Decoder = DECODERS?.[bodySpec.charset]; if (!Decoder) { return Promise.reject(new YHTTPError(500, 'E_DECODER_LACK', bodySpec.charset)); } inputStream.on('error', (err) => { reject(YHTTPError.wrap(err, 400, 'E_REQUEST_FAILURE')); }); inputStream.pipe(new Decoder()).pipe(new FirstChunkStream({ chunkSize: bufferLimit + 1, }, async (chunk) => { if (bufferLimit >= chunk.length) { resolve(Buffer.from(chunk)); return chunk; } reject(new YHTTPError(400, 'E_REQUEST_CONTENT_TOO_LARGE', chunk.length, bufferLimit)); return FirstChunkStream.stop; })); }); if (body.length !== bodySpec.contentLength) { throw new YHTTPError(400, 'E_BAD_BODY_LENGTH', body.length, bodySpec.contentLength); } try { return await new Promise((resolve, reject) => { try { resolve(PARSERS[bodySpec.contentType](body.toString(), bodySpec)); } catch (err) { reject(err); } }); } catch (err) { throw YHTTPError.wrap(err, 400, 'E_BAD_BODY', body.toString()); } } export async function sendBody({ ENCODERS, STRINGIFIERS, }, response) { if (!response.body) { return response; } if (response.body instanceof Readable) { return response; } const responseContentType = pickFirstHeaderValue('content-type', response.headers || {}) || 'text/plain'; if (!STRINGIFIERS?.[responseContentType]) { throw new YError('E_STRINGIFYER_LACK', response.headers?.['content-type'], response); } const Encoder = ENCODERS?.['utf-8']; if (!Encoder) { throw new YError('E_ENCODER_LACK', 'utf-8'); } const stream = new Encoder(); const content = STRINGIFIERS[responseContentType](response.body); stream.write(content); stream.end(); return { ...response, headers: { 'content-type': `${response.headers?.['content-type']}; charset=utf-8`, ...response.headers, }, body: stream, }; } //# sourceMappingURL=body.js.map