wcgi_host/
lib.rs

1//! Common abstractions for implementing a WCGI host.
2//!
3//! # Cargo Features
4//!
5//! - `schemars` - will enable JSON Schema generation for certain types using the
6//!   [`schemars`](https://crates.io/crates/schemars) crate
7
8use std::{
9    collections::HashMap,
10    fmt::{self, Display, Formatter},
11    str::FromStr,
12};
13
14use tokio::io::AsyncBufRead;
15
16mod cgi;
17
18/// The CGI dialect to use when running a CGI workload.
19#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
20#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
21pub enum CgiDialect {
22    /// The "official" CGI dialect, as defined by
23    /// [RFC 3875](https://www.ietf.org/rfc/rfc3875).
24    #[default]
25    Rfc3875,
26}
27
28impl CgiDialect {
29    pub fn prepare_environment_variables(
30        self,
31        parts: http::request::Parts,
32        env: &mut HashMap<String, String>,
33    ) {
34        match self {
35            CgiDialect::Rfc3875 => cgi::prepare_environment_variables(parts, env),
36        }
37    }
38
39    /// Extract the [`http::response::Parts`] from a CGI script's stdout.
40    ///
41    /// # Note
42    ///
43    /// This might stall if reading from stdout stalls. Care should be taken to
44    /// avoid waiting forever (e.g. by adding a timeout).
45    pub async fn extract_response_header(
46        self,
47        stdout: &mut (impl AsyncBufRead + Unpin),
48    ) -> Result<http::response::Parts, CgiError> {
49        match self {
50            CgiDialect::Rfc3875 => cgi::extract_response_header(stdout).await,
51        }
52    }
53
54    pub const fn to_str(self) -> &'static str {
55        match self {
56            CgiDialect::Rfc3875 => "rfc-3875",
57        }
58    }
59}
60
61impl FromStr for CgiDialect {
62    type Err = UnknownCgiDialect;
63
64    fn from_str(s: &str) -> Result<Self, Self::Err> {
65        match s {
66            "rfc-3875" => Ok(CgiDialect::Rfc3875),
67            _ => Err(UnknownCgiDialect),
68        }
69    }
70}
71
72impl Display for CgiDialect {
73    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
74        write!(f, "{}", self.to_str())
75    }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct UnknownCgiDialect;
80
81impl Display for UnknownCgiDialect {
82    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
83        write!(f, "Unknown CGI dialect")
84    }
85}
86
87impl std::error::Error for UnknownCgiDialect {}
88
89#[derive(Debug)]
90pub enum CgiError {
91    StdoutRead(std::io::Error),
92    InvalidHeaders {
93        error: http::Error,
94        header: String,
95        value: String,
96    },
97    MalformedWcgiHeader {
98        error: ::wcgi::WcgiError,
99        header: String,
100    },
101}
102
103impl std::error::Error for CgiError {
104    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
105        match self {
106            CgiError::StdoutRead(e) => Some(e),
107            CgiError::InvalidHeaders { error, .. } => Some(error),
108            CgiError::MalformedWcgiHeader { error, .. } => error.source(),
109        }
110    }
111}
112
113impl fmt::Display for CgiError {
114    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
115        match self {
116            CgiError::StdoutRead(_) => write!(f, "Unable to read the STDOUT pipe"),
117            CgiError::InvalidHeaders { header, value, .. } => {
118                write!(f, "Unable to parse header ({header}: {value})")
119            }
120            CgiError::MalformedWcgiHeader { header, .. } => {
121                write!(f, "Unable to parse WCGI header ({header})")
122            }
123        }
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn round_trip_cgi_dialect_to_string() {
133        let dialects = [CgiDialect::Rfc3875];
134
135        for dialect in dialects {
136            let repr = dialect.to_string();
137            let round_tripped: CgiDialect = repr.parse().unwrap();
138            assert_eq!(round_tripped, dialect);
139        }
140    }
141}