Skip to main content

arvalez_plugin_sdk/
lib.rs

1use 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}