Add `Graph.edges` filtering by prefixing (#391)

Similar to #390, we now allow filtering the results from `Graph.edges`
by address prefixes. It's a little more complicated than #390, as we
allow filtering by src, dst, or address.

Test plan:
Unit tests added. `yarn travis` passes.

Paired with @wchargin
This commit is contained in:
Dandelion Mané 2018-06-14 11:59:58 -07:00 committed by GitHub
parent 1a08a48c03
commit 7199586262
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 166 additions and 6 deletions

View File

@ -46,6 +46,12 @@ export type NeighborsOptions = {|
+edgePrefix: EdgeAddressT,
|};
export type EdgesOptions = {|
+addressPrefix: EdgeAddressT,
+srcPrefix: NodeAddressT,
+dstPrefix: NodeAddressT,
|};
type AddressJSON = string[]; // Result of calling {Node,Edge}Address.toParts
type Integer = number;
type IndexedEdgeJSON = {|
@ -409,18 +415,45 @@ export class Graph {
return result;
}
edges(): Iterator<Edge> {
const result = this._edgesIterator(this._modificationCount);
edges(options?: EdgesOptions): Iterator<Edge> {
if (options == null) {
options = {
addressPrefix: EdgeAddress.fromParts([]),
srcPrefix: NodeAddress.fromParts([]),
dstPrefix: NodeAddress.fromParts([]),
};
}
if (options.addressPrefix == null) {
throw new Error(
`Invalid address prefix: ${String(options.addressPrefix)}`
);
}
if (options.srcPrefix == null) {
throw new Error(`Invalid src prefix: ${String(options.srcPrefix)}`);
}
if (options.dstPrefix == null) {
throw new Error(`Invalid dst prefix: ${String(options.dstPrefix)}`);
}
const result = this._edgesIterator(this._modificationCount, options);
this._maybeCheckInvariants();
return result;
}
*_edgesIterator(initialModificationCount: ModificationCount): Iterator<Edge> {
*_edgesIterator(
initialModificationCount: ModificationCount,
options: EdgesOptions
): Iterator<Edge> {
for (const edge of this._edges.values()) {
if (
EdgeAddress.hasPrefix(edge.address, options.addressPrefix) &&
NodeAddress.hasPrefix(edge.src, options.srcPrefix) &&
NodeAddress.hasPrefix(edge.dst, options.dstPrefix)
) {
this._checkForComodification(initialModificationCount);
this._maybeCheckInvariants();
yield edge;
}
}
this._checkForComodification(initialModificationCount);
this._maybeCheckInvariants();
}

View File

@ -3,7 +3,9 @@
import sortBy from "lodash.sortby";
import {
type Edge,
type EdgeAddressT,
type EdgesOptions,
type Neighbor,
type NeighborsOptions,
type NodeAddressT,
@ -585,6 +587,131 @@ describe("core/graph", () => {
});
});
describe("edges filtering", () => {
const src1 = NodeAddress.fromParts(["src", "1"]);
const src2 = NodeAddress.fromParts(["src", "2"]);
const dst1 = NodeAddress.fromParts(["dst", "1"]);
const dst2 = NodeAddress.fromParts(["dst", "2"]);
const e11 = {
src: src1,
dst: dst1,
address: EdgeAddress.fromParts(["e", "1", "1"]),
};
const e12 = {
src: src1,
dst: dst2,
address: EdgeAddress.fromParts(["e", "1", "2"]),
};
const e21 = {
src: src2,
dst: dst1,
address: EdgeAddress.fromParts(["e", "2", "1"]),
};
const e22 = {
src: src2,
dst: dst2,
address: EdgeAddress.fromParts(["e", "2", "2"]),
};
const graph = () =>
[e11, e12, e21, e22].reduce(
(g, e) => g.addEdge(e),
[src1, src2, dst1, dst2].reduce((g, n) => g.addNode(n), new Graph())
);
function expectEdges(
options: EdgesOptions | void,
expected: $ReadOnlyArray<Edge>
) {
const sort = (es) => sortBy(es, (e) => e.address);
expect(sort(Array.from(graph().edges(options)))).toEqual(
sort(expected.slice())
);
}
it("finds all edges when no options are specified", () => {
expectEdges(undefined, [e11, e12, e21, e22]);
});
it("finds all edges when universal filters are specified", () => {
expectEdges(
{
addressPrefix: EdgeAddress.fromParts(["e"]),
srcPrefix: NodeAddress.fromParts(["src"]),
dstPrefix: NodeAddress.fromParts(["dst"]),
},
[e11, e12, e21, e22]
);
});
it("requires `addressPrefix` to be present in provided options", () => {
expect(() => {
graph()
// $ExpectFlowError
.edges({srcPrefix: src1, dstPrefix: dst1});
}).toThrow("Invalid address prefix: undefined");
});
it("requires `srcPrefix` to be present in provided options", () => {
expect(() => {
graph()
// $ExpectFlowError
.edges({addressPrefix: e11, dstPrefix: dst1});
}).toThrow("Invalid src prefix: undefined");
});
it("requires `dstPrefix` to be present in provided options", () => {
expect(() => {
graph()
// $ExpectFlowError
.edges({addressPrefix: e11, srcPrefix: dst1});
}).toThrow("Invalid dst prefix: undefined");
});
it("finds edges by address prefix", () => {
expectEdges(
{
addressPrefix: EdgeAddress.fromParts(["e", "1"]),
srcPrefix: NodeAddress.fromParts([]),
dstPrefix: NodeAddress.fromParts([]),
},
[e11, e12]
);
});
it("finds edges by src prefix", () => {
expectEdges(
{
addressPrefix: EdgeAddress.fromParts([]),
srcPrefix: NodeAddress.fromParts(["src", "1"]),
dstPrefix: NodeAddress.fromParts([]),
},
[e11, e12]
);
});
it("finds edges by dst prefix", () => {
expectEdges(
{
addressPrefix: EdgeAddress.fromParts([]),
srcPrefix: NodeAddress.fromParts([]),
dstPrefix: NodeAddress.fromParts(["dst", "1"]),
},
[e11, e21]
);
});
it("yields nothing for disjoint filters", () => {
expectEdges(
{
addressPrefix: EdgeAddress.fromParts(["e", "1"]),
srcPrefix: NodeAddress.fromParts(["src", "2"]),
dstPrefix: NodeAddress.fromParts([]),
},
[]
);
});
it("yields appropriate filter intersection", () => {
expectEdges(
{
addressPrefix: EdgeAddress.fromParts([]),
srcPrefix: NodeAddress.fromParts(["src", "1"]),
dstPrefix: NodeAddress.fromParts(["dst", "2"]),
},
[e12]
);
});
});
describe("on a graph", () => {
const src = NodeAddress.fromParts(["foo"]);
const dst = NodeAddress.fromParts(["bar"]);