Skip to main content

a3s_box_sdk/sandbox/
script.rs

1use std::time::Duration;
2
3use super::{CommandResult, CommandRunOptions, Commands, SandboxCommand};
4use crate::{ClientError, Result};
5
6/// Fluent builder for a script sent through stdin to an explicit interpreter.
7///
8/// The source is not interpolated into a shell command, so arbitrary script
9/// contents do not require host-side quoting or a temporary host file.
10#[derive(Debug, Clone)]
11pub struct ScriptBuilder {
12    commands: Commands,
13    source: Vec<u8>,
14    interpreter: Vec<String>,
15    options: CommandRunOptions,
16}
17
18impl ScriptBuilder {
19    pub(crate) fn new(commands: Commands, source: impl AsRef<[u8]>) -> Self {
20        Self {
21            commands,
22            source: source.as_ref().to_vec(),
23            interpreter: vec!["/bin/sh".to_string(), "-se".to_string()],
24            options: CommandRunOptions::default(),
25        }
26    }
27
28    /// Select the interpreter argv, for example `["python", "-"]`.
29    pub fn interpreter(mut self, command: impl IntoIterator<Item = impl Into<String>>) -> Self {
30        self.interpreter = command.into_iter().map(Into::into).collect();
31        self
32    }
33
34    pub const fn timeout(mut self, timeout: Duration) -> Self {
35        self.options.timeout = Some(timeout);
36        self
37    }
38
39    pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
40        self.options.envs.insert(key.into(), value.into());
41        self
42    }
43
44    pub fn cwd(mut self, cwd: impl Into<String>) -> Self {
45        self.options.cwd = Some(cwd.into());
46        self
47    }
48
49    pub fn user(mut self, user: impl Into<String>) -> Self {
50        self.options.user = Some(user.into());
51        self
52    }
53
54    pub async fn run(mut self) -> Result<CommandResult> {
55        if self.source.is_empty() {
56            return Err(ClientError::Validation(
57                "script source cannot be empty".to_string(),
58            ));
59        }
60        if self.interpreter.is_empty() {
61            return Err(ClientError::Validation(
62                "script interpreter cannot be empty".to_string(),
63            ));
64        }
65        self.options.stdin = Some(self.source);
66        self.commands
67            .run_with_options(SandboxCommand::Argv(self.interpreter), self.options)
68            .await
69    }
70}
71
72impl Commands {
73    /// Start a fluent, stdin-backed script request.
74    pub fn script(&self, source: impl AsRef<[u8]>) -> ScriptBuilder {
75        ScriptBuilder::new(self.clone(), source)
76    }
77}