Add `NodeReference` and `NodePorcelain` to core (#286)
`NodeReference` and `NodePorcelain` act as abstractions over the two states a Node can be in. - We might have the address of a node (because some edge pointed to it), but don't actually have the Node in the graph. In that case, we can do some queries on the node (e.g. find its neighbors), but can't access the payload. This corresponds to having a `NodeReference`. - We might have the node in the graph. In that case, we can access a `NodePorcelain`. The main benefit this abstraction brings is type-safety over accessing data from a `NodePayload`. Previously, the coding conventions encouraged clients to ignore the distinction, and the type signatures incorrectly reported that many payload-level properties were non-nullable. Now, the `get` method that mapp a `Reference` to a `Payload` is explicilty nullable. Given a `NodePorcelain`, it's always possible to retrieve the reference via `ref()`. Given the `NodeReference`, you might be able to retrieve the `NodePorcelain` via `get()`. Clients that subtype `NodePorcelain` and `NodeReference` should, in general, override the `ref()` and `get()` methods to return their subtype. We also recommend having subclasses overwrite the constructors to take a base `NodePorcelain` and `NodeReference` respectively (although the base classes take a `Graph` and `address` as constructor arguments). Test Plan: Inspect the unit tests, they are pretty thorough. Paired with @wchargin
This commit is contained in:
parent
f31d2c517d
commit
7ccef98c87
|
@ -0,0 +1,68 @@
|
|||
// @flow
|
||||
|
||||
import type {Address} from "./address";
|
||||
import type {Edge, Graph, Node} from "./graph";
|
||||
|
||||
export class NodeReference<+T> {
|
||||
_graph: Graph<any, any>;
|
||||
_address: Address;
|
||||
|
||||
constructor(g: Graph<any, any>, a: Address) {
|
||||
this._graph = g;
|
||||
this._address = a;
|
||||
}
|
||||
|
||||
neighbors(options?: {|
|
||||
+nodeType?: string,
|
||||
+edgeType?: string,
|
||||
+direction?: "IN" | "OUT" | "ANY",
|
||||
|}): {|+ref: NodeReference<any>, edge: Edge<any>|}[] {
|
||||
return this._graph
|
||||
.neighborhood(this._address, options)
|
||||
.map(({neighbor, edge}) => ({
|
||||
ref: new NodeReference(this._graph, neighbor),
|
||||
edge,
|
||||
}));
|
||||
}
|
||||
|
||||
graph(): Graph<any, any> {
|
||||
return this._graph;
|
||||
}
|
||||
|
||||
address(): Address {
|
||||
return this._address;
|
||||
}
|
||||
|
||||
type(): string {
|
||||
return this._address.type;
|
||||
}
|
||||
|
||||
get(): ?NodePorcelain<T> {
|
||||
const node = this._graph.node(this._address);
|
||||
if (node != null) {
|
||||
return new NodePorcelain(this, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class NodePorcelain<+T> {
|
||||
+_ref: NodeReference<T>;
|
||||
+_node: Node<T>;
|
||||
|
||||
constructor(ref: NodeReference<T>, n: Node<T>) {
|
||||
this._ref = ref;
|
||||
this._node = n;
|
||||
}
|
||||
|
||||
node(): Node<T> {
|
||||
return this._node;
|
||||
}
|
||||
|
||||
payload(): T {
|
||||
return this._node.payload;
|
||||
}
|
||||
|
||||
ref(): NodeReference<T> {
|
||||
return this._ref;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
// @flow
|
||||
|
||||
import {NodePorcelain, NodeReference} from "./porcelain";
|
||||
import * as demoData from "./graphDemoData";
|
||||
|
||||
function exampleStuff() {
|
||||
const graph = demoData.advancedMealGraph();
|
||||
const heroNode = demoData.heroNode();
|
||||
const heroReference = new NodeReference(graph, heroNode.address);
|
||||
const heroPorcelain = new NodePorcelain(heroReference, heroNode);
|
||||
const fakeAddress = demoData.makeAddress(
|
||||
"I do not exist",
|
||||
"minion of Magnificent Foo Plugin"
|
||||
);
|
||||
const fakeReference = new NodeReference(graph, fakeAddress);
|
||||
return {
|
||||
graph,
|
||||
heroNode,
|
||||
heroReference,
|
||||
heroPorcelain,
|
||||
fakeAddress,
|
||||
fakeReference,
|
||||
};
|
||||
}
|
||||
|
||||
describe("NodeReference", () => {
|
||||
it("can retrieve graph", () => {
|
||||
const {graph, heroReference, fakeReference} = exampleStuff();
|
||||
expect(heroReference.graph()).toBe(graph);
|
||||
expect(fakeReference.graph()).toBe(graph);
|
||||
});
|
||||
|
||||
it("can retrieve address", () => {
|
||||
const {
|
||||
heroReference,
|
||||
fakeReference,
|
||||
fakeAddress,
|
||||
heroNode,
|
||||
} = exampleStuff();
|
||||
expect(heroReference.address()).toEqual(heroNode.address);
|
||||
expect(fakeReference.address()).toEqual(fakeAddress);
|
||||
});
|
||||
|
||||
it("can retrieve type", () => {
|
||||
const {
|
||||
heroReference,
|
||||
fakeReference,
|
||||
fakeAddress,
|
||||
heroNode,
|
||||
} = exampleStuff();
|
||||
expect(heroReference.type()).toBe(heroNode.address.type);
|
||||
expect(fakeReference.type()).toBe(fakeAddress.type);
|
||||
});
|
||||
|
||||
it("can retrieve porcelain", () => {
|
||||
const {heroReference, heroPorcelain, fakeReference} = exampleStuff();
|
||||
expect(heroReference.get()).toEqual(heroPorcelain);
|
||||
expect(fakeReference.get()).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("can retrieve neighbors", () => {
|
||||
const {heroReference, fakeReference, graph} = exampleStuff();
|
||||
expect(
|
||||
heroReference
|
||||
.neighbors()
|
||||
.map(({edge, ref}) => ({edge, neighbor: ref.address()}))
|
||||
).toEqual(graph.neighborhood(heroReference.address()));
|
||||
expect(fakeReference.neighbors()).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("NodePorcelain", () => {
|
||||
it("can get node", () => {
|
||||
const {heroNode, heroPorcelain} = exampleStuff();
|
||||
expect(heroPorcelain.node()).toEqual(heroNode);
|
||||
});
|
||||
|
||||
it("can get payload", () => {
|
||||
const {heroNode, heroPorcelain} = exampleStuff();
|
||||
expect(heroPorcelain.payload()).toEqual(heroNode.payload);
|
||||
});
|
||||
|
||||
it("can get ref", () => {
|
||||
const {heroPorcelain, heroReference} = exampleStuff();
|
||||
expect(heroPorcelain.ref()).toEqual(heroReference);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue