warpgate_api/
host_funcs.rs

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