clnrm_core/backend/
mod.rs1use crate::error::Result;
6use crate::policy::Policy;
7use std::collections::HashMap;
8use std::path::PathBuf;
9
10pub mod mock;
12pub mod testcontainer;
13pub mod volume;
14
15pub use mock::MockBackend;
16pub use testcontainer::TestcontainerBackend;
17pub use volume::{VolumeMount, VolumeValidator};
18
19pub fn mock_backend() -> MockBackend {
21 MockBackend::new()
22}
23
24#[derive(Debug, Clone)]
31pub struct Cmd {
32 pub bin: String,
34 pub args: Vec<String>,
36 pub workdir: Option<PathBuf>,
38 pub env: HashMap<String, String>,
40 pub policy: Policy,
42}
43
44#[derive(Debug, Clone)]
46pub struct RunResult {
47 pub exit_code: i32,
49 pub stdout: String,
51 pub stderr: String,
53 pub duration_ms: u64,
55 pub steps: Vec<crate::scenario::StepResult>,
57 pub redacted_env: Vec<String>,
59 pub backend: String,
61 pub concurrent: bool,
63 pub step_order: Vec<String>,
65}
66
67impl RunResult {
68 pub fn new(exit_code: i32, stdout: String, stderr: String, duration_ms: u64) -> Self {
70 Self {
71 exit_code,
72 stdout,
73 stderr,
74 duration_ms,
75 steps: Vec::new(),
76 redacted_env: Vec::new(),
77 backend: "unknown".to_string(),
78 concurrent: false,
79 step_order: Vec::new(),
80 }
81 }
82
83 pub fn success(&self) -> bool {
85 self.exit_code == 0
86 }
87
88 pub fn failed(&self) -> bool {
90 self.exit_code != 0
91 }
92}
93
94impl Cmd {
95 pub fn new(bin: impl Into<String>) -> Self {
97 Self {
98 bin: bin.into(),
99 args: Vec::new(),
100 workdir: None,
101 env: HashMap::new(),
102 policy: Policy::default(),
103 }
104 }
105
106 pub fn arg(mut self, arg: impl Into<String>) -> Self {
108 self.args.push(arg.into());
109 self
110 }
111
112 pub fn args(mut self, args: &[&str]) -> Self {
114 for arg in args {
115 self.args.push(arg.to_string());
116 }
117 self
118 }
119
120 pub fn workdir(mut self, workdir: PathBuf) -> Self {
122 self.workdir = Some(workdir);
123 self
124 }
125
126 pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
128 self.env.insert(key.into(), value.into());
129 self
130 }
131
132 pub fn policy(mut self, policy: Policy) -> Self {
134 self.policy = policy;
135 self
136 }
137}
138
139pub trait Backend: Send + Sync + std::fmt::Debug {
141 fn run_cmd(&self, cmd: Cmd) -> Result<RunResult>;
143 fn name(&self) -> &str;
145 fn is_available(&self) -> bool;
147 fn supports_hermetic(&self) -> bool;
149 fn supports_deterministic(&self) -> bool;
151}
152
153#[derive(Debug)]
155pub struct AutoBackend {
156 inner: TestcontainerBackend,
158}
159
160impl AutoBackend {
161 pub fn new(backend: TestcontainerBackend) -> Self {
163 Self { inner: backend }
164 }
165
166 pub fn detect() -> Result<Self> {
168 let backend = TestcontainerBackend::new("alpine:latest")?;
169 Ok(Self { inner: backend })
170 }
171
172 pub fn from_name(name: &str) -> Result<Self> {
174 match name {
175 "testcontainers" | "auto" => Self::detect(),
176 _ => Err(crate::error::CleanroomError::new(
177 crate::error::ErrorKind::ConfigurationError,
178 format!(
179 "Unknown backend: {}. Only 'testcontainers' and 'auto' are supported",
180 name
181 ),
182 )),
183 }
184 }
185
186 pub fn resolved_backend(&self) -> String {
188 self.inner.name().to_string()
189 }
190
191 pub fn is_backend_available(name: &str) -> bool {
193 match name {
194 "testcontainers" | "auto" => TestcontainerBackend::is_available(),
195 _ => false,
196 }
197 }
198}
199
200impl Backend for AutoBackend {
201 fn run_cmd(&self, cmd: Cmd) -> Result<RunResult> {
202 self.inner.run_cmd(cmd)
203 }
204
205 fn name(&self) -> &str {
206 self.inner.name()
207 }
208
209 fn is_available(&self) -> bool {
210 self.inner.is_available()
211 }
212
213 fn supports_hermetic(&self) -> bool {
214 self.inner.supports_hermetic()
215 }
216
217 fn supports_deterministic(&self) -> bool {
218 self.inner.supports_deterministic()
219 }
220}