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