1use anyhow::{Context, Result};
6use async_trait::async_trait;
7use opa_wasm::Runtime;
8use serde_json::Value;
9use stormchaser_model::auth::OpaWasmExecutor;
10use wasmtime::*;
11
12pub struct OpaWasmInstance {
14 engine: Engine,
15 module: Module,
16}
17
18impl OpaWasmInstance {
19 pub fn new(module_bytes: &[u8]) -> Result<Self> {
21 let config = Config::new();
22 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 }
82}