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}