Skip to content

Commit

Permalink
Merge pull request #34 from nautls/core-validate-plugin-configs
Browse files Browse the repository at this point in the history
Add plugin config validation to ergomatic core
  • Loading branch information
ross-weir authored Sep 18, 2023
2 parents 02052f7 + 8d18688 commit dcb6216
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 57 deletions.
17 changes: 13 additions & 4 deletions plugins/example_plugin/mod.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Plugin, PluginDescriptor } from "../../src/plugins/mod.ts";
import { z } from "zod/mod.ts";

export const EXAMPLE_PLUGIN_ID = "example_plugin";

Expand Down Expand Up @@ -26,13 +27,13 @@ export class ExamplePlugin extends Plugin<ExamplePluginConfig> {
let currentPage = 0;

for await (
const page of this.blockchainProvider.getBoxesByTokenId(tokenId)
const page of this.blockchainProvider.getBoxesByTokenId(
tokenId,
)
) {
currentPage++;

this.logger.info(
`Got page ${currentPage} of boxes for token ${tokenId}`,
);
this.logger.info(`Got page ${currentPage} of boxes for token ${tokenId}`);

this.logger.info(`there was ${page.length} boxes in this page`);

Expand All @@ -45,4 +46,12 @@ export class ExamplePlugin extends Plugin<ExamplePluginConfig> {
}
}
}

// deno-lint-ignore no-explicit-any
configSchema(): z.ZodObject<any> | undefined {
return z.object({
tokenId: z.string(),
exitAtPage: z.number(),
});
}
}
43 changes: 30 additions & 13 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,19 @@ async function runHandler({ config }: RunArgs) {
const ergomaticConfig = mergeUserConfigAndValidate(userConfig);

_ergomatic = new Ergomatic({ config: ergomaticConfig });

// TODO: more robust handling
_ergomatic.addEventListener("plugin:error", (e) => {
console.error(`Plugin error: ${e.detail.plugin.descriptor.name}`);
console.error(e.detail.error);
});

// TODO: more robust handling
_ergomatic.addEventListener("component:error", (e) => {
console.error(`Component error: ${e.detail.component.name}`);
console.error(e.detail.error);
});

_ergomatic.start();
}

Expand All @@ -71,16 +84,20 @@ Deno.addSignalListener("SIGINT", onExit);
* Setting `scriptName` is required to show correct CLI name in `yargs` output.
* Without it `yargs` shows the app name as `deno`.
*/
yargs(Deno.args).scriptName(getScriptName()).command(
"run",
"Start running ergomatic",
// deno-lint-ignore no-explicit-any
function (yargs: any) {
return yargs.option("config", {
alias: "c",
default: "ergomatic.yaml",
describe: "Path to your ergomatic configuration file.",
});
},
runHandler,
).version(`ergomatic ${version} (${Deno.build.target})`).parse();
yargs(Deno.args)
.scriptName(getScriptName())
.command(
"run",
"Start running ergomatic",
// deno-lint-ignore no-explicit-any
function (yargs: any) {
return yargs.option("config", {
alias: "c",
default: "ergomatic.yaml",
describe: "Path to your ergomatic configuration file.",
});
},
runHandler,
)
.version(`ergomatic ${version} (${Deno.build.target})`)
.parse();
36 changes: 35 additions & 1 deletion src/plugins/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Logger } from "std/log/mod.ts";
import { BlockchainProvider, BlockchainSnapshot } from "../blockchain/mod.ts";
import { Block, SignedTransaction } from "@fleet-sdk/common";
import { z } from "zod/mod.ts";

