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:
parent
c02a81ff43
commit
0cf5923bce
|
@ -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()
|
||||
|
|
|
@ -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())
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue