Remove legacy V3 `NodeAddress`/`EdgeAddress` (#362)
Summary: These are superseded by the unified implementation in `address.js`. Test Plan: Existing unit tests suffice. wchargin-branch: remove-legacy-address
This commit is contained in:
parent
70c1f64854
commit
abb44883cd
|
@ -1,172 +0,0 @@
|
|||
// @flow
|
||||
// This module implements Address functionality for the Graph module
|
||||
// This module should not be directly imported by clients; rather, all public
|
||||
// parts of this module are re-exported via Graph
|
||||
|
||||
import stringify from "json-stable-stringify";
|
||||
|
||||
export opaque type NodeAddress: string = string;
|
||||
export opaque type EdgeAddress: string = string;
|
||||
type GenericAddress = NodeAddress | EdgeAddress;
|
||||
|
||||
const NODE_PREFIX = "N";
|
||||
const EDGE_PREFIX = "E";
|
||||
const NODE_CODE_POINT = NODE_PREFIX.charCodeAt(0);
|
||||
const EDGE_CODE_POINT = EDGE_PREFIX.charCodeAt(0);
|
||||
const SEPARATOR = "\0";
|
||||
|
||||
const NODE: "NODE" = "NODE";
|
||||
const EDGE: "EDGE" = "EDGE";
|
||||
type AddressType = typeof NODE | typeof EDGE;
|
||||
|
||||
function addressType(x: string): ?AddressType {
|
||||
if (x != null && x.endsWith(SEPARATOR)) {
|
||||
if (x.charCodeAt(0) === NODE_CODE_POINT && x.charCodeAt(1) === 0) {
|
||||
return NODE;
|
||||
}
|
||||
if (x.charCodeAt(0) === EDGE_CODE_POINT && x.charCodeAt(1) === 0) {
|
||||
return EDGE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function assertNodeAddress(x: NodeAddress, what: string = "node") {
|
||||
const type = addressType(x);
|
||||
switch (type) {
|
||||
case "NODE":
|
||||
return;
|
||||
case "EDGE":
|
||||
throw new Error(
|
||||
`${what}: expected NodeAddress, got EdgeAddress: ${stringify(x)}`
|
||||
);
|
||||
case null:
|
||||
case undefined:
|
||||
throw new Error(`${what}: bad address: ${stringify(x)}`);
|
||||
default:
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
(type: empty);
|
||||
throw new Error(`${what}: invariant violation: ${stringify(x)}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function assertEdgeAddress(x: EdgeAddress, what: string = "edge") {
|
||||
const type = addressType(x);
|
||||
switch (type) {
|
||||
case "NODE":
|
||||
throw new Error(
|
||||
`${what}: expected EdgeAddress, got NodeAddress: ${stringify(x)}`
|
||||
);
|
||||
case "EDGE":
|
||||
return;
|
||||
case null:
|
||||
case undefined:
|
||||
throw new Error(`${what}: bad address: ${stringify(x)}`);
|
||||
default:
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
(type: empty);
|
||||
throw new Error(`${what}: invariant violation: ${stringify(x)}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function assertAddress(x: GenericAddress, what: string = "address") {
|
||||
if (addressType(x) == null) {
|
||||
throw new Error(
|
||||
`${what}: expected NodeAddress or EdgeAddress, got: ${stringify(x)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function assertAddressArray(
|
||||
arr: $ReadOnlyArray<string>,
|
||||
what: string = "address array"
|
||||
) {
|
||||
if (arr == null) {
|
||||
throw new Error(String(arr));
|
||||
}
|
||||
arr.forEach((s: string) => {
|
||||
if (s == null) {
|
||||
throw new Error(`${what}: ${String(s)} in ${stringify(arr)}`);
|
||||
}
|
||||
if (s.indexOf(SEPARATOR) !== -1) {
|
||||
throw new Error(`${what}: NUL char: ${stringify(arr)}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function nullDelimited(components: $ReadOnlyArray<string>): string {
|
||||
return [...components, ""].join(SEPARATOR);
|
||||
}
|
||||
|
||||
export function nodeAddress(arr: $ReadOnlyArray<string>): NodeAddress {
|
||||
assertAddressArray(arr);
|
||||
return NODE_PREFIX + SEPARATOR + nullDelimited(arr);
|
||||
}
|
||||
|
||||
export function edgeAddress(arr: $ReadOnlyArray<string>): EdgeAddress {
|
||||
assertAddressArray(arr);
|
||||
return EDGE_PREFIX + SEPARATOR + nullDelimited(arr);
|
||||
}
|
||||
|
||||
export function toParts(a: GenericAddress): string[] {
|
||||
assertAddress(a);
|
||||
const parts = a.split(SEPARATOR);
|
||||
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);
|
||||
}
|
||||
|
||||
export function nodeToString(a: NodeAddress): string {
|
||||
assertNodeAddress(a);
|
||||
const parts = toParts(a);
|
||||
return `nodeAddress(${stringify(parts)})`;
|
||||
}
|
||||
|
||||
export function edgeToString(a: EdgeAddress): string {
|
||||
assertEdgeAddress(a);
|
||||
const parts = toParts(a);
|
||||
return `edgeAddress(${stringify(parts)})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the parts of `prefix` form a prefix of the parts of
|
||||
* `address`. That is, determine whether there exists an `i` such that
|
||||
* `toParts(prefix)` equals `toParts(address).slice(0, i)`.
|
||||
*/
|
||||
export function nodeHasPrefix(
|
||||
address: NodeAddress,
|
||||
prefix: NodeAddress
|
||||
): boolean {
|
||||
assertNodeAddress(address);
|
||||
assertNodeAddress(prefix);
|
||||
return address.startsWith(prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the parts of `prefix` form a prefix of the parts of
|
||||
* `address`. That is, determine whether there exists an `i` such that
|
||||
* `toParts(prefix)` equals `toParts(address).slice(0, i)`.
|
||||
*/
|
||||
export function edgeHasPrefix(
|
||||
address: EdgeAddress,
|
||||
prefix: EdgeAddress
|
||||
): boolean {
|
||||
assertEdgeAddress(address);
|
||||
assertEdgeAddress(prefix);
|
||||
return address.startsWith(prefix);
|
||||
}
|
|
@ -1,340 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import type {NodeAddress, EdgeAddress} from "./_address";
|
||||
import {
|
||||
assertNodeAddress,
|
||||
assertEdgeAddress,
|
||||
edgeAddress,
|
||||
edgeAppend,
|
||||
edgeHasPrefix,
|
||||
edgeToString,
|
||||
nodeAddress,
|
||||
nodeAppend,
|
||||
nodeHasPrefix,
|
||||
nodeToString,
|
||||
toParts,
|
||||
} from "./_address";
|
||||
|
||||
describe("core/address", () => {
|
||||
function throwOnNullOrUndefined(f) {
|
||||
[null, undefined].forEach((bad) => {
|
||||
it(`${f.name} throws on ${String(bad)}`, () => {
|
||||
// $ExpectFlowError
|
||||
expect(() => f(bad)).toThrow(String(bad));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function checkAddressFactory(f) {
|
||||
describe(f.name, () => {
|
||||
throwOnNullOrUndefined(f);
|
||||
[null, undefined].forEach((bad) => {
|
||||
it(`throws on parts containing ${String(bad)}`, () => {
|
||||
// $ExpectFlowError
|
||||
expect(() => f(["foo", bad])).toThrow(String(bad));
|
||||
});
|
||||
});
|
||||
describe("composes to identity with toParts", () => {
|
||||
function checkIdentity(name, example) {
|
||||
it(name, () => {
|
||||
expect(toParts(f(example))).toEqual(example);
|
||||
});
|
||||
}
|
||||
checkIdentity("on a simple example", ["an", "example"]);
|
||||
describe("with an empty component", () => {
|
||||
checkIdentity("at the start", ["", "example"]);
|
||||
checkIdentity("in the middle", ["example", "", "foo"]);
|
||||
checkIdentity("at the end", ["example", "", "foo", ""]);
|
||||
});
|
||||
checkIdentity("with an empty array", []);
|
||||
});
|
||||
});
|
||||
}
|
||||
checkAddressFactory(nodeAddress);
|
||||
checkAddressFactory(edgeAddress);
|
||||
|
||||
describe("toParts", () => {
|
||||
throwOnNullOrUndefined(toParts);
|
||||
it("throws on malformed address", () => {
|
||||
// $ExpectFlowError
|
||||
expect(() => toParts("zookomoobo")).toThrow(/expected .*Address/);
|
||||
});
|
||||
it("throws on fake (slash-separated) node address", () => {
|
||||
// $ExpectFlowError
|
||||
expect(() => toParts("N/bad/stuff\0")).toThrow();
|
||||
});
|
||||
it("throws on fake (slash-separated) edge address", () => {
|
||||
// $ExpectFlowError
|
||||
expect(() => toParts("E/bad/stuff\0")).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("node and edge addresses are distinct", () => {
|
||||
it("at a type level", () => {
|
||||
// $ExpectFlowError
|
||||
const _unused_edgeAddress: EdgeAddress = nodeAddress([]);
|
||||
// $ExpectFlowError
|
||||
const _unused_nodeAddress: NodeAddress = edgeAddress([]);
|
||||
});
|
||||
describe("at a value level", () => {
|
||||
it("base address", () => {
|
||||
expect(nodeAddress([])).not.toEqual(edgeAddress([]));
|
||||
});
|
||||
it("normal address", () => {
|
||||
expect(nodeAddress(["foo"])).not.toEqual(edgeAddress(["foo"]));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
function checkToString<
|
||||
Good: NodeAddress | EdgeAddress,
|
||||
Bad: NodeAddress | EdgeAddress
|
||||
>(
|
||||
f: (Good) => string,
|
||||
kind: "NodeAddress" | "EdgeAddress",
|
||||
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)).toThrow(String(bad));
|
||||
});
|
||||
});
|
||||
it("wrong kind", () => {
|
||||
// $ExpectFlowError
|
||||
expect(() => f(badConstructor(["foo"]))).toThrow(`expected ${kind}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("works on", () => {
|
||||
const camelKind = kind.charAt(0).toLowerCase() + kind.substring(1);
|
||||
test("the empty address", () => {
|
||||
expect(f(goodConstructor([]))).toEqual(`${camelKind}([])`);
|
||||
});
|
||||
test("the address with one empty component", () => {
|
||||
expect(f(goodConstructor([""]))).toEqual(`${camelKind}([""])`);
|
||||
});
|
||||
test("a normal address", () => {
|
||||
expect(f(goodConstructor(["one", "", "two"]))).toEqual(
|
||||
`${camelKind}(["one","","two"])`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
checkToString(nodeToString, "NodeAddress", nodeAddress, edgeAddress);
|
||||
checkToString(edgeToString, "EdgeAddress", edgeAddress, nodeAddress);
|
||||
|
||||
function checkHasPrefix<
|
||||
Good: NodeAddress | EdgeAddress,
|
||||
Bad: NodeAddress | EdgeAddress
|
||||
>(
|
||||
hasPrefix: (Good, Good) => boolean,
|
||||
kind: "NodeAddress" | "EdgeAddress",
|
||||
goodConstructor: (string[]) => Good,
|
||||
badConstructor: (string[]) => Bad
|
||||
) {
|
||||
describe(hasPrefix.name, () => {
|
||||
describe("errors on", () => {
|
||||
[null, undefined].forEach((bad) => {
|
||||
it(`${String(bad)} base input`, () => {
|
||||
// $ExpectFlowError
|
||||
expect(() => hasPrefix(bad)).toThrow(String(bad));
|
||||
});
|
||||
});
|
||||
it("wrong kind", () => {
|
||||
// $ExpectFlowError
|
||||
expect(() => hasPrefix(badConstructor(["foo"]))).toThrow(
|
||||
`expected ${kind}`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const address = goodConstructor;
|
||||
it("accepts the empty prefix of non-empty input", () => {
|
||||
expect(hasPrefix(address(["foo", "bar"]), address([]))).toBe(true);
|
||||
});
|
||||
it("accepts the empty prefix of empty input", () => {
|
||||
expect(hasPrefix(address([]), address([]))).toBe(true);
|
||||
});
|
||||
it("rejects a non-empty prefix of empty input", () => {
|
||||
expect(hasPrefix(address([]), address(["foo", "bar"]))).toBe(false);
|
||||
});
|
||||
it("accepts a normal input", () => {
|
||||
expect(
|
||||
hasPrefix(address(["foo", "bar", "baz"]), address(["foo", "bar"]))
|
||||
).toBe(true);
|
||||
});
|
||||
it("accepts that an address is a prefix of itself", () => {
|
||||
expect(
|
||||
hasPrefix(address(["foo", "bar"]), address(["foo", "bar"]))
|
||||
).toBe(true);
|
||||
});
|
||||
it("accepts inputs with empty components", () => {
|
||||
expect(
|
||||
hasPrefix(
|
||||
address(["foo", "", "bar", "", "baz"]),
|
||||
address(["foo", "", "bar", ""])
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
it("rejects inputs with no nontrivial common prefix", () => {
|
||||
expect(
|
||||
hasPrefix(address(["foo", "bar", "baz"]), address(["bar", "foo"]))
|
||||
).toBe(false);
|
||||
});
|
||||
it("rejects inputs with insufficiently long common prefix", () => {
|
||||
expect(
|
||||
hasPrefix(address(["foo", "bar", "baz"]), address(["foo", "quux"]))
|
||||
).toBe(false);
|
||||
});
|
||||
it("rejects when the putative prefix is a proper infix", () => {
|
||||
expect(
|
||||
hasPrefix(address(["foo", "bar", "baz"]), address(["bar"]))
|
||||
).toBe(false);
|
||||
});
|
||||
it("rejects when the putative prefix is a proper suffix", () => {
|
||||
expect(
|
||||
hasPrefix(address(["foo", "bar", "baz"]), address(["bar", "baz"]))
|
||||
).toBe(false);
|
||||
});
|
||||
it("rejects when the arguments are reversed", () => {
|
||||
expect(
|
||||
hasPrefix(address(["foo", "bar"]), address(["foo", "bar", "baz"]))
|
||||
).toBe(false);
|
||||
});
|
||||
it("rejects when the last component is truncated", () => {
|
||||
expect(
|
||||
hasPrefix(address(["foo", "bar", "baz"]), address(["foo", "ba"]))
|
||||
).toBe(false);
|
||||
});
|
||||
it("rejects when two components have been concatenated", () => {
|
||||
expect(
|
||||
hasPrefix(address(["foo", "bar", "baz"]), address(["foobar", "baz"]))
|
||||
).toBe(false);
|
||||
});
|
||||
it("rejects an extra empty component in the middle of the base", () => {
|
||||
expect(
|
||||
hasPrefix(address(["foo", "", "baz"]), address(["foo", "baz"]))
|
||||
).toBe(false);
|
||||
});
|
||||
it("rejects an extra empty component in the middle of the prefix", () => {
|
||||
expect(
|
||||
hasPrefix(address(["foo", "baz"]), address(["foo", "", "baz"]))
|
||||
).toBe(false);
|
||||
});
|
||||
it("rejects an extra empty component at the end of the prefix", () => {
|
||||
expect(
|
||||
hasPrefix(address(["foo", "baz"]), address(["foo", "baz", ""]))
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
checkHasPrefix(nodeHasPrefix, "NodeAddress", nodeAddress, edgeAddress);
|
||||
checkHasPrefix(edgeHasPrefix, "EdgeAddress", edgeAddress, nodeAddress);
|
||||
|
||||
describe("type assertions", () => {
|
||||
function checkAssertion(f, good, bad, badMsg) {
|
||||
describe(f.name, () => {
|
||||
it("does not error on the right type of address", () => {
|
||||
// Technically, the below invocation isn't an error; but no need to
|
||||
// persuade Flow of this, as we already check that Node/Edge
|
||||
// addresses are handled correctly by flow in a different test case.
|
||||
f((good: any));
|
||||
});
|
||||
throwOnNullOrUndefined(f);
|
||||
it("errors on the wrong type of address", () => {
|
||||
// $ExpectFlowError
|
||||
expect(() => f(bad)).toThrow(badMsg);
|
||||
});
|
||||
it("errors on non-address", () => {
|
||||
it("errors on the wrong type of address with a custom message", () => {
|
||||
// $ExpectFlowError
|
||||
expect(() => f(bad, "widget")).toThrow(
|
||||
new RegExp("widget:.*" + badMsg)
|
||||
);
|
||||
});
|
||||
// $ExpectFlowError
|
||||
expect(() => f("foomulous")).toThrow("bad address:");
|
||||
});
|
||||
});
|
||||
}
|
||||
checkAssertion(
|
||||
assertNodeAddress,
|
||||
nodeAddress([]),
|
||||
edgeAddress([]),
|
||||
"got EdgeAddress"
|
||||
);
|
||||
checkAssertion(
|
||||
assertEdgeAddress,
|
||||
edgeAddress([]),
|
||||
nodeAddress([]),
|
||||
"got NodeAddress"
|
||||
);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue