@worker-tools/middleware
Version:
A suite of standalone HTTP server middlewares for Worker Runtimes.
134 lines • 6.21 kB
JavaScript
// deno-lint-ignore-file
import { notAcceptable, unsupportedMediaType } from "@worker-tools/response-creators";
import negotiated from 'negotiated';
const weightSortFn = (a, b) => a.weight >= b.weight ? a : b;
const ACCEPT = 'Accept';
const ACCEPT_ENCODING = 'Accept-Encoding';
const ACCEPT_LANGUAGE = 'Accept-Language';
const CONTENT_TYPE = 'Content-Type';
const CONTENT_LANGUAGE = 'Content-Language';
const CONTENT_ENCODING = 'Content-Encoding';
const VARY = 'Vary';
/**
* Performs content negotiation over the content type of the response.
* @param types The content types _provided_ by this endpoint.
*/
export function contentTypes(types) {
return async (ax) => {
const ctx = await ax;
const { headers } = ctx.request;
const type = [...negotiated.mediaTypes(headers.get(ACCEPT))]
.filter(t => !types || types.includes(t.type))
.reduce(weightSortFn, { weight: -1 }).type;
if (headers.has(ACCEPT) && types && !type)
throw notAcceptable();
ctx.effects.push(response => {
var _a;
if (!response.headers.has(CONTENT_TYPE))
response.headers.set(CONTENT_TYPE, type);
// If the server accepts more than 1 option, we set the vary header for correct caching
if (((_a = types === null || types === void 0 ? void 0 : types.length) !== null && _a !== void 0 ? _a : 0) > 1)
response.headers.append(VARY, ACCEPT);
return response;
});
return Object.assign(ctx, { type });
};
}
/**
* Performs content negotiation over the content language of the response.
* @param languages The languages _provided_ by this endpoint.
*/
export function contentLanguages(languages) {
return async (ax) => {
const ctx = await ax;
const { headers } = ctx.request;
const language = [...negotiated.languages(headers.get(ACCEPT_LANGUAGE))]
.filter(l => !languages || languages.includes(l.language))
.reduce(weightSortFn, { weight: -1 }).language;
if (headers.has(ACCEPT_LANGUAGE) && languages && !language)
throw notAcceptable();
ctx.effects.push(response => {
var _a;
if (!response.headers.has(CONTENT_LANGUAGE))
response.headers.set(CONTENT_LANGUAGE, language);
// If the server accepts more than 1 option, we set the vary header for correct caching
if (((_a = languages === null || languages === void 0 ? void 0 : languages.length) !== null && _a !== void 0 ? _a : 0) > 1)
response.headers.append(VARY, ACCEPT_LANGUAGE);
return response;
});
return Object.assign(ctx, { language });
};
}
/**
* Performs content negotiation over the content encoding of the response.
* @param encodings The encodings _provided_ by this endpoint.
*/
export function contentEncodings(encodings) {
return async (ax) => {
const ctx = await ax;
const { headers } = ctx.request;
const encoding = [...negotiated.encodings(headers.get(ACCEPT_ENCODING))]
.filter(e => !encodings || encodings.includes(e.encoding))
.reduce(weightSortFn, { weight: -1 }).encoding;
// TODO: how to handle status errors in middleware??
if (headers.has(ACCEPT_ENCODING) && encodings && !encoding)
throw notAcceptable();
ctx.effects.push(response => {
var _a;
if (!response.headers.has(CONTENT_ENCODING))
response.headers.set(CONTENT_ENCODING, encoding);
// If the server accepts more than 1 option, we set the vary header for correct caching
if (((_a = encodings === null || encodings === void 0 ? void 0 : encodings.length) !== null && _a !== void 0 ? _a : 0) > 1)
response.headers.append(VARY, ACCEPT_ENCODING);
return response;
});
return Object.assign(ctx, { encoding });
};
}
export { contentTypes as provides, contentLanguages as providesLanguages, contentEncodings as providesEncodings, };
/**
* Determines if a request body content type is _acceptable_ to this endpoint.
* @param types The content types _acceptable_ to this endpoint.
*/
export function accepts(types) {
return async (ax) => {
var _a;
const ctx = await ax;
const { headers } = ctx.request;
const accepted = (_a = [...negotiated.mediaTypes(headers.get(CONTENT_TYPE))][0]) === null || _a === void 0 ? void 0 : _a.type;
if ((types === null || types === void 0 ? void 0 : types.length) && !types.includes(accepted))
throw unsupportedMediaType();
return Object.assign(ctx, { accepted });
};
}
/**
* Determines if a request body content language is _acceptable_ to this endpoint.
* @param languages The languages (of the request body) _acceptable_ to this endpoint.
*/
export function acceptsLanguages(languages) {
return async (ax) => {
var _a;
const ctx = await ax;
const { headers } = ctx.request;
const acceptedLanguage = (_a = [...negotiated.languages(headers.get(CONTENT_LANGUAGE))][0]) === null || _a === void 0 ? void 0 : _a.language;
if ((languages === null || languages === void 0 ? void 0 : languages.length) && !languages.includes(acceptedLanguage))
throw notAcceptable();
return Object.assign(ctx, { acceptedLanguage });
};
}
/**
* Determines if a request body content encoding is _acceptable_ to this endpoint.
* @param encodings The body encodings _acceptable_ to this endpoint.
*/
export function acceptsEncodings(encodings) {
return async (ax) => {
var _a;
const ctx = await ax;
const { headers } = ctx.request;
const acceptedEncoding = (_a = [...negotiated.encodings(headers.get(CONTENT_ENCODING))][0]) === null || _a === void 0 ? void 0 : _a.encoding;
if ((encodings === null || encodings === void 0 ? void 0 : encodings.length) && !encodings.includes(acceptedEncoding))
throw notAcceptable();
return Object.assign(ctx, { acceptedEncoding });
};
}
//# sourceMappingURL=content-negotiation.js.map