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}