arvalez_plugin_sdk/
lib.rs1use std::{env, fs};
2
3use anyhow::{Context, Result};
4use arvalez_ir::{CoreIr, PluginRequest, PluginResponse, Target, validate_ir};
5use serde::de::DeserializeOwned;
6
7const REQUEST_PATH_ENV: &str = "ARVALEZ_REQUEST_PATH";
8const RESPONSE_PATH_ENV: &str = "ARVALEZ_RESPONSE_PATH";
9
10pub trait Plugin {
11 type Options: DeserializeOwned + Default;
12
13 fn transform_core(
14 &self,
15 ctx: &PluginContext<Self::Options>,
16 ir: CoreIr,
17 ) -> Result<TransformOutput>;
18}
19
20#[derive(Debug, Clone)]
21pub struct PluginContext<Options> {
22 plugin_name: String,
23 target: Option<Target>,
24 options: Options,
25}
26
27impl<Options> PluginContext<Options> {
28 pub fn plugin_name(&self) -> &str {
29 &self.plugin_name
30 }
31
32 pub fn target(&self) -> Option<Target> {
33 self.target
34 }
35
36 pub fn options(&self) -> &Options {
37 &self.options
38 }
39}
40
41#[derive(Debug, Clone)]
42pub struct TransformOutput {
43 pub ir: CoreIr,
44 pub warnings: Vec<String>,
45}
46
47impl TransformOutput {
48 pub fn ok(ir: CoreIr) -> Self {
49 Self {
50 ir,
51 warnings: Vec::new(),
52 }
53 }
54
55 pub fn with_warning(mut self, warning: impl Into<String>) -> Self {
56 self.warnings.push(warning.into());
57 self
58 }
59}
60
61pub fn run_plugin<P>(plugin: P) -> Result<()>
62where
63 P: Plugin,
64{
65 let request_path =
66 env::var(REQUEST_PATH_ENV).context("missing ARVALEZ_REQUEST_PATH environment variable")?;
67 let response_path = env::var(RESPONSE_PATH_ENV)
68 .context("missing ARVALEZ_RESPONSE_PATH environment variable")?;
69
70 let request_bytes = fs::read(&request_path)
71 .with_context(|| format!("failed to read plugin request from `{request_path}`"))?;
72 let request: PluginRequest =
73 serde_json::from_slice(&request_bytes).context("failed to deserialize plugin request")?;
74 validate_ir(&request.ir).context("input IR is invalid")?;
75
76 let PluginRequest { context, ir } = request;
77 let options = if context.options.is_null() {
78 P::Options::default()
79 } else {
80 serde_json::from_value(context.options).context("failed to deserialize plugin options")?
81 };
82
83 let plugin_context = PluginContext {
84 plugin_name: context.plugin_name,
85 target: context.target,
86 options,
87 };
88
89 let output = plugin.transform_core(&plugin_context, ir)?;
90 validate_ir(&output.ir).context("plugin returned an invalid IR")?;
91
92 let response = PluginResponse {
93 ir: output.ir,
94 warnings: output.warnings,
95 };
96 let response_bytes =
97 serde_json::to_vec_pretty(&response).context("failed to serialize plugin response")?;
98 fs::write(&response_path, response_bytes)
99 .with_context(|| format!("failed to write plugin response to `{response_path}`"))?;
100
101 Ok(())
102}