Add `addressAppend` (#332)
Consider a case where a user wants to construct many addresses sharing a common prefix. For example, every GitHub entity may have an address starting with the component sequence `["sourcecred", "github"]`. Currently, users can implement this with `toParts`: ```js const GITHUB_ADDRESS = Address.nodeAddress(["sourcecred", "github"]); function createGithubAddress(ids: $ReadOnlyArray<string>): NodeAddress { return nodeAddress([...Address.toParts(GITHUB_ADDRESS), ...ids]); } ``` This is unfortunate from a performance standpoint, as we needlessly perform string and array operations, when under the hood this is basically a string concatenation. This commit fixes this by adding functions `nodeAppend` and `edgeAppend`, which take a node (resp. edge) and some components to append, returning a new address. Consider how straightforward our example case becomes: ```js const GITHUB_NODE_PREFIX = Address.nodeAddress(["sourcecred", "github"]); const ISSUE_NODE_PREFIX = Address.nodeAppend(GITHUB_NODE_PREFIX, "issue"); // There is no longer any need for an explicit creation function const anIssueAddress = Address.nodeAppend(ISSUE_NODE_PREFIX, someIssueID); ``` Paired with @wchargin. Test Plan: Unit tests added; run `yarn travis`.
This commit is contained in:
parent
bd28030caa
commit
e092b32bca
|
@ -86,14 +86,18 @@ export function assertAddressArray(arr: $ReadOnlyArray<string>) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function nullDelimited(components: $ReadOnlyArray<string>): string {
|
||||||
|
return [...components, ""].join(SEPARATOR);
|
||||||
|
}
|
||||||
|
|
||||||
export function nodeAddress(arr: $ReadOnlyArray<string>): NodeAddress {
|
export function nodeAddress(arr: $ReadOnlyArray<string>): NodeAddress {
|
||||||
assertAddressArray(arr);
|
assertAddressArray(arr);
|
||||||
return [NODE_PREFIX, ...arr, ""].join(SEPARATOR);
|
return NODE_PREFIX + SEPARATOR + nullDelimited(arr);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function edgeAddress(arr: $ReadOnlyArray<string>): EdgeAddress {
|
export function edgeAddress(arr: $ReadOnlyArray<string>): EdgeAddress {
|
||||||
assertAddressArray(arr);
|
assertAddressArray(arr);
|
||||||
return [EDGE_PREFIX, ...arr, ""].join(SEPARATOR);
|
return EDGE_PREFIX + SEPARATOR + nullDelimited(arr);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toParts(a: GenericAddress): string[] {
|
export function toParts(a: GenericAddress): string[] {
|
||||||
|
@ -101,3 +105,21 @@ export function toParts(a: GenericAddress): string[] {
|
||||||
const parts = a.split(SEPARATOR);
|
const parts = a.split(SEPARATOR);
|
||||||
return parts.slice(1, parts.length - 1);
|
return parts.slice(1, parts.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function nodeAppend(
|
||||||
|
base: NodeAddress,
|
||||||
|
...components: string[]
|
||||||
|
): NodeAddress {
|
||||||
|
assertNodeAddress(base);
|
||||||
|
assertAddressArray(components);
|
||||||
|
return base + nullDelimited(components);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function edgeAppend(
|
||||||
|
base: EdgeAddress,
|
||||||
|
...components: string[]
|
||||||
|
): EdgeAddress {
|
||||||
|
assertEdgeAddress(base);
|
||||||
|
assertAddressArray(components);
|
||||||
|
return base + nullDelimited(components);
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
|
import type {NodeAddress, EdgeAddress} from "./_address";
|
||||||
import {
|
import {
|
||||||
assertNodeAddress,
|
assertNodeAddress,
|
||||||
assertEdgeAddress,
|
assertEdgeAddress,
|
||||||
nodeAddress,
|
|
||||||
edgeAddress,
|
edgeAddress,
|
||||||
|
edgeAppend,
|
||||||
|
nodeAddress,
|
||||||
|
nodeAppend,
|
||||||
toParts,
|
toParts,
|
||||||
} from "./_address";
|
} from "./_address";
|
||||||
|
|
||||||
|
@ -79,6 +82,68 @@ describe("core/address", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function checkAppend<
|
||||||
|
Good: NodeAddress | EdgeAddress,
|
||||||
|
Bad: NodeAddress | EdgeAddress
|
||||||
|
>(
|
||||||
|
f: (Good, ...string[]) => Good,
|
||||||
|
goodConstructor: (string[]) => Good,
|
||||||
|
badConstructor: (string[]) => Bad
|
||||||
|
) {
|
||||||
|
describe(f.name, () => {
|
||||||
|
describe("errors on", () => {
|
||||||
|
[null, undefined].forEach((bad) => {
|
||||||
|
it(`${String(bad)} base input`, () => {
|
||||||
|
// $ExpectFlowError
|
||||||
|
expect(() => f(bad, "foo")).toThrow(String(bad));
|
||||||
|
});
|
||||||
|
it(`${String(bad)} component`, () => {
|
||||||
|
// $ExpectFlowError
|
||||||
|
expect(() => f(goodConstructor(["foo"]), bad)).toThrow(String(bad));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("malformed base", () => {
|
||||||
|
// $ExpectFlowError
|
||||||
|
expect(() => f("foo", "foo")).toThrow("Bad address");
|
||||||
|
});
|
||||||
|
it("base of wrong kind", () => {
|
||||||
|
// $ExpectFlowError
|
||||||
|
expect(() => f(badConstructor(["foo"]), "foo")).toThrow(
|
||||||
|
/Expected.*Address/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("invalid component", () => {
|
||||||
|
expect(() => f(goodConstructor(["foo"]), "foo\0oo"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("works on", () => {
|
||||||
|
function check(
|
||||||
|
description: string,
|
||||||
|
baseComponents: string[],
|
||||||
|
...components: string[]
|
||||||
|
) {
|
||||||
|
test(description, () => {
|
||||||
|
const base = goodConstructor(baseComponents);
|
||||||
|
const expectedParts = [...baseComponents, ...components];
|
||||||
|
expect(toParts(f(base, ...components))).toEqual(expectedParts);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
check("the base address with no extra component", []);
|
||||||
|
check("the base address with empty component", [], "");
|
||||||
|
check("the base address with nonempty component", [], "a");
|
||||||
|
check("the base address with lots of components", [], "a", "b");
|
||||||
|
|
||||||
|
check("a longer address with no extra component", ["a", ""]);
|
||||||
|
check("a longer address with empty component", ["a", ""], "");
|
||||||
|
check("a longer address with nonempty component", ["a", ""], "b");
|
||||||
|
check("a longer address with lots of components", ["a", ""], "b", "c");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
checkAppend(nodeAppend, nodeAddress, edgeAddress);
|
||||||
|
checkAppend(edgeAppend, edgeAddress, nodeAddress);
|
||||||
|
|
||||||
describe("type assertions", () => {
|
describe("type assertions", () => {
|
||||||
function checkAssertion(f, good, bad, badMsg) {
|
function checkAssertion(f, good, bad, badMsg) {
|
||||||
describe(f.name, () => {
|
describe(f.name, () => {
|
||||||
|
|
Loading…
Reference in New Issue