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:
William Chargin 2018-06-07 09:35:18 -07:00 committed by GitHub
parent 70c1f64854
commit abb44883cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 0 additions and 512 deletions

View File

@ -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);
}

View File

@ -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"
);
});
});