UNPKG

nested-query-params

Version:
139 lines 4.65 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseQueryParamKey = exports.parseQuery = void 0; const util_1 = require("./util"); const types_1 = require("./types"); const logger = prepareConsole(console, "nested-query-params"); /** * Expands a search params into structural types. Supported types are Arrays, * Objects and basic value types. Heavily inspired by * {@link https://github.com/rack/rack/blob/bad8fe37c8867596855dcd0b3fe3030acc6b8621/lib/rack/query_parser.rb#L63-L68|Rack's nested query parser}. * * @example * // returns { one: { two: "3" } }; * parseQuery("?one[two]=3") */ function parseQuery(query) { const pairs = (0, util_1.splitQuery)(query); return pairs.reduce((acc, [key, value]) => { const comps = parseQueryParamKey(key); if (Array.isArray(value)) { return value.reduce((acc, value) => applyNestedParam(acc, comps, value), acc); } return applyNestedParam(acc, comps, value); }, {}); } exports.parseQuery = parseQuery; /** * Parses nested search parameters keys into their individual path components. */ function parseQueryParamKey(key) { if (key.trim() === "") { return []; } let start; if ((start = key.indexOf("[", 1)) === -1) { return [[types_1.QueryParamPathComponentKind.Map, key]]; } const head = key.slice(0, start); const rest = key.slice(start); return [ [types_1.QueryParamPathComponentKind.Map, head], ...parseQueryParamKeyComponents(rest), ]; } exports.parseQueryParamKey = parseQueryParamKey; /** * Applies given key value pair to search parameters. */ function applyNestedParam(params, comps, value) { const [head] = comps; if (!head) { return params; } if (head[0] !== types_1.QueryParamPathComponentKind.Map) { logger.warn("#applyNestedParam", "cannot apply list op to root"); return params; } return applyComponent(params, comps, value); } function applyComponent(params, comps, value) { const log = prepareConsole(logger, "#applyComponent"); const [head, ...rest] = comps; if (!head) { return params; } if (head[0] === types_1.QueryParamPathComponentKind.Map) { if (typeof params !== "object" || Array.isArray(params)) { params = {}; } if (rest.length === 0) { return { ...params, [head[1]]: Array.isArray(value) ? value[value.length - 1] : value, }; } const key = head[1]; return { ...params, [head[1]]: applyComponent(params[key], rest, value), }; } if (head[0] === types_1.QueryParamPathComponentKind.List) { if (!Array.isArray(params)) { params = []; } if (rest.length === 0) { return [ ...params, ...(Array.isArray(value) ? value : [value]), ]; } const [peek] = rest; if (peek[0] !== types_1.QueryParamPathComponentKind.Map) { log.warn("expected map"); return params; } const last = params[params.length - 1] || undefined; if (typeof last === "object" && !Array.isArray(last) && !last[peek[1]]) { const next = applyNestedParam(last, rest, value); return [...params.slice(0, -1), next]; } else if (Array.isArray(last)) { log.warn("nested arrays are not supported"); } else if (last && typeof last !== "object") { log.warn("expected map"); } // add value to list if list was previously empty or the given key is // already in the map. const next = applyNestedParam({}, rest, value); return [...params, next]; } return params; } function parseQueryParamKeyComponents(key) { // list if (key.startsWith("[]")) { return [ [types_1.QueryParamPathComponentKind.List], ...parseQueryParamKeyComponents(key.slice(2)), ]; } // map let start; if (key.startsWith("[") && (start = key.indexOf("]", 1)) !== -1) { return [ [types_1.QueryParamPathComponentKind.Map, key.slice(1, start)], ...parseQueryParamKeyComponents(key.slice(start + 1)), ]; } return []; } function prepareConsole(parent, ...init) { return ["info", "error", "warn", "debug"].reduce((acc, method) => ({ [method]: (...msgs) => parent[method](...init, ...msgs), ...acc, }), {}); } //# sourceMappingURL=parse.js.map