1mod config;
6pub mod describe;
7mod error;
8mod resolve;
9mod runner;
10mod store;
11mod verify;
12
13pub use config::{ExecConfig, RuntimePolicy, VerifyPolicy};
14pub use error::ExecError;
15pub use store::{ToolInfo, ToolStore};
16
17use greentic_types::TenantCtx;
18use serde_json::Value;
19
20use crate::error::RunnerError;
21use crate::runner::Runner;
22
23#[derive(Clone, Debug)]
24pub struct ExecRequest {
25 pub component: String,
26 pub action: String,
27 pub args: Value,
28 pub tenant: Option<TenantCtx>,
29}
30
31pub fn exec(req: ExecRequest, cfg: &ExecConfig) -> Result<Value, ExecError> {
36 let resolved = resolve::resolve(&req.component, &cfg.store)
37 .map_err(|err| ExecError::resolve(&req.component, err))?;
38
39 let verified = verify::verify(&req.component, resolved, &cfg.security)
40 .map_err(|err| ExecError::verification(&req.component, err))?;
41
42 let runner = runner::DefaultRunner::new(&cfg.runtime)
43 .map_err(|err| ExecError::runner(&req.component, err))?;
44
45 let result = runner.run(
46 &req,
47 &verified,
48 runner::ExecutionContext {
49 runtime: &cfg.runtime,
50 http_enabled: cfg.http_enabled,
51 tenant: req.tenant.as_ref(),
52 },
53 );
54
55 let value = match result {
56 Ok(v) => v,
57 Err(RunnerError::ActionNotFound { .. }) => {
58 return Err(ExecError::not_found(
59 req.component.clone(),
60 req.action.clone(),
61 ));
62 }
63 Err(err) => return Err(ExecError::runner(&req.component, err)),
64 };
65
66 if let Some(error_value) = value.get("error").cloned()
67 && let Some(code) = error_value
68 .get("code")
69 .and_then(Value::as_str)
70 .map(|s| s.to_string())
71 {
72 if code == "iface-error.not-found" {
73 return Err(ExecError::not_found(req.component, req.action));
74 } else {
75 return Err(ExecError::tool_error(
76 req.component,
77 req.action,
78 code,
79 value,
80 ));
81 }
82 }
83
84 Ok(value)
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use crate::config::{RuntimePolicy, VerifyPolicy};
91 use crate::error::RunnerError;
92 use crate::store::ToolStore;
93 use serde_json::json;
94 use std::collections::HashMap;
95 use std::path::PathBuf;
96
97 use crate::verify::VerifiedArtifact;
98
99 #[derive(Default)]
100 struct MockRunner;
101
102 impl runner::Runner for MockRunner {
103 fn run(
104 &self,
105 request: &ExecRequest,
106 artifact: &VerifiedArtifact,
107 _ctx: runner::ExecutionContext<'_>,
108 ) -> Result<Value, RunnerError> {
109 let mut payload = request.args.clone();
110 if let Value::Object(map) = &mut payload {
111 map.insert(
112 "component_digest".to_string(),
113 Value::String(artifact.resolved.digest.clone()),
114 );
115 }
116 Ok(payload)
117 }
118 }
119
120 #[test]
121 fn local_resolve_and_verify_success() {
122 let tempdir = tempfile::tempdir().expect("tempdir");
123 let wasm_path = tempdir.path().join("echo.component.wasm");
124 std::fs::write(&wasm_path, b"fake wasm contents").expect("write");
125
126 let digest = crate::resolve::resolve(
127 "echo.component",
128 &ToolStore::LocalDir(PathBuf::from(tempdir.path())),
129 )
130 .expect("resolve")
131 .digest;
132
133 let mut required = HashMap::new();
134 required.insert("echo.component".to_string(), digest.clone());
135
136 let cfg = ExecConfig {
137 store: ToolStore::LocalDir(PathBuf::from(tempdir.path())),
138 security: VerifyPolicy {
139 allow_unverified: false,
140 required_digests: required,
141 trusted_signers: Vec::new(),
142 },
143 runtime: RuntimePolicy::default(),
144 http_enabled: false,
145 };
146
147 let req = ExecRequest {
148 component: "echo.component".into(),
149 action: "noop".into(),
150 args: json!({"message": "hello"}),
151 tenant: None,
152 };
153
154 let resolved =
156 crate::resolve::resolve(&req.component, &cfg.store).expect("resolve second time");
157 let verified =
158 crate::verify::verify(&req.component, resolved, &cfg.security).expect("verify");
159 let result = MockRunner
160 .run(
161 &req,
162 &verified,
163 runner::ExecutionContext {
164 runtime: &cfg.runtime,
165 http_enabled: cfg.http_enabled,
166 tenant: req.tenant.as_ref(),
167 },
168 )
169 .expect("run");
170
171 assert_eq!(
172 result.get("component_digest").and_then(Value::as_str),
173 Some(digest.as_str())
174 );
175 }
176}