export interface PluginDescriptor {
/** User friendly name of the plugin. */
Expand Down Expand Up @@ -63,7 +64,7 @@ export abstract class Plugin<T = unknown> {
return Promise.resolve();
}

/** Called when a transaction is dropped from mempool. */
/** Called when a transaction is dropped from mempool without being included in a block. */
onMempoolTxDrop(
_tx: SignedTransaction,
_snapshot: Readonly<BlockchainSnapshot>,
Expand All @@ -87,5 +88,38 @@ export abstract class Plugin<T = unknown> {
return Promise.resolve();
}

/**
* The `configSchema` is used to validate the user supplied configuration
* in the `ergomatic` config file. The schema is applied to the {@link T} type.
*
* LSPs auto-complete the function signature for this function to be a complex
* ZodObject<..> return type, you can safely use `z.ZodObject<any>` like below to simplify it.
*
* @example
* ```ts
* // deno-lint-ignore no-explicit-any
* configSchema(): z.ZodObject<any> | undefined {
* return z.object({
* tokenId: z.string(),
* exitAtPage: z.number().optional(),
* });
* }
* ```
*
* @returns Zod schema if the config should be validated by
* `ergomatic` otherwise `undefined`.
*/
// deno-lint-ignore no-explicit-any
configSchema(): z.ZodObject<any> | undefined {
return;
}

/**
* @throws {ZodError} If the config doesn't pass the schema provided by {@link configSchema}.
*/
validateConfig(): void {
this.configSchema()?.parse(this.config);
}

abstract get descriptor(): PluginDescriptor;
}
79 changes: 40 additions & 39 deletions src/plugins/plugin_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,48 +43,49 @@ export class PluginManager extends Component<PluginManagerEvent> {

this.#blockchainProvider = blockchainProvider;
this.#pluginConstructorMap = pluginCtorMap;
this.#_plugins = config.plugins.filter((p) => p.enabled).map((
pluginEntry,
) => ({
plugin: this.#createPlugin(config, pluginEntry),
state: PluginState.Stopped,
}));
this.#_plugins = config.plugins
.filter((p) => p.enabled)
.map((pluginEntry) => ({
plugin: this.#createPlugin(config, pluginEntry),
state: PluginState.Stopped,
}));

this.#setupEventHandlers(blockchainMonitor);
}

public async start(): Promise<void> {
this.logger.debug("Starting plugins");

const promises = this.#pluginsByState(PluginState.Stopped).map(async (
managedPlugin,
) => {
try {
await managedPlugin.plugin.onStart();

managedPlugin.state = PluginState.Running;
} catch (e) {
this.#handlePluginError(managedPlugin, e);
}
});
const promises = this.#pluginsByState(PluginState.Stopped).map(
async (managedPlugin) => {
try {
managedPlugin.plugin.validateConfig();
await managedPlugin.plugin.onStart();

managedPlugin.state = PluginState.Running;
} catch (e) {
this.#handlePluginError(managedPlugin, e);
}
},
);

await Promise.allSettled(promises);
}

public async stop(): Promise<void> {
this.logger.debug("Stopping plugins");

const promises = this.#pluginsByState(PluginState.Running).map(async (
managedPlugin,
) => {
try {
await managedPlugin.plugin.onStop();
const promises = this.#pluginsByState(PluginState.Running).map(
async (managedPlugin) => {
try {
await managedPlugin.plugin.onStop();

managedPlugin.state = PluginState.Stopped;
} catch (e) {
this.#handlePluginError(managedPlugin, e);
}
});
managedPlugin.state = PluginState.Stopped;
} catch (e) {
this.#handlePluginError(managedPlugin, e);
}
},
);

await Promise.allSettled(promises);
}
Expand Down Expand Up @@ -136,36 +137,36 @@ export class PluginManager extends Component<PluginManagerEvent> {
"monitor:mempool-tx",
({ detail }) =>
this.#pluginsByState(PluginState.Running).forEach((p) =>
p.plugin.onMempoolTx(...detail).catch((e) =>
this.#handlePluginError(p, e)
)
p.plugin
.onMempoolTx(...detail)
.catch((e) => this.#handlePluginError(p, e))
),
);
blockchainMonitor.addEventListener(
"monitor:mempool-tx-drop",
({ detail }) =>
this.#pluginsByState(PluginState.Running).forEach((p) =>
p.plugin.onMempoolTxDrop(...detail).catch((e) =>
this.#handlePluginError(p, e)
)
p.plugin
.onMempoolTxDrop(...detail)
.catch((e) => this.#handlePluginError(p, e))
),
);
blockchainMonitor.addEventListener(
"monitor:included-tx",
({ detail }) =>
this.#pluginsByState(PluginState.Running).forEach((p) =>
p.plugin.onIncludedTx(...detail).catch((e) =>
this.#handlePluginError(p, e)
)
p.plugin
.onIncludedTx(...detail)
.catch((e) => this.#handlePluginError(p, e))
),
);
blockchainMonitor.addEventListener(
"monitor:new-block",
({ detail }) =>
this.#pluginsByState(PluginState.Running).forEach((p) =>
p.plugin.onNewBlock(...detail).catch((e) =>
this.#handlePluginError(p, e)
)
p.plugin
.onNewBlock(...detail)
.catch((e) => this.#handlePluginError(p, e))
),
);
}
Expand Down

0 comments on commit dcb6216

Please sign in to comment.