From abb44883cdc332998c08220de021691d9f162c3a Mon Sep 17 00:00:00 2001 From: William Chargin Date: Thu, 7 Jun 2018 09:35:18 -0700 Subject: [PATCH] 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 --- src/v3/core/_address.js | 172 ------------------ src/v3/core/_address.test.js | 340 ----------------------------------- 2 files changed, 512 deletions(-) delete mode 100644 src/v3/core/_address.js delete mode 100644 src/v3/core/_address.test.js diff --git a/src/v3/core/_address.js b/src/v3/core/_address.js deleted file mode 100644 index b82f6bb..0000000 --- a/src/v3/core/_address.js +++ /dev/null @@ -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, - 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 { - return [...components, ""].join(SEPARATOR); -} - -export function nodeAddress(arr: $ReadOnlyArray): NodeAddress { - assertAddressArray(arr); - return NODE_PREFIX + SEPARATOR + nullDelimited(arr); -} - -export function edgeAddress(arr: $ReadOnlyArray): 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); -} diff --git a/src/v3/core/_address.test.js b/src/v3/core/_address.test.js deleted file mode 100644 index 42603a0..0000000 --- a/src/v3/core/_address.test.js +++ /dev/null @@ -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" - ); - }); -});