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