Skip to main content

bnto_core/
context.rs

1// Controlled system access for node processors.
2//
3// Processors that wrap external tools (yt-dlp, ffmpeg) need to run commands,
4// create temp files, and read env vars. This trait is the boundary:
5//   - Browser (WASM): `NoopContext` — all methods return errors
6//   - CLI (native): `NativeContext` — full `std::process` access
7//   - Desktop (future): `SandboxedContext` — scoped to approved directories
8
9use std::path::{Path, PathBuf};
10
11use crate::errors::BntoError;
12
13/// System access boundary for processors that need external tools.
14pub trait ProcessContext: Send + Sync {
15    /// Run an external command, capturing stdout.
16    fn run_command(&self, cmd: &str, args: &[&str]) -> Result<Vec<u8>, BntoError>;
17
18    /// Return a unique temporary file path. The file is NOT pre-created —
19    /// the caller (or external tool) is responsible for writing to it.
20    fn temp_file(&self, suffix: &str) -> Result<PathBuf, BntoError>;
21
22    /// Read an environment variable.
23    fn env_var(&self, key: &str) -> Option<String>;
24
25    /// Get the working directory for this execution.
26    fn work_dir(&self) -> Result<&Path, BntoError>;
27}
28
29/// No-op context for browser (WASM) execution.
30/// All system-access methods return errors — browser has no shell.
31pub struct NoopContext;
32
33impl ProcessContext for NoopContext {
34    fn run_command(&self, _cmd: &str, _args: &[&str]) -> Result<Vec<u8>, BntoError> {
35        Err(BntoError::ProcessingFailed(
36            "System commands not available in browser".to_string(),
37        ))
38    }
39
40    fn temp_file(&self, _suffix: &str) -> Result<PathBuf, BntoError> {
41        Err(BntoError::ProcessingFailed(
42            "Temp files not available in browser".to_string(),
43        ))
44    }
45
46    fn env_var(&self, _key: &str) -> Option<String> {
47        None
48    }
49
50    fn work_dir(&self) -> Result<&Path, BntoError> {
51        Err(BntoError::ProcessingFailed(
52            "Working directory not available in browser".to_string(),
53        ))
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    #[test]
62    fn test_noop_context_run_command_returns_err() {
63        let ctx = NoopContext;
64        let result = ctx.run_command("ls", &[]);
65        assert!(result.is_err());
66        assert!(
67            result
68                .unwrap_err()
69                .to_string()
70                .contains("not available in browser")
71        );
72    }
73
74    #[test]
75    fn test_noop_context_temp_file_returns_err() {
76        let ctx = NoopContext;
77        let result = ctx.temp_file(".txt");
78        assert!(result.is_err());
79        assert!(
80            result
81                .unwrap_err()
82                .to_string()
83                .contains("not available in browser")
84        );
85    }
86
87    #[test]
88    fn test_noop_context_env_var_returns_none() {
89        let ctx = NoopContext;
90        assert_eq!(ctx.env_var("PATH"), None);
91    }
92
93    #[test]
94    fn test_noop_context_work_dir_returns_err() {
95        let ctx = NoopContext;
96        let result = ctx.work_dir();
97        assert!(result.is_err());
98        assert!(
99            result
100                .unwrap_err()
101                .to_string()
102                .contains("not available in browser")
103        );
104    }
105}