rocket/config/
ident.rs

1use std::fmt;
2
3use serde::{Deserialize, Serialize};
4use serde::de::{self, Deserializer};
5
6use crate::http::Header;
7
8/// An identifier (or `None`) to send as the `Server` header.
9///
10/// # Deserialization
11///
12/// An `Ident` deserializes from any of the following:
13///
14/// * `string`
15///
16///   The string must be a valid `Ident`. See [`Ident::try_new()`] for details.
17///
18/// * `boolean`
19///
20///   The boolean must be `false`. The value will be [`Ident::none()`].
21///
22/// * `Option<string>`
23///
24///   If `Some`, this is the same as deserializing from the inner string. If
25///   `None`, the value is [`Ident::none()`].
26///
27/// * `unit`
28///
29///   Always deserializes as [`Ident::none()`].
30///
31/// # Examples
32///
33/// As with all Rocket configuration options, when using the default
34/// [`Config::figment()`](crate::Config::figment()), `Ident` can be configured
35/// via a `Rocket.toml` file. When no value for `ident` is provided, the value
36/// defaults to `"Rocket"`. Because a default is provided, configuration only
37/// needs to provided to customize or remove the value.
38///
39/// ```rust
40/// # use rocket::figment::{Figment, providers::{Format, Toml}};
41/// use rocket::config::{Config, Ident};
42///
43/// // If these are the contents of `Rocket.toml`...
44/// # let toml = Toml::string(r#"
45/// [default]
46/// ident = false
47/// # "#).nested();
48///
49/// // The config parses as follows:
50/// # let config = Config::from(Figment::from(Config::debug_default()).merge(toml));
51/// assert_eq!(config.ident, Ident::none());
52///
53/// // If these are the contents of `Rocket.toml`...
54/// # let toml = Toml::string(r#"
55/// [default]
56/// ident = "My Server"
57/// # "#).nested();
58///
59/// // The config parses as follows:
60/// # let config = Config::from(Figment::from(Config::debug_default()).merge(toml));
61/// assert_eq!(config.ident, Ident::try_new("My Server").unwrap());
62/// ```
63///
64/// The following example illustrates manual configuration:
65///
66/// ```rust
67/// use rocket::config::{Config, Ident};
68///
69/// let figment = rocket::Config::figment().merge(("ident", false));
70/// let config = rocket::Config::from(figment);
71/// assert_eq!(config.ident, Ident::none());
72///
73/// let figment = rocket::Config::figment().merge(("ident", "Fancy/1.0"));
74/// let config = rocket::Config::from(figment);
75/// assert_eq!(config.ident, Ident::try_new("Fancy/1.0").unwrap());
76/// ```
77#[derive(Debug, Clone, PartialEq, Serialize)]
78pub struct Ident(Option<String>);
79
80macro_rules! ident {
81    ($value:expr) => {
82        {
83            #[allow(unknown_lints, eq_op)]
84            const _: [(); 0 - !{
85                const ASSERT: bool = $crate::http::Header::is_valid_value($value, false);
86                ASSERT
87            } as usize] = [];
88
89            $crate::config::Ident::try_new($value).unwrap()
90        }
91    }
92}
93
94impl Ident {
95    /// Returns a new `Ident` with the string `ident`.
96    ///
97    /// When configured as the [`Config::ident`](crate::Config::ident), Rocket
98    /// will set a `Server` header with the `ident` value on all responses.
99    ///
100    /// # Errors
101    ///
102    /// The string `ident` must be non-empty and may only contain visible ASCII
103    /// characters. The first character cannot be whitespace. The only
104    /// whitespace characters allowed are ` ` (space) and `\t` (horizontal tab).
105    /// The string is returned wrapped in `Err` if it contains any invalid
106    /// characters.
107    ///
108    /// # Example
109    ///
110    /// ```rust
111    /// use rocket::config::Ident;
112    ///
113    /// let ident = Ident::try_new("Rocket").unwrap();
114    /// assert_eq!(ident.as_str(), Some("Rocket"));
115    ///
116    /// let ident = Ident::try_new("Rocket Run").unwrap();
117    /// assert_eq!(ident.as_str(), Some("Rocket Run"));
118    ///
119    /// let ident = Ident::try_new(" Rocket");
120    /// assert!(ident.is_err());
121    ///
122    /// let ident = Ident::try_new("Rocket\nRun");
123    /// assert!(ident.is_err());
124    ///
125    /// let ident = Ident::try_new("\tShip");
126    /// assert!(ident.is_err());
127    /// ```
128    pub fn try_new<S: Into<String>>(ident: S) -> Result<Ident, String> {
129        // This is a little more lenient than reality.
130        let ident = ident.into();
131        if !Header::is_valid_value(&ident, false) {
132            return Err(ident);
133        }
134
135        Ok(Ident(Some(ident)))
136    }
137
138    /// Returns a new `Ident` which is `None`.
139    ///
140    /// When configured as the [`Config::ident`](crate::Config::ident), Rocket
141    /// will not set a `Server` header on any response. Any `Server` header
142    /// emitted by the application will still be written out.
143    ///
144    /// # Example
145    ///
146    /// ```rust
147    /// use rocket::config::Ident;
148    ///
149    /// let ident = Ident::none();
150    /// assert_eq!(ident.as_str(), None);
151    /// ```
152    pub const fn none() -> Ident {
153        Ident(None)
154    }
155
156    /// Returns `self` as an `Option<&str>`.
157    ///
158    /// # Example
159    ///
160    /// ```rust
161    /// use rocket::config::Ident;
162    ///
163    /// let ident = Ident::try_new("Rocket").unwrap();
164    /// assert_eq!(ident.as_str(), Some("Rocket"));
165    ///
166    /// let ident = Ident::try_new("Rocket/1 (Unix)").unwrap();
167    /// assert_eq!(ident.as_str(), Some("Rocket/1 (Unix)"));
168    ///
169    /// let ident = Ident::none();
170    /// assert_eq!(ident.as_str(), None);
171    /// ```
172    pub fn as_str(&self) -> Option<&str> {
173        self.0.as_deref()
174    }
175}
176
177impl<'de> Deserialize<'de> for Ident {
178    fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
179        struct Visitor;
180
181        impl<'de> de::Visitor<'de> for Visitor {
182            type Value = Ident;
183
184            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
185                formatter.write_str("a server ident string or `false`")
186            }
187
188            fn visit_bool<E: de::Error>(self, v: bool) -> Result<Self::Value, E> {
189                if !v {
190                    return Ok(Ident::none());
191                }
192
193                Err(E::invalid_value(de::Unexpected::Bool(v), &self))
194            }
195
196            fn visit_some<D>(self, de: D) -> Result<Self::Value, D::Error>
197                where D: Deserializer<'de>
198            {
199                de.deserialize_string(self)
200            }
201
202            fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
203                Ok(Ident::none())
204            }
205
206            fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
207                Ok(Ident::none())
208            }
209
210            fn visit_string<E: de::Error>(self, v: String) -> Result<Self::Value, E> {
211                Ident::try_new(v)
212                    .map_err(|s| E::invalid_value(de::Unexpected::Str(&s), &self))
213            }
214
215            fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
216                self.visit_string(v.into())
217            }
218        }
219
220        de.deserialize_string(Visitor)
221    }
222}
223
224impl fmt::Display for Ident {
225    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226        match self.as_str() {
227            Some(name) => name.fmt(f),
228            None => "disabled".fmt(f),
229        }
230    }
231}
232
233/// The default `Ident` is `"Rocket"`.
234impl Default for Ident {
235    fn default() -> Self {
236        ident!("Rocket")
237    }
238}