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