rocket_community/config/
ident.rs

1use std::fmt;
2
3use serde::de::{self, Deserializer};
4use serde::{Deserialize, Serialize};
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/// # extern crate rocket_community as rocket;
41/// # use rocket::figment::{Figment, providers::{Format, Toml}};
42/// use rocket::config::{Config, Ident};
43///
44/// // If these are the contents of `Rocket.toml`...
45/// # let toml = Toml::string(r#"
46/// [default]
47/// ident = false
48/// # "#).nested();
49///
50/// // The config parses as follows:
51/// # let config = Config::from(Figment::from(Config::debug_default()).merge(toml));
52/// assert_eq!(config.ident, Ident::none());
53///
54/// // If these are the contents of `Rocket.toml`...
55/// # let toml = Toml::string(r#"
56/// [default]
57/// ident = "My Server"
58/// # "#).nested();
59///
60/// // The config parses as follows:
61/// # let config = Config::from(Figment::from(Config::debug_default()).merge(toml));
62/// assert_eq!(config.ident, Ident::try_new("My Server").unwrap());
63/// ```
64///
65/// The following example illustrates manual configuration:
66///
67/// ```rust
68/// # extern crate rocket_community as rocket;
69/// use rocket::config::{Config, Ident};
70///
71/// let figment = rocket::Config::figment().merge(("ident", false));
72/// let config = rocket::Config::from(figment);
73/// assert_eq!(config.ident, Ident::none());
74///
75/// let figment = rocket::Config::figment().merge(("ident", "Fancy/1.0"));
76/// let config = rocket::Config::from(figment);
77/// assert_eq!(config.ident, Ident::try_new("Fancy/1.0").unwrap());
78/// ```
79#[derive(Debug, Clone, PartialEq, Serialize)]
80pub struct Ident(Option<String>);
81
82macro_rules! ident {
83    ($value:expr) => {{
84        #[allow(unknown_lints)]
85        const _: [(); 0 - !{
86            const ASSERT: bool = $crate::http::Header::is_valid_value($value, false);
87            ASSERT
88        } as usize] = [];
89
90        $crate::config::Ident::try_new($value).unwrap()
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    /// # extern crate rocket_community as rocket;
112    /// use rocket::config::Ident;
113    ///
114    /// let ident = Ident::try_new("Rocket").unwrap();
115    /// assert_eq!(ident.as_str(), Some("Rocket"));
116    ///
117    /// let ident = Ident::try_new("Rocket Run").unwrap();
118    /// assert_eq!(ident.as_str(), Some("Rocket Run"));
119    ///
120    /// let ident = Ident::try_new(" Rocket");
121    /// assert!(ident.is_err());
122    ///
123    /// let ident = Ident::try_new("Rocket\nRun");
124    /// assert!(ident.is_err());
125    ///
126    /// let ident = Ident::try_new("\tShip");
127    /// assert!(ident.is_err());
128    /// ```
129    pub fn try_new<S: Into<String>>(ident: S) -> Result<Ident, String> {
130        // This is a little more lenient than reality.
131        let ident = ident.into();
132        if !Header::is_valid_value(&ident, false) {
133            return Err(ident);
134        }
135
136        Ok(Ident(Some(ident)))
137    }
138
139    /// Returns a new `Ident` which is `None`.
140    ///
141    /// When configured as the [`Config::ident`](crate::Config::ident), Rocket
142    /// will not set a `Server` header on any response. Any `Server` header
143    /// emitted by the application will still be written out.
144    ///
145    /// # Example
146    ///
147    /// ```rust
148    /// # extern crate rocket_community as rocket;
149    /// use rocket::config::Ident;
150    ///
151    /// let ident = Ident::none();
152    /// assert_eq!(ident.as_str(), None);
153    /// ```
154    pub const fn none() -> Ident {
155        Ident(None)
156    }
157
158    /// Returns `self` as an `Option<&str>`.
159    ///
160    /// # Example
161    ///
162    /// ```rust
163    /// # extern crate rocket_community as rocket;
164    /// use rocket::config::Ident;
165    ///
166    /// let ident = Ident::try_new("Rocket").unwrap();
167    /// assert_eq!(ident.as_str(), Some("Rocket"));
168    ///
169    /// let ident = Ident::try_new("Rocket/1 (Unix)").unwrap();
170    /// assert_eq!(ident.as_str(), Some("Rocket/1 (Unix)"));
171    ///
172    /// let ident = Ident::none();
173    /// assert_eq!(ident.as_str(), None);
174    /// ```
175    pub fn as_str(&self) -> Option<&str> {
176        self.0.as_deref()
177    }
178}
179
180impl<'de> Deserialize<'de> for Ident {
181    fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
182        struct Visitor;
183
184        impl<'de> de::Visitor<'de> for Visitor {
185            type Value = Ident;
186
187            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
188                formatter.write_str("a server ident string or `false`")
189            }
190
191            fn visit_bool<E: de::Error>(self, v: bool) -> Result<Self::Value, E> {
192                if !v {
193                    return Ok(Ident::none());
194                }
195
196                Err(E::invalid_value(de::Unexpected::Bool(v), &self))
197            }
198
199            fn visit_some<D>(self, de: D) -> Result<Self::Value, D::Error>
200            where
201                D: Deserializer<'de>,
202            {
203                de.deserialize_string(self)
204            }
205
206            fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
207                Ok(Ident::none())
208            }
209
210            fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
211                Ok(Ident::none())
212            }
213
214            fn visit_string<E: de::Error>(self, v: String) -> Result<Self::Value, E> {
215                Ident::try_new(v).map_err(|s| E::invalid_value(de::Unexpected::Str(&s), &self))
216            }
217
218            fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
219                self.visit_string(v.into())
220            }
221        }
222
223        de.deserialize_string(Visitor)
224    }
225}
226
227impl fmt::Display for Ident {
228    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229        match self.as_str() {
230            Some(name) => name.fmt(f),
231            None => "disabled".fmt(f),
232        }
233    }
234}
235
236/// The default `Ident` is `"Rocket"`.
237impl Default for Ident {
238    fn default() -> Self {
239        ident!("Rocket")
240    }
241}