From 7199586262850d0d9e506439bddec4e70c48a68a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dandelion=20Man=C3=A9?= Date: Thu, 14 Jun 2018 11:59:58 -0700 Subject: [PATCH] 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 --- src/v3/core/graph.js | 45 ++++++++++++-- src/v3/core/graph.test.js | 127 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+), 6 deletions(-) diff --git a/src/v3/core/graph.js b/src/v3/core/graph.js index 482be01..36a7299 100644 --- a/src/v3/core/graph.js +++ b/src/v3/core/graph.js @@ -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,17 +415,44 @@ export class Graph { return result; } - edges(): Iterator { - const result = this._edgesIterator(this._modificationCount); + edges(options?: EdgesOptions): Iterator { + 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 { + *_edgesIterator( + initialModificationCount: ModificationCount, + options: EdgesOptions + ): Iterator { for (const edge of this._edges.values()) { - this._checkForComodification(initialModificationCount); - this._maybeCheckInvariants(); - yield edge; + 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(); diff --git a/src/v3/core/graph.test.js b/src/v3/core/graph.test.js index 50239a8..d0d77ee 100644 --- a/src/v3/core/graph.test.js +++ b/src/v3/core/graph.test.js @@ -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 + ) { + 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"]);