Add dispatch methods for plugin adapters (#619)

This commit adds some consistent and tested methods for getting the
appropriate plugin adapter for a given Node/Edge address. There are
methods for both static and dynamic adapters.

In the event that more than one plugin adapter matches the given
address, an error is thrown; likewise in the case where there is no
matching adapter.

Test plan: `yarn test`

Relevant to #465
This commit is contained in:
Dandelion Mané 2018-08-07 15:24:34 -07:00 committed by GitHub
parent c02a81ff43
commit 0cf5923bce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 165 additions and 20 deletions

View File

@ -14,23 +14,18 @@ import type {
ScoredConnection,
} from "../../core/attribution/pagerankNodeDecomposition";
import type {Connection} from "../../core/attribution/graphToMarkovChain";
import type {DynamicPluginAdapter} from "../pluginAdapter";
import {
type DynamicPluginAdapter,
dynamicDispatchByNode,
dynamicDispatchByEdge,
} from "../pluginAdapter";
import * as NullUtil from "../../util/null";
// TODO: Factor this out and test it (#465)
export function nodeDescription(
address: NodeAddressT,
adapters: $ReadOnlyArray<DynamicPluginAdapter>
): string {
const adapter = adapters.find((adapter) =>
NodeAddress.hasPrefix(address, adapter.static().nodePrefix())
);
if (adapter == null) {
const result = NodeAddress.toString(address);
console.warn(`No adapter for ${result}`);
return result;
}
const adapter = dynamicDispatchByNode(adapters, address);
try {
return adapter.nodeDescription(address);
} catch (e) {
@ -45,14 +40,7 @@ function edgeVerb(
direction: "FORWARD" | "BACKWARD",
adapters: $ReadOnlyArray<DynamicPluginAdapter>
): string {
const adapter = adapters.find((adapter) =>
EdgeAddress.hasPrefix(address, adapter.static().edgePrefix())
);
if (adapter == null) {
const result = EdgeAddress.toString(address);
console.warn(`No adapter for ${result}`);
return result;
}
const adapter = dynamicDispatchByEdge(adapters, address);
const edgeType = adapter
.static()

View File

@ -1,6 +1,12 @@
// @flow
import type {Graph, NodeAddressT, EdgeAddressT} from "../core/graph";
import {
Graph,
NodeAddress,
EdgeAddress,
type NodeAddressT,
type EdgeAddressT,
} from "../core/graph";
import type {Repo} from "../core/repo";
export type EdgeType = {|
@ -29,3 +35,53 @@ export interface DynamicPluginAdapter {
nodeDescription(NodeAddressT): string;
static (): StaticPluginAdapter;
}
function findUniqueMatch<T>(
xs: $ReadOnlyArray<T>,
predicate: (T) => boolean
): T {
const results = xs.filter(predicate);
if (results.length > 1) {
throw new Error("Multiple entities match predicate");
}
if (results.length === 0) {
throw new Error("No entity matches predicate");
}
return results[0];
}
export function staticDispatchByNode(
adapters: $ReadOnlyArray<StaticPluginAdapter>,
x: NodeAddressT
): StaticPluginAdapter {
return findUniqueMatch(adapters, (a) =>
NodeAddress.hasPrefix(x, a.nodePrefix())
);
}
export function staticDispatchByEdge(
adapters: $ReadOnlyArray<StaticPluginAdapter>,
x: EdgeAddressT
): StaticPluginAdapter {
return findUniqueMatch(adapters, (a) =>
EdgeAddress.hasPrefix(x, a.edgePrefix())
);
}
export function dynamicDispatchByNode(
adapters: $ReadOnlyArray<DynamicPluginAdapter>,
x: NodeAddressT
): DynamicPluginAdapter {
return findUniqueMatch(adapters, (a) =>
NodeAddress.hasPrefix(x, a.static().nodePrefix())
);
}
export function dynamicDispatchByEdge(
adapters: $ReadOnlyArray<DynamicPluginAdapter>,
x: EdgeAddressT
): DynamicPluginAdapter {
return findUniqueMatch(adapters, (a) =>
EdgeAddress.hasPrefix(x, a.static().edgePrefix())
);
}

View File

@ -0,0 +1,101 @@
// @flow
import {
Graph,
NodeAddress,
EdgeAddress,
type NodeAddressT,
} from "../core/graph";
import {
type StaticPluginAdapter,
type DynamicPluginAdapter,
staticDispatchByNode,
staticDispatchByEdge,
dynamicDispatchByNode,
dynamicDispatchByEdge,
} from "./pluginAdapter";
describe("app/pluginAdapter", () => {
function example() {
const staticFooAdapter: StaticPluginAdapter = {
name: () => "foo",
nodePrefix: () => NodeAddress.fromParts(["foo"]),
edgePrefix: () => EdgeAddress.fromParts(["foo"]),
nodeTypes: () => [],
edgeTypes: () => [],
load: (_unused_repo) => Promise.resolve(dynamicFooAdapter),
};
const dynamicFooAdapter: DynamicPluginAdapter = {
graph: () => new Graph(),
nodeDescription: (x: NodeAddressT) => NodeAddress.toString(x),
static: () => staticFooAdapter,
};
const staticBarAdapter: StaticPluginAdapter = {
name: () => "bar",
nodePrefix: () => NodeAddress.fromParts(["bar"]),
edgePrefix: () => EdgeAddress.fromParts(["bar"]),
nodeTypes: () => [],
edgeTypes: () => [],
load: (_unused_repo) => Promise.resolve(dynamicBarAdapter),
};
const dynamicBarAdapter: DynamicPluginAdapter = {
graph: () => new Graph(),
nodeDescription: (x) => NodeAddress.toString(x),
static: () => staticBarAdapter,
};
const statics = [staticFooAdapter, staticBarAdapter];
const dynamics = [dynamicFooAdapter, dynamicBarAdapter];
return {
statics,
dynamics,
staticFooAdapter,
dynamicFooAdapter,
};
}
describe("dispatching", () => {
describe("error handling", () => {
// Just testing staticDispatchByNode is fine, as they all call the same
// implementation
it("errors if it cannot match", () => {
const {statics} = example();
const zod = NodeAddress.fromParts(["zod"]);
expect(() => staticDispatchByNode(statics, zod)).toThrowError(
"No entity matches"
);
});
it("errors if there are multiple matches", () => {
const {staticFooAdapter} = example();
const statics = [staticFooAdapter, staticFooAdapter];
const foo = NodeAddress.fromParts(["foo"]);
expect(() => staticDispatchByNode(statics, foo)).toThrowError(
"Multiple entities match"
);
});
});
it("staticDispatchByNode works", () => {
const {statics, staticFooAdapter} = example();
const fooSubnode = NodeAddress.fromParts(["foo", "sub"]);
expect(staticDispatchByNode(statics, fooSubnode)).toBe(staticFooAdapter);
});
it("staticDispatchByEdge works", () => {
const {statics, staticFooAdapter} = example();
const fooSubedge = EdgeAddress.fromParts(["foo", "sub"]);
expect(staticDispatchByEdge(statics, fooSubedge)).toBe(staticFooAdapter);
});
it("dynamicDispatchByNode works", () => {
const {dynamics, dynamicFooAdapter} = example();
const fooSubnode = NodeAddress.fromParts(["foo", "sub"]);
expect(dynamicDispatchByNode(dynamics, fooSubnode)).toBe(
dynamicFooAdapter
);
});
it("dynamicDispatchByEdge works", () => {
const {dynamics, dynamicFooAdapter} = example();
const fooSubedge = EdgeAddress.fromParts(["foo", "sub"]);
expect(dynamicDispatchByEdge(dynamics, fooSubedge)).toBe(
dynamicFooAdapter
);
});
});
});