Skip to main content

stormchaser_opa/
lib.rs

1//! OPA WebAssembly Integration Module.
2//!
3//! Provides execution capabilities for OPA policies compiled to WebAssembly.
4
5use anyhow::{Context, Result};
6use async_trait::async_trait;
7use opa_wasm::Runtime;
8use serde_json::Value;
9use stormchaser_model::auth::OpaWasmExecutor;
10use wasmtime::*;
11
12/// Represents an instantiated OPA WASM engine ready for evaluation.
13pub struct OpaWasmInstance {
14    engine: Engine,
15    module: Module,
16}
17
18impl OpaWasmInstance {
19    /// Creates a new `OpaWasmInstance` from the given WASM module bytes.
20    pub fn new(module_bytes: &[u8]) -> Result<Self> {
21        let config = Config::new();
22        // config.async_support(true); // No longer needed in wasmtime 43.0
23        let engine = Engine::new(&config)?;
24        let module = Module::new(&engine, module_bytes)?;
25        Ok(Self { engine, module })
26    }
27}
28
29#[async_trait]
30impl OpaWasmExecutor for OpaWasmInstance {
31    async fn evaluate(&self, entrypoint: &str, input: &Value) -> Result<bool> {
32        let mut store = Store::new(&self.engine, ());
33        let runtime = Runtime::new(&mut store, &self.module)
34            .await
35            .context("Failed to initialize OPA WASM runtime")?;
36
37        let policy = runtime.without_data(&mut store).await?;
38
39        let result: Value = policy
40            .evaluate(&mut store, entrypoint, input)
41            .await
42            .context("Failed to evaluate OPA policy")?;
43
44        tracing::debug!("OPA WASM result: {:?}", result);
45
46        if let Some(arr) = result.as_array() {
47            if let Some(first) = arr.first() {
48                if let Some(b) = first.as_bool() {
49                    return Ok(b);
50                }
51                if let Some(obj) = first.as_object() {
52                    if let Some(res) = obj.get("result").and_then(|v| v.as_bool()) {
53                        return Ok(res);
54                    }
55                }
56            }
57        }
58
59        if let Some(b) = result.as_bool() {
60            return Ok(b);
61        }
62
63        Ok(false)
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn test_opa_wasm_instance_new_invalid_bytes() {
73        let result = OpaWasmInstance::new(b"not a wasm module");
74        assert!(result.is_err());
75    }
76
77    #[tokio::test]
78    async fn test_opa_wasm_evaluate_fails_on_empty_module() {
79        // We can't easily test a real evaluation without a valid OPA WASM module,
80        // but we can at least test that we handle invalid states.
81    }
82}