graphql-yoga
Version:
97 lines (96 loc) • 3.92 kB
JavaScript
import { getDefaultIncrementalDeliverySpec, getIncrementalSpecParameterNames, getMultipartContentType, INCREMENTAL_DELIVERY_SPECS, lookupIncrementalDeliverySpec, } from '@graphql-tools/utils';
export { DEFAULT_MULTIPART_CONTENT_TYPE, getMultipartContentType, INCREMENTAL_DELIVERY_SPECS, } from '@graphql-tools/utils';
function typeAndSubtype(mediaType) {
const [essence] = mediaType.split(';');
const [pre, suf] = (essence ?? '').split('/');
return [pre ?? '', suf ?? ''];
}
function parseMediaTypeParams(mediaType) {
const params = {};
const parts = mediaType.split(';');
for (let i = 1; i < parts.length; i++) {
const part = parts[i];
if (part == null)
continue;
const eq = part.indexOf('=');
if (eq === -1)
continue;
let value = part.slice(eq + 1);
if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
value = value.slice(1, -1);
}
params[part.slice(0, eq)] = value;
}
return params;
}
export function getMediaTypesForRequestInOrder(request, specs = INCREMENTAL_DELIVERY_SPECS) {
const specParamNames = getIncrementalSpecParameterNames(specs);
const accepts = (request.headers.get('accept') || '*/*')
.replace(/\s/g, '')
.toLowerCase()
.split(',');
const mediaTypes = [];
for (const accept of accepts) {
const [mediaType, ...params] = accept.split(';');
if (mediaType === undefined)
continue; // If true, malformed header.
const charset = params?.find(param => param.includes('charset=')) || 'charset=utf-8'; // utf-8 is assumed when not specified;
if (charset !== 'charset=utf-8') {
// only utf-8 is supported
continue;
}
const specParams = params.filter(param => {
const name = param.split('=')[0];
return name != null && specParamNames.has(name);
});
mediaTypes.push(specParams.length ? `${mediaType};${specParams.join(';')}` : mediaType);
}
return mediaTypes.reverse();
}
export function isMatchingMediaType(askedMediaType, processorMediaType) {
const [askedPre, askedSuf] = typeAndSubtype(askedMediaType);
const [pre, suf] = typeAndSubtype(processorMediaType);
if ((pre === '*' || pre === askedPre) && (suf === '*' || suf === askedSuf)) {
return true;
}
return false;
}
/**
* Map a single Accept media type to an executor `incrementalSpec` using the
* registered HTTP parameter table. Non-multipart types return `undefined`.
*/
export function negotiateIncrementalSpecFromMediaType(mediaType, specs = INCREMENTAL_DELIVERY_SPECS) {
const [type, subtype] = typeAndSubtype(mediaType);
if (type !== 'multipart' || subtype !== 'mixed') {
return undefined;
}
const lookedUp = lookupIncrementalDeliverySpec(parseMediaTypeParams(mediaType), specs);
if (lookedUp === 'unsupported') {
return 'unsupported';
}
if (lookedUp) {
return {
spec: lookedUp.id,
contentType: getMultipartContentType(lookedUp, { echoSpec: true }),
};
}
const fallback = getDefaultIncrementalDeliverySpec(specs);
return {
spec: fallback.id,
contentType: getMultipartContentType(fallback, { echoSpec: false }),
};
}
/**
* Last matching `multipart/mixed` in the request Accept list (same last-match
* rule as result-processor selection). `undefined` leaves the executor default.
*/
export function getIncrementalSpecForRequest(request, specs = INCREMENTAL_DELIVERY_SPECS) {
let spec;
for (const mediaType of getMediaTypesForRequestInOrder(request, specs)) {
const negotiated = negotiateIncrementalSpecFromMediaType(mediaType, specs);
if (negotiated && negotiated !== 'unsupported') {
spec = negotiated.spec;
}
}
return spec;
}