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