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