Skip to main content

warpgate_api/
host_funcs.rs

1use crate::virtual_path::VirtualPath;
2use crate::{AnyResult, api_struct, api_unit_enum};
3use derive_setters::Setters;
4use rustc_hash::FxHashMap;
5use serde::de::DeserializeOwned;
6use std::path::PathBuf;
7
8api_unit_enum!(
9    /// Target where host logs should be written to.
10    pub enum HostLogTarget {
11        /// Write to the standard error console stream.
12        Stderr,
13
14        /// Write to the standard output console stream.
15        Stdout,
16
17        /// Log a message with the error level.
18        Error,
19
20        /// Log a message with the warn level.
21        Warn,
22
23        /// Log a message with the debug level.
24        Debug,
25
26        /// Log a message with the trace level.
27        #[default]
28        Trace,
29    }
30);
31
32api_struct!(
33    /// Input passed to the `host_log` host function.
34    #[derive(Setters)]
35    #[serde(default)]
36    pub struct HostLogInput {
37        /// Additional data/fields to log.
38        pub data: FxHashMap<String, serde_json::Value>,
39
40        /// The message to log.
41        #[setters(into)]
42        pub message: String,
43
44        /// Target where the log should be written to.
45        pub target: HostLogTarget,
46    }
47);
48
49impl HostLogInput {
50    /// Create a new host log with the provided message.
51    pub fn new(message: impl AsRef<str>) -> Self {
52        Self {
53            message: message.as_ref().to_owned(),
54            ..Default::default()
55        }
56    }
57}
58
59impl From<&str> for HostLogInput {
60    fn from(message: &str) -> Self {
61        HostLogInput::new(message)
62    }
63}
64
65impl From<String> for HostLogInput {
66    fn from(message: String) -> Self {
67        HostLogInput::new(message)
68    }
69}
70
71api_struct!(
72    /// Input passed to the `exec_command` host function.
73    #[derive(Setters)]
74    #[serde(default)]
75    pub struct ExecCommandInput {
76        /// The command or script to execute. Accepts an executable
77        /// name available on `PATH` or a virtual path.
78        #[setters(into)]
79        pub command: String,
80
81        /// Arguments to pass to the command.
82        #[serde(skip_serializing_if = "Vec::is_empty")]
83        pub args: Vec<String>,
84
85        /// Override the current working directory.
86        #[setters(strip_option)]
87        #[serde(alias = "working_dir", skip_serializing_if = "Option::is_none")]
88        pub cwd: Option<VirtualPath>,
89
90        /// Environment variables to pass to the command. Variables
91        /// can customize behavior by appending one of the following
92        /// characters to the name:
93        ///
94        ///  `?` - Will only set variable if it doesn't exist
95        ///        in the current environment.
96        ///  `!` - Will remove the variable from being inherited
97        ///        by the child process.
98        #[serde(skip_serializing_if = "FxHashMap::is_empty")]
99        pub env: FxHashMap<String, String>,
100
101        /// List of real or virtual paths to prepend to the `PATH`
102        /// environment variable when executing the command.
103        #[serde(skip_serializing_if = "Vec::is_empty")]
104        pub paths: Vec<PathBuf>,
105
106        /// Mark the command as executable before executing.
107        #[setters(skip)]
108        #[doc(hidden)]
109        pub set_executable: bool,
110
111        /// Set the shell to execute the command with, for example "bash".
112        #[setters(into, strip_option)]
113        pub shell: Option<String>,
114
115        /// Stream the output instead of capturing it.
116        #[setters(bool)]
117        pub stream: bool,
118    }
119);
120
121impl ExecCommandInput {
122    /// Create a new command that inherits and streams the output.
123    pub fn new<C, I, V>(command: C, args: I) -> ExecCommandInput
124    where
125        C: AsRef<str>,
126        I: IntoIterator<Item = V>,
127        V: AsRef<str>,
128    {
129        let mut input = Self::pipe(command, args);
130        input.stream = true;
131        input
132    }
133
134    /// Create a new command that pipes and captures the output.
135    pub fn pipe<C, I, V>(command: C, args: I) -> ExecCommandInput
136    where
137        C: AsRef<str>,
138        I: IntoIterator<Item = V>,
139        V: AsRef<str>,
140    {
141        ExecCommandInput {
142            command: command.as_ref().to_string(),
143            args: args
144                .into_iter()
145                .map(|arg| arg.as_ref().to_owned())
146                .collect(),
147            ..Default::default()
148        }
149    }
150
151    /// Create a new command that inherits and streams the output.
152    pub fn inherit<C, I, V>(command: C, args: I) -> ExecCommandInput
153    where
154        C: AsRef<str>,
155        I: IntoIterator<Item = V>,
156        V: AsRef<str>,
157    {
158        Self::new(command, args)
159    }
160}
161
162api_struct!(
163    /// Output returned from the `exec_command` host function.
164    #[serde(default)]
165    pub struct ExecCommandOutput {
166        /// The command (without arguments) that was executed.
167        pub command: String,
168
169        /// The exit code returned from the command.
170        pub exit_code: i32,
171
172        /// The standard error output returned from the command.
173        pub stderr: String,
174
175        /// The standard output returned from the command.
176        pub stdout: String,
177
178        /// Whether the command was streamed (inherit) or piped.
179        pub streamed: bool,
180    }
181);
182
183impl ExecCommandOutput {
184    /// Get the combined output of stdout and stderr, trimmed of whitespace.
185    pub fn get_output(&self) -> String {
186        let mut out = String::new();
187        out.push_str(self.stdout.trim());
188
189        if !self.stderr.is_empty() {
190            if !out.is_empty() {
191                out.push(' ');
192            }
193
194            out.push_str(self.stderr.trim());
195        }
196
197        out
198    }
199}
200
201api_struct!(
202    /// Input passed to the `send_request` host function.
203    #[derive(Setters)]
204    pub struct SendRequestInput {
205        /// The URL to send to.
206        #[setters(into)]
207        pub url: String,
208
209        /// HTTP headers to inject into the request.
210        #[serde(default, skip_serializing_if = "FxHashMap::is_empty")]
211        pub headers: FxHashMap<String, String>,
212    }
213);
214
215impl SendRequestInput {
216    /// Create a new send request with the provided url.
217    pub fn new(url: impl AsRef<str>) -> Self {
218        Self {
219            url: url.as_ref().to_owned(),
220            ..Default::default()
221        }
222    }
223}
224
225impl From<&str> for SendRequestInput {
226    fn from(url: &str) -> Self {
227        SendRequestInput::new(url)
228    }
229}
230
231impl From<String> for SendRequestInput {
232    fn from(url: String) -> Self {
233        SendRequestInput::new(url)
234    }
235}
236
237api_struct!(
238    /// Output returned from the `send_request` host function.
239    pub struct SendRequestOutput {
240        /// The response body as raw bytes. When empty, the body must be
241        /// loaded from WASM memory using the offset and length.
242        pub body: Vec<u8>,
243
244        /// Length of the response body stored in WASM memory.
245        pub body_length: u64,
246
247        /// Offset of the response body stored in WASM memory.
248        pub body_offset: u64,
249
250        /// The response status code.
251        pub status: u16,
252    }
253);
254
255impl SendRequestOutput {
256    /// Consume the response body and return as JSON.
257    pub fn json<T: DeserializeOwned>(self) -> AnyResult<T> {
258        Ok(serde_json::from_slice(&self.body)?)
259    }
260
261    /// Consume the response body and return as raw text.
262    pub fn text(self) -> AnyResult<String> {
263        Ok(String::from_utf8(self.body)?)
264    }
265}