rusty_runner_api/
api.rs

1//! This module defines the API query, request body and response body
2//! schema for this crate and its server by means of serde serializable
3//! and deserializable rust structs.
4
5use serde::{Deserialize, Serialize};
6use std::time::Duration;
7
8pub const VERSION: &str = env!("CARGO_PKG_VERSION");
9
10/// The json-response schema for `GET /api/info`.
11///
12/// # Serialized Example
13/// ```
14/// # let ser = r#"
15/// {
16///    "os_type": "Unix",
17///    "computer_name": "GLaDOS",
18///    "api_version": "2.0.0"
19/// }
20/// # "#;
21/// # let deser: rusty_runner_api::api::InfoResponse
22/// #    = serde_json::from_str(ser).expect("failed parsing");
23/// # assert_eq!(deser.computer_name, "GLaDOS");
24/// # assert_eq!(deser.api_version, rusty_runner_api::api::VERSION);
25/// ```
26#[derive(Debug, Serialize, Deserialize)]
27pub struct InfoResponse {
28    /// The operating system type running.
29    pub os_type: OsType,
30    /// Any descriptive name of the runner.
31    pub computer_name: String,
32    /// The version of the api supported. Defined by [`VERSION`].
33    pub api_version: String,
34}
35
36/// The OS type as given by `#[cfg(windows)]` and `#[cfg(unix)]`.
37#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
38pub enum OsType {
39    Windows,
40    Unix,
41}
42
43/// The json-body schema for `POST /api/run`.
44///
45/// # Serialized Example
46/// ```
47/// # let ser = r#"
48/// {
49///  "command": "echo",
50///  "arguments": [
51///    "Hello",
52///    "World"
53///  ],
54///  "return_stderr": true,
55///  "return_stdout": false
56///}
57/// # "#;
58/// # let deser: rusty_runner_api::api::RunRequest
59/// #    = serde_json::from_str(ser).expect("failed parsing");
60/// # assert_eq!(deser.command, "echo");
61/// ```
62#[derive(Debug, Serialize, Deserialize)]
63pub struct RunRequest {
64    /// The command as available on the path or a path to an executable.
65    pub command: String,
66    /// The arguments as passed to `tokio::process::Command::args`
67    ///
68    /// # Warning
69    /// [Raw args](https://doc.rust-lang.org/stable/std/os/windows/process/trait.CommandExt.html#tymethod.raw_arg)
70    /// are not supported.
71    /// Avoid `cmd.exe /C`!
72    pub arguments: Vec<String>,
73    /// `true` if the api should capture and return `stdout`. Defaults to `false`.
74    #[serde(default)]
75    pub return_stdout: bool,
76    /// `true` if the api should capture and return `stderr`. Defaults to `false`.
77    #[serde(default)]
78    pub return_stderr: bool,
79}
80
81/// The query schema for `POST /api/runscript`.
82///
83/// # Serialized Example
84/// ```
85/// # let ser = r#"
86/// interpreter=bash&return_stderr=true
87/// # "#;
88/// # let deser: rusty_runner_api::api::RunScriptQuery
89/// #    = serde_urlencoded::from_str(ser.trim()).expect("failed parsing");
90/// # assert!(matches!(deser.interpreter, rusty_runner_api::api::ScriptInterpreter::Bash));
91/// ```
92#[derive(Debug, Serialize, Deserialize)]
93pub struct RunScriptQuery {
94    /// The script in the request body will be run by the given `interpreter`.
95    pub interpreter: ScriptInterpreter,
96    // Note, `serde` does not support proper flattening here, so this cannot be moved to a struct `OutputOptions`,
97    // <https://github.com/nox/serde_urlencoded/issues/33>.
98    /// `true` if the api should capture and return `stdout`. Defaults to `false`.
99    #[serde(default)]
100    pub return_stdout: bool,
101    /// `true` if the api should capture and return `stderr`. Defaults to `false`.
102    #[serde(default)]
103    pub return_stderr: bool,
104}
105
106/// The interpreter that the script will be called with.
107///
108/// Not all interpreters may be supported by any runner.
109#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
110#[serde(rename_all = "lowercase")]
111pub enum ScriptInterpreter {
112    Bash,
113    Cmd,
114    Powershell,
115}
116
117impl ScriptInterpreter {
118    /// Returns the default file extension.
119    #[must_use]
120    pub fn as_extension(&self) -> &'static str {
121        match self {
122            ScriptInterpreter::Bash => "sh",
123            ScriptInterpreter::Cmd => "bat",
124            ScriptInterpreter::Powershell => "ps1",
125        }
126    }
127}
128
129/// The json response format for `/api/run` and `/api/runscript`.
130///
131/// # Serialized Examples
132/// A completed command:
133/// ```
134/// # let ser = r#"
135/// {
136///     "id": 73001,
137///     "status": "Completed",
138///     "exit_code": 1,
139///     "time_taken": {
140///         "secs": 21,
141///         "nanos": 800000
142///     }
143/// }
144/// # "#;
145/// # let deser: rusty_runner_api::api::RunResponse
146/// #    = serde_json::from_str(ser).expect("failed parsing");
147/// # assert!(matches!(deser.status, rusty_runner_api::api::RunStatus::Completed { .. }));
148/// ```
149/// A command that could not be executed:
150/// ```
151/// # let ser = r#"
152/// {
153///     "id": 1234567890,
154///     "status": "Failure",
155///     "reason": "Not supported"
156/// }
157/// # "#;
158/// # let deser: rusty_runner_api::api::RunResponse
159/// #    = serde_json::from_str(ser).expect("failed parsing");
160/// # assert!(matches!(deser.status, rusty_runner_api::api::RunStatus::Failure { .. }));
161/// ```
162#[derive(Debug, Serialize, Deserialize)]
163pub struct RunResponse {
164    pub id: u64,
165    #[serde(flatten)]
166    pub status: RunStatus,
167}
168
169/// The outcome of a command.
170///
171/// If the command could be started, then this is a [`Completed`](RunStatus::Completed)
172/// even if the command itself exited non-successfully.
173/// Otherwise this is [`Failure`](RunStatus::Failure).
174#[derive(Debug, Serialize, Deserialize)]
175#[serde(tag = "status")]
176pub enum RunStatus {
177    /// Completely ran the command. The command may have succeeded of failed.
178    Completed {
179        /// Exit code of the command or -1001 if terminated by a signal.
180        /// This may get only return the least byte.
181        exit_code: i32,
182        /// The wall time it took to run.
183        time_taken: Duration,
184        /// If `return_stdout` is set, this returns the raw `stdout` bytes.
185        #[serde(skip_serializing_if = "Option::is_none")]
186        stdout: Option<Vec<u8>>,
187        /// If `return_stderr` is set, this returns the raw `stderr` bytes.
188        #[serde(skip_serializing_if = "Option::is_none")]
189        stderr: Option<Vec<u8>>,
190    },
191    /// Failed to run the command due to internal reasons.
192    /// Does not indicate a command that ran with a non-success exit code, but
193    /// rather that the command couldn't even be started.
194    Failure { reason: String },
195}