UNPKG

@azure/search-documents

Version:
200 lines 6.81 kB
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. // --------------------- // helpers // --------------------- function encodeComponent(val, reserved, op) { return (reserved ?? op === "+") || op === "#" ? encodeReservedComponent(val) : encodeRFC3986URIComponent(val); } function encodeReservedComponent(str) { return str .split(/(%[0-9A-Fa-f]{2})/g) .map((part) => (!/%[0-9A-Fa-f]/.test(part) ? encodeURI(part) : part)) .join(""); } function encodeRFC3986URIComponent(str) { return encodeURIComponent(str).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`); } function isDefined(val) { return val !== undefined && val !== null; } function getNamedAndIfEmpty(op) { return [!!op && [";", "?", "&"].includes(op), !!op && ["?", "&"].includes(op) ? "=" : ""]; } function getFirstOrSep(op, isFirst = false) { if (isFirst) { return !op || op === "+" ? "" : op; } else if (!op || op === "+" || op === "#") { return ","; } else if (op === "?") { return "&"; } else { return op; } } function getExpandedValue(option) { let isFirst = option.isFirst; const { op, varName, varValue: value, reserved } = option; const vals = []; const [named, ifEmpty] = getNamedAndIfEmpty(op); if (Array.isArray(value)) { for (const val of value.filter(isDefined)) { // prepare the following parts: separator, varName, value vals.push(`${getFirstOrSep(op, isFirst)}`); if (named && varName) { vals.push(`${encodeURIComponent(varName)}`); if (val === "") { vals.push(ifEmpty); } else { vals.push("="); } } vals.push(encodeComponent(val, reserved, op)); isFirst = false; } } else if (typeof value === "object") { for (const key of Object.keys(value)) { const val = value[key]; if (!isDefined(val)) { continue; } // prepare the following parts: separator, key, value vals.push(`${getFirstOrSep(op, isFirst)}`); if (key) { vals.push(`${encodeURIComponent(key)}`); if (named && val === "") { vals.push(ifEmpty); } else { vals.push("="); } } vals.push(encodeComponent(val, reserved, op)); isFirst = false; } } return vals.join(""); } function getNonExpandedValue(option) { const { op, varName, varValue: value, isFirst, reserved } = option; const vals = []; const first = getFirstOrSep(op, isFirst); const [named, ifEmpty] = getNamedAndIfEmpty(op); if (named && varName) { vals.push(encodeComponent(varName, reserved, op)); if (value === "") { if (!ifEmpty) { vals.push(ifEmpty); } return !vals.join("") ? undefined : `${first}${vals.join("")}`; } vals.push("="); } const items = []; if (Array.isArray(value)) { for (const val of value.filter(isDefined)) { items.push(encodeComponent(val, reserved, op)); } } else if (typeof value === "object") { for (const key of Object.keys(value)) { if (!isDefined(value[key])) { continue; } items.push(encodeRFC3986URIComponent(key)); items.push(encodeComponent(value[key], reserved, op)); } } vals.push(items.join(",")); return !vals.join(",") ? undefined : `${first}${vals.join("")}`; } function getVarValue(option) { const { op, varName, modifier, isFirst, reserved, varValue: value } = option; if (!isDefined(value)) { return undefined; } else if (["string", "number", "boolean"].includes(typeof value)) { let val = value.toString(); const [named, ifEmpty] = getNamedAndIfEmpty(op); const vals = [getFirstOrSep(op, isFirst)]; if (named && varName) { // No need to encode varName considering it is already encoded vals.push(varName); if (val === "") { vals.push(ifEmpty); } else { vals.push("="); } } if (modifier && modifier !== "*") { val = val.substring(0, parseInt(modifier, 10)); } vals.push(encodeComponent(val, reserved, op)); return vals.join(""); } else if (modifier === "*") { return getExpandedValue(option); } else { return getNonExpandedValue(option); } } // --------------------------------------------------------------------------------------------------- // This is an implementation of RFC 6570 URI Template: https://datatracker.ietf.org/doc/html/rfc6570. // --------------------------------------------------------------------------------------------------- export function expandUrlTemplate(template, context, option) { const result = template.replace(/\{([^{}]+)\}|([^{}]+)/g, (_, expr, text) => { if (!expr) { return encodeReservedComponent(text); } let op; if (["+", "#", ".", "/", ";", "?", "&"].includes(expr[0])) { op = expr[0]; expr = expr.slice(1); } const varList = expr.split(/,/g); const innerResult = []; for (const varSpec of varList) { const varMatch = /([^:*]*)(?::(\d+)|(\*))?/.exec(varSpec); if (!varMatch || !varMatch[1]) { continue; } const varValue = getVarValue({ isFirst: innerResult.length === 0, op, varValue: context[varMatch[1]], varName: varMatch[1], modifier: varMatch[2] || varMatch[3], reserved: option?.allowReserved, }); if (varValue) { innerResult.push(varValue); } } return innerResult.join(""); }); return normalizeUnreserved(result); } /** * Normalize an expanded URI by decoding percent-encoded unreserved characters. * RFC 3986 unreserved: "-" / "." / "~" */ function normalizeUnreserved(uri) { return uri.replace(/%([0-9A-Fa-f]{2})/g, (match, hex) => { const char = String.fromCharCode(parseInt(hex, 16)); // Decode only if it's unreserved if (/[.~-]/.test(char)) { return char; } return match; // leave other encodings intact }); } //# sourceMappingURL=urlTemplate.js.map