This commit implements Node/Edge addresses, and helper functions for generating and manipulating them. Test plan: Unit tests included. Paired with @wchargin
This commit is contained in:
parent
aff4ddd4e3
commit
3acfefb904
|
@ -1,5 +1,6 @@
|
|||
// @flow
|
||||
|
||||
import stringify from "json-stable-stringify";
|
||||
export opaque type NodeAddress = string;
|
||||
export opaque type EdgeAddress = string;
|
||||
export type Edge = {|
|
||||
|
@ -20,22 +21,74 @@ export type NeighborsOptions = {|
|
|||
|
||||
export opaque type GraphJSON = any; // TODO
|
||||
|
||||
export function toNodeAddress(arr: $ReadOnlyArray<string>): NodeAddress {
|
||||
const _ = arr;
|
||||
throw new Error("toNodeAddress");
|
||||
const NODE_PREFIX = "N";
|
||||
const EDGE_PREFIX = "E";
|
||||
const SEPARATOR = "\0";
|
||||
|
||||
function isNodeAddress(x: string): boolean {
|
||||
return x.startsWith(NODE_PREFIX) && x.endsWith(SEPARATOR);
|
||||
}
|
||||
function isEdgeAddress(x: string): boolean {
|
||||
return x.startsWith(EDGE_PREFIX) && x.endsWith(SEPARATOR);
|
||||
}
|
||||
|
||||
function assertNodeAddress(x: NodeAddress) {
|
||||
if (x == null) {
|
||||
throw new Error(String(x));
|
||||
}
|
||||
if (!isNodeAddress(x)) {
|
||||
if (isEdgeAddress(x)) {
|
||||
throw new Error(`Expected NodeAddress, got EdgeAddress: ${x}`);
|
||||
}
|
||||
throw new Error(`Malformed address: ${x}`);
|
||||
}
|
||||
}
|
||||
function assertEdgeAddress(x: EdgeAddress) {
|
||||
if (x == null) {
|
||||
throw new Error(String(x));
|
||||
}
|
||||
if (!isEdgeAddress(x)) {
|
||||
if (isNodeAddress(x)) {
|
||||
throw new Error(`Expected EdgeAddress, got NodeAddress: ${x}`);
|
||||
}
|
||||
throw new Error(`Malformed address: ${x}`);
|
||||
}
|
||||
}
|
||||
|
||||
function assertAddressArray(arr: $ReadOnlyArray<string>) {
|
||||
if (arr == null) {
|
||||
throw new Error(String(arr));
|
||||
}
|
||||
arr.forEach((s: string) => {
|
||||
if (s == null) {
|
||||
throw new Error(`${String(s)} in ${stringify(arr)}`);
|
||||
}
|
||||
if (s.indexOf(SEPARATOR) !== -1) {
|
||||
throw new Error(`NUL char: ${stringify(arr)}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function toNodeAddress(arr: $ReadOnlyArray<string>): NodeAddress {
|
||||
assertAddressArray(arr);
|
||||
return [NODE_PREFIX, ...arr, ""].join(SEPARATOR);
|
||||
}
|
||||
|
||||
export function fromNodeAddress(n: NodeAddress): string[] {
|
||||
const _ = n;
|
||||
throw new Error("fromNodeAddress");
|
||||
assertNodeAddress(n);
|
||||
const parts = n.split(SEPARATOR);
|
||||
return parts.slice(1, parts.length - 1);
|
||||
}
|
||||
|
||||
export function toEdgeAddress(arr: $ReadOnlyArray<string>): EdgeAddress {
|
||||
const _ = arr;
|
||||
throw new Error("toEdgeAddress");
|
||||
assertAddressArray(arr);
|
||||
return [EDGE_PREFIX, ...arr, ""].join(SEPARATOR);
|
||||
}
|
||||
|
||||
export function fromEdgeAddress(n: EdgeAddress): string[] {
|
||||
const _ = n;
|
||||
throw new Error("fromEdgeAddress");
|
||||
assertEdgeAddress(n);
|
||||
const parts = n.split(SEPARATOR);
|
||||
return parts.slice(1, parts.length - 1);
|
||||
}
|
||||
|
||||
export class Graph {
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
// @flow
|
||||
|
||||
import {
|
||||
toNodeAddress,
|
||||
fromNodeAddress,
|
||||
toEdgeAddress,
|
||||
fromEdgeAddress,
|
||||
} from "./graph";
|
||||
|
||||
describe("core/graph", () => {
|
||||
describe("address functions", () => {
|
||||
function throwOnNullOrUndefined(f) {
|
||||
[null, undefined].forEach((bad) => {
|
||||
it(`${f.name} throws on ${String(bad)}`, () => {
|
||||
// $ExpectFlowError
|
||||
expect(() => f(bad)).toThrow(String(bad));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe("toNodeAddress & fromNodeAddress", () => {
|
||||
throwOnNullOrUndefined(toNodeAddress);
|
||||
throwOnNullOrUndefined(fromNodeAddress);
|
||||
it("toNodeAddress errors on path containing NUL char", () => {
|
||||
expect(() => toNodeAddress(["foo", "bar\0", "zoink"])).toThrow(
|
||||
"NUL char"
|
||||
);
|
||||
});
|
||||
[null, undefined].forEach((bad) => {
|
||||
it(`toNodeAddress errors on path containing ${String(bad)}`, () => {
|
||||
// $ExpectFlowError
|
||||
expect(() => toNodeAddress(["foo", bad, "zoink"])).toThrow(
|
||||
String(bad)
|
||||
);
|
||||
});
|
||||
});
|
||||
describe("compose to identity", () => {
|
||||
function checkIdentity(name, example) {
|
||||
it(name, () => {
|
||||
expect(fromNodeAddress(toNodeAddress(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", []);
|
||||
});
|
||||
it("fromNodeAddress errors if passed an edge address", () => {
|
||||
// $ExpectFlowError
|
||||
expect(() => fromNodeAddress(toEdgeAddress(["ex"]))).toThrow(
|
||||
"EdgeAddress"
|
||||
);
|
||||
});
|
||||
it("fromNodeAddress errors if passed a malformed string", () => {
|
||||
// $ExpectFlowError
|
||||
expect(() => fromNodeAddress("N/foo")).toThrow("Malformed");
|
||||
});
|
||||
});
|
||||
|
||||
describe("toEdgeAddress & fromEdgeAddress", () => {
|
||||
throwOnNullOrUndefined(toEdgeAddress);
|
||||
throwOnNullOrUndefined(fromEdgeAddress);
|
||||
it("toEdgeAddress errors on path containing NUL char", () => {
|
||||
expect(() => toEdgeAddress(["foo", "bar\0", "zoink"])).toThrow(
|
||||
"NUL char"
|
||||
);
|
||||
});
|
||||
[null, undefined].forEach((bad) => {
|
||||
it(`toEdgeAddress errors on path containing ${String(bad)}`, () => {
|
||||
// $ExpectFlowError
|
||||
expect(() => toEdgeAddress(["foo", bad, "zoink"])).toThrow(
|
||||
String(bad)
|
||||
);
|
||||
});
|
||||
});
|
||||
describe("compose to identity", () => {
|
||||
function checkIdentity(name, example) {
|
||||
it(name, () => {
|
||||
expect(fromEdgeAddress(toEdgeAddress(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", []);
|
||||
});
|
||||
it("fromEdgeAddress errors if passed an node address", () => {
|
||||
// $ExpectFlowError
|
||||
expect(() => fromEdgeAddress(toNodeAddress(["ex"]))).toThrow(
|
||||
"NodeAddress"
|
||||
);
|
||||
});
|
||||
it("fromEdgeAddress errors if passed a malformed string", () => {
|
||||
// $ExpectFlowError
|
||||
expect(() => fromEdgeAddress("E/foo")).toThrow("Malformed");
|
||||
});
|
||||
});
|
||||
|
||||
it("edge and node addresses are distinct", () => {
|
||||
expect(toEdgeAddress([""])).not.toEqual(toNodeAddress([""]));
|
||||
expect(toEdgeAddress(["foo"])).not.toEqual(toNodeAddress(["foo"]));
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue