Skip to content

Commit

Permalink
fix: closes #27
Browse files Browse the repository at this point in the history
  • Loading branch information
izatop committed Mar 17, 2024
1 parent 5a1118c commit c55201a
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 23 deletions.
Expand Up @@ -17,10 +17,9 @@ export const MultipartFormDataTransform = async <T = unknown>(request: IRequest)

const rs = await request.createReadableStream();
const defer = new Defer<void>();
const result: Record<string, any> = {};
const pending: Defer<void>[] = [];

const {parseFieldName, inject} = QueryString;
const qs = new QueryString();

bb
.on("file", (name, file, info) => {
Expand All @@ -37,7 +36,7 @@ export const MultipartFormDataTransform = async <T = unknown>(request: IRequest)
tmpname,
};

inject(parseFieldName(name), value, result);
qs.push(name, value);

const def = new Defer<void>();
pending.push(def);
Expand All @@ -48,7 +47,7 @@ export const MultipartFormDataTransform = async <T = unknown>(request: IRequest)
})
.on("field", (name, value) => {
try {
inject(parseFieldName(name), JSON.parse(value), result);
qs.push(name, JSON.parse(value));
} catch {
// skip
}
Expand All @@ -60,5 +59,5 @@ export const MultipartFormDataTransform = async <T = unknown>(request: IRequest)
await defer;
await Promise.all(pending);

return result as any;
return qs.toObject();
};
27 changes: 23 additions & 4 deletions packages/util/src/qs.ts
@@ -1,15 +1,34 @@
import {Rec} from "@bunt/type";

const isNumeric = (key: string): boolean => !isNaN(+key);

export class QueryString {
public static parseFieldName = (name: string): string[] => {
readonly #value: Rec;

constructor(entries: [field: string, value: unknown][] = []) {
this.#value = Object.create(null);
for (const [field, value] of entries) {
this.push(field, value);
}
}

public parseField(name: string): string[] {
const base = name.replace(/\[.+/, "");

return [base, ...[...name.matchAll(/\[([^\]]*)\]/ig)].map(([, key]) => key)];
};
}

public push(name: string, value: unknown): Rec {
return this.#inject(this.parseField(name), value, this.#value);
}

public toObject(): Rec {
return this.#value;
}

public static inject = ([key, ...paths]: string[], value: unknown, fields: any = {}): any => {
#inject = ([key, ...paths]: string[], value: unknown, fields: Rec = Object.create(null)): Rec => {
if (paths.length > 0) {
fields[key] = this.inject(paths, value, fields[key]);
fields[key] = this.#inject(paths, value, fields[key]);
} else {
fields[key] = value;
}
Expand Down
37 changes: 23 additions & 14 deletions packages/util/test/src/qs/QueryStirng.test.ts
@@ -1,57 +1,66 @@
import {QueryString} from "../../../src";

describe("QueryString", () => {
test("Prevent pollution", () => {
const injectTest = {};
const injectKey = "__proto__[polluted]";

const qs = new QueryString();
qs.push(injectKey, true);

expect(Reflect.has(Reflect.get(injectTest, "__proto__"), "polluted")).toBeFalsy();
});

test("Base", () => {
const qs = new QueryString();
const field = "foo[bar][baz][0]";
const parsed = QueryString.parseFieldName(field);
const parsed = qs.parseField(field);

expect(parsed)
.toEqual(["foo", "bar", "baz", "0"]);

expect(QueryString.inject(parsed, 1))
expect(qs.push(field, 1))
.toEqual({foo: {bar: {baz: [1]}}});
});

test("Array", () => {
const qs = new QueryString();
const map: [string, any][] = [
["foo[0]", 1],
["foo[1]", 2],
["foo[2]", 3],
];

const result = {};
for (const [field, value] of map) {
const paths = QueryString.parseFieldName(field);
QueryString.inject(paths, value, result);
qs.push(field, value);
}

expect(result).toEqual({foo: [1, 2, 3]});
expect(qs.toObject()).toEqual({foo: [1, 2, 3]});
});

test("Nested array", () => {
const qs = new QueryString();
const map: [string, any][] = [
["foo[0][a]", 1],
["foo[0][b]", 2],
["foo[1][c]", 3],
["foo[1][d]", 4],
];

const result = {};
for (const [field, value] of map) {
const paths = QueryString.parseFieldName(field);
QueryString.inject(paths, value, result);
qs.push(field, value);
}

expect(result).toEqual({foo: [{a: 1, b: 2}, {c: 3, d: 4}]});
expect(qs.toObject()).toEqual({foo: [{a: 1, b: 2}, {c: 3, d: 4}]});
});

test.each([
["foo", 1, {foo: 1}],
["foo[bar]", 1, {foo: {bar: 1}}],
["foo[bar][0]", 1, {foo: {bar: [1]}}],
["foo[0][bar]", 1, {foo: [{bar: 1}]}],
])("Variants", (field, value, res) => (
expect(QueryString.inject(QueryString.parseFieldName(field), value))
.toEqual(res)
));
])("Variants", (field, value, res) => {
const qs = new QueryString([[field, value]]);
expect(qs.toObject()).toEqual(res);
});
});

0 comments on commit c55201a

Please sign in to comment.