lambda-live-debugger
Version:
Debug Lambda functions locally like it is running in the cloud
363 lines (357 loc) • 10.4 kB
JavaScript
const ATTR_ESCAPE_RE = /[&<>"]/g;
const ATTR_ESCAPE_MAP = {
"&": "&",
"<": "<",
">": ">",
'"': """,
};
function escapeAttribute(value) {
return value.replace(ATTR_ESCAPE_RE, (ch) => ATTR_ESCAPE_MAP[ch]);
}
const ELEMENT_ESCAPE_RE = /[&"'<>\r\n\u0085\u2028]/g;
const ELEMENT_ESCAPE_MAP = {
"&": "&",
'"': """,
"'": "'",
"<": "<",
">": ">",
"\r": "
",
"\n": "
",
"\u0085": "…",
"\u2028": "
",
};
function escapeElement(value) {
return value.replace(ELEMENT_ESCAPE_RE, (ch) => ELEMENT_ESCAPE_MAP[ch]);
}
class XmlText {
value;
constructor(value) {
this.value = value;
}
toString() {
return escapeElement("" + this.value);
}
}
class XmlNode {
name;
children;
attributes = {};
static of(name, childText, withName) {
const node = new XmlNode(name);
if (childText !== undefined) {
node.addChildNode(new XmlText(childText));
}
if (withName !== undefined) {
node.withName(withName);
}
return node;
}
constructor(name, children = []) {
this.name = name;
this.children = children;
}
withName(name) {
this.name = name;
return this;
}
addAttribute(name, value) {
this.attributes[name] = value;
return this;
}
addChildNode(child) {
this.children.push(child);
return this;
}
removeAttribute(name) {
delete this.attributes[name];
return this;
}
n(name) {
this.name = name;
return this;
}
c(child) {
this.children.push(child);
return this;
}
a(name, value) {
if (value != null) {
this.attributes[name] = value;
}
return this;
}
cc(input, field, withName = field) {
if (input[field] != null) {
const node = XmlNode.of(field, input[field]).withName(withName);
this.c(node);
}
}
l(input, listName, memberName, valueProvider) {
if (input[listName] != null) {
const nodes = valueProvider();
nodes.map((node) => {
node.withName(memberName);
this.c(node);
});
}
}
lc(input, listName, memberName, valueProvider) {
if (input[listName] != null) {
const nodes = valueProvider();
const containerNode = new XmlNode(memberName);
nodes.map((node) => {
containerNode.c(node);
});
this.c(containerNode);
}
}
toString() {
const hasChildren = Boolean(this.children.length);
let xmlText = `<${this.name}`;
const attributes = this.attributes;
for (const attributeName of Object.keys(attributes)) {
const attribute = attributes[attributeName];
if (attribute != null) {
xmlText += ` ${attributeName}="${escapeAttribute("" + attribute)}"`;
}
}
return (xmlText += !hasChildren ? "/>" : `>${this.children.map((c) => c.toString()).join("")}</${this.name}>`);
}
}
function parseXML(xml) {
const state = new AwsXmlParser(xml);
return state.parse();
}
class AwsXmlParser {
x;
i = 0;
z;
constructor(x) {
this.x = x;
this.x = x.replace(/\r\n?/g, "\n");
this.z = this.x.length;
}
parse() {
const p = this;
const { z } = p;
while (p.i < z) {
p.trim();
if (p.i >= z) {
break;
}
if (p.isNext("<?")) {
p.readTo("?>");
p.trim();
}
else if (p.isNext("<!--")) {
p.readTo("-->");
p.trim();
}
else if (p.isNext("<!DOCTYPE", false)) {
p.skipDoctype();
p.trim();
}
else if (p.x[p.i] === "<") {
const root = p.parseTag();
return { [root.tag]: root.value };
}
else {
throw new Error("@aws-sdk XML parse error: unexpected content.");
}
}
throw new Error("@aws-sdk XML parse error: no root element.");
}
isNext(s, caseSensitive = true) {
const p = this;
if (caseSensitive) {
return p.x.startsWith(s, p.i);
}
return p.x.toLowerCase().startsWith(s.toLowerCase(), p.i);
}
readTo(stop) {
const p = this;
const _i = p.x.indexOf(stop, p.i);
if (_i === -1) {
throw new Error(`@aws-sdk XML parse error: expected "${stop}" not found.`);
}
const result = p.x.slice(p.i, _i);
p.i = _i + stop.length;
return result;
}
trim() {
const p = this;
while (p.i < p.z && " \t\r\n".includes(p.x[p.i])) {
++p.i;
}
}
readAttrValue() {
const p = this;
const quote = p.x[p.i];
++p.i;
let value = "";
while (p.i < p.z && p.x[p.i] !== quote) {
value += p.x[p.i++];
}
++p.i;
return p.decodeEntities(value);
}
parseTag() {
const p = this;
++p.i;
let tag = "";
while (p.i < p.z && !" \t\r\n>/".includes(p.x[p.i])) {
tag += p.x[p.i++];
}
let hasAttrs = false;
const attrs = Object.create(null);
while (p.i < p.z) {
p.trim();
if (">/".includes(p.x[p.i])) {
break;
}
let name = "";
while (p.i < p.z && !"= \t\r\n>/?".includes(p.x[p.i])) {
name += p.x[p.i++];
}
p.trim();
if (p.x[p.i] !== "=") {
break;
}
++p.i;
p.trim();
attrs[name] = p.readAttrValue();
hasAttrs = true;
}
if (p.i >= p.z) {
throw new Error("@aws-sdk XML parse error: unexpected end of input.");
}
if (p.x[p.i] === "/") {
++p.i;
if (p.i >= p.z || p.x[p.i] !== ">") {
throw new Error("@aws-sdk XML parse error: expected > at the end of self-closing tag.");
}
++p.i;
Object.setPrototypeOf(attrs, Object.prototype);
return { tag, value: hasAttrs ? attrs : "" };
}
if (p.x[p.i] !== ">") {
throw new Error("@aws-sdk XML parse error: expected > at the end of opening tag.");
}
++p.i;
const textParts = [];
const childTags = [];
let hasElementChild = false;
while (p.i < p.z) {
if (p.isNext("</")) {
break;
}
if (p.x[p.i] === "<") {
if (p.isNext("<!--")) {
p.readTo("-->");
}
else if (p.isNext("<![CDATA[")) {
p.i += 9;
textParts.push(p.readTo("]]>"));
}
else if (p.isNext("<?")) {
p.readTo("?>");
}
else {
hasElementChild = true;
childTags.push(p.parseTag());
}
}
else {
let text = "";
while (p.i < p.z && p.x[p.i] !== "<") {
text += p.x[p.i++];
}
textParts.push(p.decodeEntities(text));
}
}
if (!p.isNext("</")) {
throw new Error(`@aws-sdk XML parse error: missing closing tag </${tag}>.`);
}
p.i += 2;
const closeTag = p.readTo(">").trim();
if (closeTag !== tag) {
throw new Error(`@aws-sdk XML parse error: mismatched tags <${tag}> and </${closeTag}>.`);
}
if (!hasAttrs && textParts.length === 0 && !hasElementChild) {
return { tag, value: "" };
}
if (!hasAttrs && !hasElementChild) {
const text = textParts.length === 1 ? textParts[0] : textParts.join("");
if (text.trim() === "" && text.includes("\n")) {
return { tag, value: "" };
}
return { tag, value: text };
}
const obj = Object.create(null);
for (const text of textParts) {
if (text.trim() === "" && text.includes("\n")) {
continue;
}
obj["#text"] = "#text" in obj ? obj["#text"] + text : text;
}
for (const child of childTags) {
if (child.tag in obj) {
if (Array.isArray(obj[child.tag])) {
obj[child.tag].push(child.value);
}
else {
obj[child.tag] = [obj[child.tag], child.value];
}
}
else {
obj[child.tag] = child.value;
}
}
for (const [k, v] of Object.entries(attrs)) {
obj[k] = v;
}
Object.setPrototypeOf(obj, Object.prototype);
return { tag, value: obj };
}
static ENTITIES = {
amp: "&",
lt: "<",
gt: ">",
quot: '"',
apos: "'",
};
skipDoctype() {
const p = this;
p.i += 9;
let depth = 0;
while (p.i < p.z) {
const c = p.x[p.i];
if (c === "[") {
++depth;
}
else if (c === "]") {
--depth;
}
else if (c === ">" && depth === 0) {
++p.i;
return;
}
++p.i;
}
throw new Error("@aws-sdk XML parse error: unclosed DOCTYPE.");
}
decodeEntities(s) {
return s.replace(/&(?:#x([0-9a-fA-F]{1,6})|#(\d{1,7})|([a-zA-Z][a-zA-Z0-9]{0,30}));/g, (_, hex, dec, named) => {
if (hex) {
return String.fromCharCode(parseInt(hex, 16));
}
if (dec) {
return String.fromCharCode(parseInt(dec, 10));
}
return AwsXmlParser.ENTITIES[named] ?? "";
});
}
}
exports.XmlNode = XmlNode;
exports.XmlText = XmlText;
exports.parseXML = parseXML;