in src/parse.ts [113:259]
function parseCommand(source: string, options: ParseOptions) {
source = source
// removes comments tags, such as `<1>`
.replace(/<([\S\s])>/g, "")
// removes comments, such as `// optional`
.replace(/\s*\/\/\s.+/g, "")
// trimp whitespace
.trim();
const data: ParsedRequest = {
source: source,
service: "es",
params: {},
method: "",
url: "",
path: "",
rawPath: "",
};
const len = source.length;
let index = 0;
// identify the method
for (const method of httpMethods) {
if (source.slice(index, len).startsWith(method)) {
data.method = method;
index += method.length;
break;
}
}
/* istanbul ignore if */
if (!data.method) {
if (options?.ignoreErrors) {
return data;
}
throw new Error("Invalid request method");
}
// identify the url and query
skip(" ", "\n");
const urlStart = index;
until("{", "\n");
if (source[index] == "{" && source[index + 1].match(/[a-z]/i)) {
// this is a placeholder element inside the URL (as used in doc examples),
// so we continue scanning
index++;
until("{", "\n");
}
data.url = source.slice(urlStart, index).trim();
if (data.url.indexOf(":/") >= 0) {
[data.service, data.url] = data.url.split(":/", 2);
data.url = "/" + data.url;
}
if (data.url[0] != "/") {
data.url = "/" + data.url;
}
data.url = data.url
// replaces { with %7B (braces in many doc examples are not URIencoded)
.replace(/{/g, "%7B")
// replaces } with %7D
.replace(/}/g, "%7D");
const parsedUrl = new URL(`http://localhost${data.url}`);
data.rawPath =
parsedUrl.pathname != "/"
? parsedUrl.pathname.replace(/\/$/, "")
: parsedUrl.pathname;
data.path = decodeURIComponent(data.rawPath);
if (parsedUrl.search.length) {
const parsedQuery = new URLSearchParams(parsedUrl.search.slice(1));
data.query = {};
for (const [key, value] of parsedQuery) {
data.query[key] = value || "true";
}
}
// TODO: this should be an issue in the docs,
// the correct url is `<index/_mapping`
if (data.path.endsWith("_mappings")) {
data.path = data.path.slice(0, -1);
data.rawPath = data.rawPath.slice(0, -1);
data.url = data.url.replace(data.path + "s", data.path);
}
// identify the body
const body = removeTrailingCommas(
collapseLiteralStrings(source.slice(index)),
);
if (body != "") {
try {
// json body
data.body = JSON.parse(body) as JSONObject;
} catch (err) {
try {
// ndjson body
const ndbody = body.split("\n").filter(Boolean) as string[];
data.body = ndbody.map((b) => JSON.parse(b));
} catch (err) {
if (options?.ignoreErrors) {
data.body = body;
} else {
throw new Error("body cannot be parsed");
}
}
}
}
return data;
// some commands have three double quotes `"""` in the body
// this utility removes them and makes the string a valid json
function collapseLiteralStrings(data: string) {
const splitData = data.split('"""');
for (let idx = 1; idx < splitData.length - 1; idx += 2) {
splitData[idx] = JSON.stringify(splitData[idx]);
}
return splitData.join("");
}
// remove any trailing commas to prevent JSON parser failures
function removeTrailingCommas(data: string) {
return data.replace(/,([ |\t|\n]+[}|\]|)])/g, "$1");
}
// proceeds until it finds a character not present
// in the list passed as input
function skip(...args: string[]) {
if (index == len) return;
if (!args.includes(source[index])) {
return;
}
index += 1;
skip(...args);
}
// proceeds until it finds a character present
// in the list passed as input
function until(...args: string[]) {
if (index == len) return;
if (args.includes(source[index])) {
return;
}
index += 1;
until(...args);
}
}