combo: add `dict` combinator (#1826)
Summary: This is for homogeneous object types with unbounded key sets: roughly, `dict` is to `object` as `array` is to `tuple`. Its implementation requires no terrifying type magic whatsoever. Test Plan: Unit tests included, retaining full coverage. wchargin-branch: combo-dict
This commit is contained in:
parent
205f6e064c
commit
500140d292
|
@ -314,3 +314,31 @@ export function tuple<T: Iterable<Parser<mixed>>>(
|
|||
return success(result);
|
||||
});
|
||||
}
|
||||
|
||||
// Create a parser for objects with arbitrary string keys and
|
||||
// homogeneous values. For instance, a set of package versions:
|
||||
//
|
||||
// {"better-sqlite3": "^7.0.0", "react": "^16.13.0"}
|
||||
//
|
||||
// might be parsed by the following parser:
|
||||
//
|
||||
// C.dict(C.fmap(C.string, (s) => SemVer.parse(s)))
|
||||
//
|
||||
// Objects may have any number of entries, including zero.
|
||||
export function dict<V>(valueParser: Parser<V>): Parser<{|[string]: V|}> {
|
||||
return new Parser((x) => {
|
||||
if (typeof x !== "object" || Array.isArray(x) || x == null) {
|
||||
return failure("expected object, got " + typename(x));
|
||||
}
|
||||
const result: {|[string]: V|} = ({}: any);
|
||||
for (const key of Object.keys(x)) {
|
||||
const raw = x[key];
|
||||
const parsed = valueParser.parse(raw);
|
||||
if (!parsed.ok) {
|
||||
return failure(`key ${JSON.stringify(key)}: ${parsed.err}`);
|
||||
}
|
||||
result[key] = parsed.value;
|
||||
}
|
||||
return success(result);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -439,4 +439,36 @@ describe("src/util/combo", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("dict", () => {
|
||||
const makeParser = (): C.Parser<{|[string]: number|}> => C.dict(C.number);
|
||||
it("rejects null", () => {
|
||||
const p = makeParser();
|
||||
const thunk = () => p.parseOrThrow(null);
|
||||
expect(thunk).toThrow("expected object, got null");
|
||||
});
|
||||
it("rejects arrays", () => {
|
||||
const p = makeParser();
|
||||
const thunk = () => p.parseOrThrow([1, 2, 3]);
|
||||
expect(thunk).toThrow("expected object, got array");
|
||||
});
|
||||
it("accepts an empty object", () => {
|
||||
const p = makeParser();
|
||||
expect(p.parseOrThrow({})).toEqual({});
|
||||
});
|
||||
it("accepts an object with one entries", () => {
|
||||
const p = makeParser();
|
||||
expect(p.parseOrThrow({one: 1})).toEqual({one: 1});
|
||||
});
|
||||
it("accepts an object with multiple entries", () => {
|
||||
const p = makeParser();
|
||||
const input = {one: 1, two: 2, three: 3};
|
||||
expect(p.parseOrThrow(input)).toEqual({one: 1, two: 2, three: 3});
|
||||
});
|
||||
it("rejects an object with bad values", () => {
|
||||
const p = makeParser();
|
||||
const thunk = () => p.parseOrThrow({one: "two?"});
|
||||
expect(thunk).toThrow('key "one": expected number, got string');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue