UNPKG

@whook/whook

Version:

Build strong and efficient REST web services.

132 lines 6.06 kB
import { YHTTPError } from 'yhttperror'; import contentType from 'content-type'; import preferredCharsets from 'negotiator/lib/charset.js'; import preferredMediaType from 'negotiator/lib/encoding.js'; import { YError } from 'yerror'; import { pickFirstHeaderValue, pickAllHeaderValues } from './headers.js'; import { ensureResolvedObject } from 'ya-open-api-types'; const { parse: parseContentType } = contentType; export async function extractConsumableMediaTypes(API, operation) { if (!('requestBody' in operation && operation.requestBody)) { return []; } const requestBody = await ensureResolvedObject(API, operation.requestBody); if (!('content' in requestBody && requestBody.content)) { return []; } // Per spec contents, the `content` property should always // be present so not checking before using it // https://swagger.io/specification/#requestBodyObject return Object.keys(requestBody.content); } export async function extractProduceableMediaTypes(API, operation) { if (!('responses' in operation && operation.responses)) { return []; } const produceableMediaTypes = []; for (const response of Object.values(operation.responses)) { if (!(typeof response === 'object' && response)) { continue; } const resolvedResponse = await ensureResolvedObject(API, response); if (!('content' in resolvedResponse && resolvedResponse.content)) { continue; } produceableMediaTypes.push(...Object.keys(resolvedResponse.content || {})); } return [...new Set(produceableMediaTypes)]; } export function extractBodySpec(request, consumableMediaTypes, consumableCharsets) { const contentLengthValues = pickAllHeaderValues('content-length', request.headers); const bodySpec = { contentType: '', contentLength: contentLengthValues.length ? Number(contentLengthValues[0]) : 0, charset: 'utf-8', }; if (request.headers['content-type']) { const baseContentType = pickFirstHeaderValue('content-type', request.headers); try { if (typeof baseContentType === 'string') { const parsedContentType = parseContentType(baseContentType); bodySpec.contentType = parsedContentType.type; if (parsedContentType.parameters && parsedContentType.parameters.charset) { if (['utf-8', 'utf8'].includes(parsedContentType.parameters.charset.toLowerCase())) { bodySpec.charset = 'utf-8'; } else { throw new YError('E_UNSUPPORTED_CHARSET', parsedContentType.parameters.charset); } } if (parsedContentType.parameters && parsedContentType.parameters.boundary) { bodySpec.boundary = parsedContentType.parameters.boundary; } } else { throw new YError('E_EMPTY_CONTENT_TYPE'); } } catch (err) { throw YHTTPError.wrap(err, 400, 'E_BAD_CONTENT_TYPE', request.headers['content-type']); } } checkBodyCharset(bodySpec, consumableCharsets); checkBodyMediaType(bodySpec, consumableMediaTypes); return bodySpec; } export function checkBodyCharset(bodySpec, consumableCharsets) { if (bodySpec.contentLength && bodySpec.charset && !consumableCharsets.includes(bodySpec.charset)) { throw new YHTTPError(406, 'E_UNSUPPORTED_CHARSET', bodySpec.charset, consumableCharsets); } } export function checkBodyMediaType(bodySpec, consumableMediaTypes) { if (bodySpec.contentLength && bodySpec.contentType && !consumableMediaTypes.includes(bodySpec.contentType)) { throw new YHTTPError(415, 'E_UNSUPPORTED_MEDIA_TYPE', bodySpec.contentType, consumableMediaTypes); } } export function extractResponseSpec(operation, request, supportedMediaTypes, supportedCharsets) { const accept = pickFirstHeaderValue('accept', request.headers) || '*'; const responseSpec = { charsets: request.headers['accept-charset'] ? preferredCharsets(pickFirstHeaderValue('accept-charset', request.headers), supportedCharsets) : supportedCharsets, contentTypes: preferredMediaType(accept.replace(/(^|,)\*\/\*($|,|;)/g, '$1*$2'), supportedMediaTypes), }; return responseSpec; } export function checkResponseMediaType(request, responseSpec, produceableMediaTypes) { if (0 === responseSpec.contentTypes.length) { throw new YHTTPError(406, 'E_UNACCEPTABLE_MEDIA_TYPE', request.headers.accept, produceableMediaTypes); } } export function checkResponseCharset(request, responseSpec, produceableCharsets) { if (0 === responseSpec.charsets.length) { throw new YHTTPError(406, 'E_UNACCEPTABLE_CHARSET', request.headers['accept-charset'], responseSpec.charsets, produceableCharsets); } } export async function executeHandler(definition, handler, parameters) { const responsePromise = handler(parameters, definition); if (!(responsePromise && responsePromise.then)) { throw new YHTTPError(500, 'E_NO_RESPONSE_PROMISE', definition.operation.operationId, definition.method, definition.path); } const response = await responsePromise; if (!response) { throw new YHTTPError(500, 'E_NO_RESPONSE', definition.operation.operationId, definition.method, definition.path); } if ('undefined' === typeof response.status) { throw new YHTTPError(500, 'E_NO_RESPONSE_STATUS', definition.operation.operationId, definition.method, definition.path); } if ('number' !== typeof response.status) { throw new YHTTPError(500, 'E_NON_NUMERIC_STATUS', definition.operation.operationId, definition.method, definition.path); } response.headers = response.headers || {}; return response; } //# sourceMappingURL=router.js.map