rocket/config/
config.rs

1use std::net::{IpAddr, Ipv4Addr};
2
3use figment::{Figment, Profile, Provider, Metadata, error::Result};
4use figment::providers::{Serialized, Env, Toml, Format};
5use figment::value::{Map, Dict, magic::RelativePathBuf};
6use serde::{Deserialize, Serialize};
7use yansi::{Paint, Style, Color::Primary};
8
9use crate::log::PaintExt;
10use crate::config::{LogLevel, Shutdown, Ident};
11use crate::request::{self, Request, FromRequest};
12use crate::http::uncased::Uncased;
13use crate::data::Limits;
14
15#[cfg(feature = "tls")]
16use crate::config::TlsConfig;
17
18#[cfg(feature = "secrets")]
19use crate::config::SecretKey;
20
21/// Rocket server configuration.
22///
23/// See the [module level docs](crate::config) as well as the [configuration
24/// guide] for further details.
25///
26/// [configuration guide]: https://rocket.rs/v0.5/guide/configuration/
27///
28/// # Defaults
29///
30/// All configuration values have a default, documented in the [fields](#fields)
31/// section below. [`Config::debug_default()`] returns the default values for
32/// the debug profile while [`Config::release_default()`] the default values for
33/// the release profile. The [`Config::default()`] method automatically selects
34/// the appropriate of the two based on the selected profile. With the exception
35/// of `log_level`, which is `normal` in `debug` and `critical` in `release`,
36/// and `secret_key`, which is regenerated from a random value if not set in
37/// "debug" mode only, all default values are identical in all profiles.
38///
39/// # Provider Details
40///
41/// `Config` is a Figment [`Provider`] with the following characteristics:
42///
43///   * **Profile**
44///
45///     The profile is set to the value of the `profile` field.
46///
47///   * **Metadata**
48///
49///     This provider is named `Rocket Config`. It does not specify a
50///     [`Source`](figment::Source) and uses default interpolation.
51///
52///   * **Data**
53///
54///     The data emitted by this provider are the keys and values corresponding
55///     to the fields and values of the structure. The dictionary is emitted to
56///     the "default" meta-profile.
57///
58/// Note that these behaviors differ from those of [`Config::figment()`].
59#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
60pub struct Config {
61    /// The selected profile. **(default: _debug_ `debug` / _release_ `release`)**
62    ///
63    /// _**Note:** This field is never serialized nor deserialized. When a
64    /// `Config` is merged into a `Figment` as a `Provider`, this profile is
65    /// selected on the `Figment`. When a `Config` is extracted, this field is
66    /// set to the extracting Figment's selected `Profile`._
67    #[serde(skip)]
68    pub profile: Profile,
69    /// IP address to serve on. **(default: `127.0.0.1`)**
70    pub address: IpAddr,
71    /// Port to serve on. **(default: `8000`)**
72    pub port: u16,
73    /// Number of threads to use for executing futures. **(default: `num_cores`)**
74    ///
75    /// _**Note:** Rocket only reads this value from sources in the [default
76    /// provider](Config::figment())._
77    pub workers: usize,
78    /// Limit on threads to start for blocking tasks. **(default: `512`)**
79    pub max_blocking: usize,
80    /// How, if at all, to identify the server via the `Server` header.
81    /// **(default: `"Rocket"`)**
82    pub ident: Ident,
83    /// The name of a header, whose value is typically set by an intermediary
84    /// server or proxy, which contains the real IP address of the connecting
85    /// client. Used internally and by [`Request::client_ip()`] and
86    /// [`Request::real_ip()`].
87    ///
88    /// To disable using any header for this purpose, set this value to `false`.
89    /// Deserialization semantics are identical to those of [`Ident`] except
90    /// that the value must syntactically be a valid HTTP header name.
91    ///
92    /// **(default: `"X-Real-IP"`)**
93    #[serde(deserialize_with = "crate::config::ip_header::deserialize")]
94    pub ip_header: Option<Uncased<'static>>,
95    /// Streaming read size limits. **(default: [`Limits::default()`])**
96    pub limits: Limits,
97    /// Directory to store temporary files in. **(default:
98    /// [`std::env::temp_dir()`])**
99    #[serde(serialize_with = "RelativePathBuf::serialize_relative")]
100    pub temp_dir: RelativePathBuf,
101    /// Keep-alive timeout in seconds; disabled when `0`. **(default: `5`)**
102    pub keep_alive: u32,
103    /// The TLS configuration, if any. **(default: `None`)**
104    #[cfg(feature = "tls")]
105    #[cfg_attr(nightly, doc(cfg(feature = "tls")))]
106    pub tls: Option<TlsConfig>,
107    /// The secret key for signing and encrypting. **(default: `0`)**
108    ///
109    /// _**Note:** This field _always_ serializes as a 256-bit array of `0`s to
110    /// aid in preventing leakage of the secret key._
111    #[cfg(feature = "secrets")]
112    #[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
113    #[serde(serialize_with = "SecretKey::serialize_zero")]
114    pub secret_key: SecretKey,
115    /// Graceful shutdown configuration. **(default: [`Shutdown::default()`])**
116    pub shutdown: Shutdown,
117    /// Max level to log. **(default: _debug_ `normal` / _release_ `critical`)**
118    pub log_level: LogLevel,
119    /// Whether to use colors and emoji when logging. **(default: `true`)**
120    #[serde(deserialize_with = "figment::util::bool_from_str_or_int")]
121    pub cli_colors: bool,
122    /// PRIVATE: This structure may grow (but never change otherwise) in a
123    /// non-breaking release. As such, constructing this structure should
124    /// _always_ be done using a public constructor or update syntax:
125    ///
126    /// ```rust
127    /// use rocket::Config;
128    ///
129    /// let config = Config {
130    ///     port: 1024,
131    ///     keep_alive: 10,
132    ///     ..Default::default()
133    /// };
134    /// ```
135    #[doc(hidden)]
136    #[serde(skip)]
137    pub __non_exhaustive: (),
138}
139
140impl Default for Config {
141    /// Returns the default configuration based on the Rust compilation profile.
142    /// This is [`Config::debug_default()`] in `debug` and
143    /// [`Config::release_default()`] in `release`.
144    ///
145    /// # Example
146    ///
147    /// ```rust
148    /// use rocket::Config;
149    ///
150    /// let config = Config::default();
151    /// ```
152    fn default() -> Config {
153        #[cfg(debug_assertions)] { Config::debug_default() }
154        #[cfg(not(debug_assertions))] { Config::release_default() }
155    }
156}
157
158impl Config {
159    const DEPRECATED_KEYS: &'static [(&'static str, Option<&'static str>)] = &[
160        ("env", Some(Self::PROFILE)), ("log", Some(Self::LOG_LEVEL)),
161        ("read_timeout", None), ("write_timeout", None),
162    ];
163
164    const DEPRECATED_PROFILES: &'static [(&'static str, Option<&'static str>)] = &[
165        ("dev", Some("debug")), ("prod", Some("release")), ("stag", None)
166    ];
167
168    /// Returns the default configuration for the `debug` profile, _irrespective
169    /// of the Rust compilation profile_ and `ROCKET_PROFILE`.
170    ///
171    /// This may differ from the configuration used by default,
172    /// [`Config::default()`], which is selected based on the Rust compilation
173    /// profile. See [defaults](#defaults) and [provider
174    /// details](#provider-details) for specifics.
175    ///
176    /// # Example
177    ///
178    /// ```rust
179    /// use rocket::Config;
180    ///
181    /// let config = Config::debug_default();
182    /// ```
183    pub fn debug_default() -> Config {
184        Config {
185            profile: Self::DEBUG_PROFILE,
186            address: Ipv4Addr::new(127, 0, 0, 1).into(),
187            port: 8000,
188            workers: num_cpus::get(),
189            max_blocking: 512,
190            ident: Ident::default(),
191            ip_header: Some(Uncased::from_borrowed("X-Real-IP")),
192            limits: Limits::default(),
193            temp_dir: std::env::temp_dir().into(),
194            keep_alive: 5,
195            #[cfg(feature = "tls")]
196            tls: None,
197            #[cfg(feature = "secrets")]
198            secret_key: SecretKey::zero(),
199            shutdown: Shutdown::default(),
200            log_level: LogLevel::Normal,
201            cli_colors: true,
202            __non_exhaustive: (),
203        }
204    }
205
206    /// Returns the default configuration for the `release` profile,
207    /// _irrespective of the Rust compilation profile_ and `ROCKET_PROFILE`.
208    ///
209    /// This may differ from the configuration used by default,
210    /// [`Config::default()`], which is selected based on the Rust compilation
211    /// profile. See [defaults](#defaults) and [provider
212    /// details](#provider-details) for specifics.
213    ///
214    /// # Example
215    ///
216    /// ```rust
217    /// use rocket::Config;
218    ///
219    /// let config = Config::release_default();
220    /// ```
221    pub fn release_default() -> Config {
222        Config {
223            profile: Self::RELEASE_PROFILE,
224            log_level: LogLevel::Critical,
225            ..Config::debug_default()
226        }
227    }
228
229    /// Returns the default provider figment used by [`rocket::build()`].
230    ///
231    /// The default figment reads from the following sources, in ascending
232    /// priority order:
233    ///
234    ///   1. [`Config::default()`] (see [defaults](#defaults))
235    ///   2. `Rocket.toml` _or_ filename in `ROCKET_CONFIG` environment variable
236    ///   3. `ROCKET_` prefixed environment variables
237    ///
238    /// The profile selected is the value set in the `ROCKET_PROFILE`
239    /// environment variable. If it is not set, it defaults to `debug` when
240    /// compiled in debug mode and `release` when compiled in release mode.
241    ///
242    /// [`rocket::build()`]: crate::build()
243    ///
244    /// # Example
245    ///
246    /// ```rust
247    /// use rocket::Config;
248    /// use serde::Deserialize;
249    ///
250    /// #[derive(Deserialize)]
251    /// struct MyConfig {
252    ///     app_key: String,
253    /// }
254    ///
255    /// let my_config = Config::figment().extract::<MyConfig>();
256    /// ```
257    pub fn figment() -> Figment {
258        Figment::from(Config::default())
259            .merge(Toml::file(Env::var_or("ROCKET_CONFIG", "Rocket.toml")).nested())
260            .merge(Env::prefixed("ROCKET_").ignore(&["PROFILE"]).global())
261            .select(Profile::from_env_or("ROCKET_PROFILE", Self::DEFAULT_PROFILE))
262    }
263
264    /// Attempts to extract a `Config` from `provider`, returning the result.
265    ///
266    /// # Example
267    ///
268    /// ```rust
269    /// use rocket::Config;
270    /// use rocket::figment::providers::{Toml, Format, Env};
271    ///
272    /// // Use Rocket's default `Figment`, but allow values from `MyApp.toml`
273    /// // and `MY_APP_` prefixed environment variables to supersede its values.
274    /// let figment = Config::figment()
275    ///     .merge(("some-thing", 123))
276    ///     .merge(Env::prefixed("CONFIG_"));
277    ///
278    /// let config = Config::try_from(figment);
279    /// ```
280    pub fn try_from<T: Provider>(provider: T) -> Result<Self> {
281        let figment = Figment::from(provider);
282        let mut config = figment.extract::<Self>()?;
283        config.profile = figment.profile().clone();
284        Ok(config)
285    }
286
287    /// Extract a `Config` from `provider`, panicking if extraction fails.
288    ///
289    /// # Panics
290    ///
291    /// If extraction fails, prints an error message indicating the error and
292    /// panics. For a version that doesn't panic, use [`Config::try_from()`].
293    ///
294    /// # Example
295    ///
296    /// ```rust
297    /// use rocket::Config;
298    /// use rocket::figment::providers::{Toml, Format, Env};
299    ///
300    /// // Use Rocket's default `Figment`, but allow values from `MyApp.toml`
301    /// // and `MY_APP_` prefixed environment variables to supersede its values.
302    /// let figment = Config::figment()
303    ///     .merge(Toml::file("MyApp.toml").nested())
304    ///     .merge(Env::prefixed("MY_APP_"));
305    ///
306    /// let config = Config::from(figment);
307    /// ```
308    pub fn from<T: Provider>(provider: T) -> Self {
309        Self::try_from(provider).unwrap_or_else(bail_with_config_error)
310    }
311
312    /// Returns `true` if TLS is enabled.
313    ///
314    /// TLS is enabled when the `tls` feature is enabled and TLS has been
315    /// configured with at least one ciphersuite. Note that without changing
316    /// defaults, all supported ciphersuites are enabled in the recommended
317    /// configuration.
318    ///
319    /// # Example
320    ///
321    /// ```rust
322    /// let config = rocket::Config::default();
323    /// if config.tls_enabled() {
324    ///     println!("TLS is enabled!");
325    /// } else {
326    ///     println!("TLS is disabled.");
327    /// }
328    /// ```
329    pub fn tls_enabled(&self) -> bool {
330        #[cfg(feature = "tls")] {
331            self.tls.as_ref().map_or(false, |tls| !tls.ciphers.is_empty())
332        }
333
334        #[cfg(not(feature = "tls"))] { false }
335    }
336
337    /// Returns `true` if mTLS is enabled.
338    ///
339    /// mTLS is enabled when TLS is enabled ([`Config::tls_enabled()`]) _and_
340    /// the `mtls` feature is enabled _and_ mTLS has been configured with a CA
341    /// certificate chain.
342    ///
343    /// # Example
344    ///
345    /// ```rust
346    /// let config = rocket::Config::default();
347    /// if config.mtls_enabled() {
348    ///     println!("mTLS is enabled!");
349    /// } else {
350    ///     println!("mTLS is disabled.");
351    /// }
352    /// ```
353    pub fn mtls_enabled(&self) -> bool {
354        if !self.tls_enabled() {
355            return false;
356        }
357
358        #[cfg(feature = "mtls")] {
359            self.tls.as_ref().map_or(false, |tls| tls.mutual.is_some())
360        }
361
362        #[cfg(not(feature = "mtls"))] { false }
363    }
364
365    #[cfg(feature = "secrets")]
366    pub(crate) fn known_secret_key_used(&self) -> bool {
367        const KNOWN_SECRET_KEYS: &'static [&'static str] = &[
368            "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk="
369        ];
370
371        KNOWN_SECRET_KEYS.iter().any(|&key_str| {
372            let value = figment::value::Value::from(key_str);
373            self.secret_key == value.deserialize().expect("known key is valid")
374        })
375    }
376
377    #[inline]
378    pub(crate) fn trace_print(&self, figment: &Figment) {
379        if self.log_level != LogLevel::Debug {
380            return;
381        }
382
383        trace!("-- configuration trace information --");
384        for param in Self::PARAMETERS {
385            if let Some(meta) = figment.find_metadata(param) {
386                let (param, name) = (param.blue(), meta.name.primary());
387                if let Some(ref source) = meta.source {
388                    trace_!("{:?} parameter source: {} ({})", param, name, source);
389                } else {
390                    trace_!("{:?} parameter source: {}", param, name);
391                }
392            }
393        }
394    }
395
396    pub(crate) fn pretty_print(&self, figment: &Figment) {
397        static VAL: Style = Primary.bold();
398
399        self.trace_print(figment);
400        launch_meta!("{}Configured for {}.", "🔧 ".emoji(), self.profile.underline());
401        launch_meta_!("address: {}", self.address.paint(VAL));
402        launch_meta_!("port: {}", self.port.paint(VAL));
403        launch_meta_!("workers: {}", self.workers.paint(VAL));
404        launch_meta_!("max blocking threads: {}", self.max_blocking.paint(VAL));
405        launch_meta_!("ident: {}", self.ident.paint(VAL));
406
407        match self.ip_header {
408            Some(ref name) => launch_meta_!("IP header: {}", name.paint(VAL)),
409            None => launch_meta_!("IP header: {}", "disabled".paint(VAL))
410        }
411
412        launch_meta_!("limits: {}", (&self.limits).paint(VAL));
413        launch_meta_!("temp dir: {}", self.temp_dir.relative().display().paint(VAL));
414        launch_meta_!("http/2: {}", (cfg!(feature = "http2").paint(VAL)));
415
416        match self.keep_alive {
417            0 => launch_meta_!("keep-alive: {}", "disabled".paint(VAL)),
418            ka => launch_meta_!("keep-alive: {}{}", ka.paint(VAL), "s".paint(VAL)),
419        }
420
421        match (self.tls_enabled(), self.mtls_enabled()) {
422            (true, true) => launch_meta_!("tls: {}", "enabled w/mtls".paint(VAL)),
423            (true, false) => launch_meta_!("tls: {} w/o mtls", "enabled".paint(VAL)),
424            (false, _) => launch_meta_!("tls: {}", "disabled".paint(VAL)),
425        }
426
427        launch_meta_!("shutdown: {}", self.shutdown.paint(VAL));
428        launch_meta_!("log level: {}", self.log_level.paint(VAL));
429        launch_meta_!("cli colors: {}", self.cli_colors.paint(VAL));
430
431        // Check for now deprecated config values.
432        for (key, replacement) in Self::DEPRECATED_KEYS {
433            if let Some(md) = figment.find_metadata(key) {
434                warn!("found value for deprecated config key `{}`", key.paint(VAL));
435                if let Some(ref source) = md.source {
436                    launch_meta_!("in {} {}", source.paint(VAL), md.name);
437                }
438
439                if let Some(new_key) = replacement {
440                    launch_meta_!("key has been by replaced by `{}`", new_key.paint(VAL));
441                } else {
442                    launch_meta_!("key has no special meaning");
443                }
444            }
445        }
446
447        // Check for now removed config values.
448        for (prefix, replacement) in Self::DEPRECATED_PROFILES {
449            if let Some(profile) = figment.profiles().find(|p| p.starts_with(prefix)) {
450                warn!("found set deprecated profile `{}`", profile.paint(VAL));
451
452                if let Some(new_profile) = replacement {
453                    launch_meta_!("profile was replaced by `{}`", new_profile.paint(VAL));
454                } else {
455                    launch_meta_!("profile `{}` has no special meaning", profile);
456                }
457            }
458        }
459
460        #[cfg(feature = "secrets")] {
461            launch_meta_!("secret key: {}", self.secret_key.paint(VAL));
462            if !self.secret_key.is_provided() {
463                warn!("secrets enabled without a stable `secret_key`");
464                launch_meta_!("disable `secrets` feature or configure a `secret_key`");
465                launch_meta_!("this becomes an {} in non-debug profiles", "error".red());
466            }
467        }
468    }
469}
470
471/// Associated constants for default profiles.
472impl Config {
473    /// The default debug profile: `debug`.
474    pub const DEBUG_PROFILE: Profile = Profile::const_new("debug");
475
476    /// The default release profile: `release`.
477    pub const RELEASE_PROFILE: Profile = Profile::const_new("release");
478
479    /// The default profile: "debug" on `debug`, "release" on `release`.
480    #[cfg(debug_assertions)]
481    pub const DEFAULT_PROFILE: Profile = Self::DEBUG_PROFILE;
482
483    /// The default profile: "debug" on `debug`, "release" on `release`.
484    #[cfg(not(debug_assertions))]
485    pub const DEFAULT_PROFILE: Profile = Self::RELEASE_PROFILE;
486}
487
488/// Associated constants for stringy versions of configuration parameters.
489impl Config {
490    /// The stringy parameter name for setting/extracting [`Config::profile`].
491    ///
492    /// This isn't `pub` because setting it directly does nothing.
493    const PROFILE: &'static str = "profile";
494
495    /// The stringy parameter name for setting/extracting [`Config::address`].
496    pub const ADDRESS: &'static str = "address";
497
498    /// The stringy parameter name for setting/extracting [`Config::port`].
499    pub const PORT: &'static str = "port";
500
501    /// The stringy parameter name for setting/extracting [`Config::workers`].
502    pub const WORKERS: &'static str = "workers";
503
504    /// The stringy parameter name for setting/extracting [`Config::max_blocking`].
505    pub const MAX_BLOCKING: &'static str = "max_blocking";
506
507    /// The stringy parameter name for setting/extracting [`Config::keep_alive`].
508    pub const KEEP_ALIVE: &'static str = "keep_alive";
509
510    /// The stringy parameter name for setting/extracting [`Config::ident`].
511    pub const IDENT: &'static str = "ident";
512
513    /// The stringy parameter name for setting/extracting [`Config::ip_header`].
514    pub const IP_HEADER: &'static str = "ip_header";
515
516    /// The stringy parameter name for setting/extracting [`Config::limits`].
517    pub const LIMITS: &'static str = "limits";
518
519    /// The stringy parameter name for setting/extracting [`Config::tls`].
520    pub const TLS: &'static str = "tls";
521
522    /// The stringy parameter name for setting/extracting [`Config::secret_key`].
523    pub const SECRET_KEY: &'static str = "secret_key";
524
525    /// The stringy parameter name for setting/extracting [`Config::temp_dir`].
526    pub const TEMP_DIR: &'static str = "temp_dir";
527
528    /// The stringy parameter name for setting/extracting [`Config::log_level`].
529    pub const LOG_LEVEL: &'static str = "log_level";
530
531    /// The stringy parameter name for setting/extracting [`Config::shutdown`].
532    pub const SHUTDOWN: &'static str = "shutdown";
533
534    /// The stringy parameter name for setting/extracting [`Config::cli_colors`].
535    pub const CLI_COLORS: &'static str = "cli_colors";
536
537    /// An array of all of the stringy parameter names.
538    pub const PARAMETERS: &'static [&'static str] = &[
539        Self::ADDRESS, Self::PORT, Self::WORKERS, Self::MAX_BLOCKING,
540        Self::KEEP_ALIVE, Self::IDENT, Self::IP_HEADER, Self::LIMITS, Self::TLS,
541        Self::SECRET_KEY, Self::TEMP_DIR, Self::LOG_LEVEL, Self::SHUTDOWN,
542        Self::CLI_COLORS,
543    ];
544}
545
546impl Provider for Config {
547    #[track_caller]
548    fn metadata(&self) -> Metadata {
549        if self == &Config::default() {
550            Metadata::named("rocket::Config::default()")
551        } else {
552            Metadata::named("rocket::Config")
553        }
554    }
555
556    #[track_caller]
557    fn data(&self) -> Result<Map<Profile, Dict>> {
558        #[allow(unused_mut)]
559        let mut map: Map<Profile, Dict> = Serialized::defaults(self).data()?;
560
561        // We need to special-case `secret_key` since its serializer zeroes.
562        #[cfg(feature = "secrets")]
563        if !self.secret_key.is_zero() {
564            if let Some(map) = map.get_mut(&Profile::Default) {
565                map.insert("secret_key".into(), self.secret_key.key.master().into());
566            }
567        }
568
569        Ok(map)
570    }
571
572    fn profile(&self) -> Option<Profile> {
573        Some(self.profile.clone())
574    }
575}
576
577#[crate::async_trait]
578impl<'r> FromRequest<'r> for &'r Config {
579    type Error = std::convert::Infallible;
580
581    async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
582        request::Outcome::Success(req.rocket().config())
583    }
584}
585
586#[doc(hidden)]
587pub fn bail_with_config_error<T>(error: figment::Error) -> T {
588    pretty_print_error(error);
589    panic!("aborting due to configuration error(s)")
590}
591
592#[doc(hidden)]
593pub fn pretty_print_error(error: figment::Error) {
594    use figment::error::{Kind, OneOf};
595
596    crate::log::init_default();
597    error!("Failed to extract valid configuration.");
598    for e in error {
599        fn w<T>(v: T) -> yansi::Painted<T> { Paint::new(v).primary() }
600
601        match e.kind {
602            Kind::Message(msg) => error_!("{}", msg),
603            Kind::InvalidType(v, exp) => {
604                error_!("invalid type: found {}, expected {}", w(v), w(exp));
605            }
606            Kind::InvalidValue(v, exp) => {
607                error_!("invalid value {}, expected {}", w(v), w(exp));
608            },
609            Kind::InvalidLength(v, exp) => {
610                error_!("invalid length {}, expected {}", w(v), w(exp))
611            },
612            Kind::UnknownVariant(v, exp) => {
613                error_!("unknown variant: found `{}`, expected `{}`", w(v), w(OneOf(exp)))
614            }
615            Kind::UnknownField(v, exp) => {
616                error_!("unknown field: found `{}`, expected `{}`", w(v), w(OneOf(exp)))
617            }
618            Kind::MissingField(v) => {
619                error_!("missing field `{}`", w(v))
620            }
621            Kind::DuplicateField(v) => {
622                error_!("duplicate field `{}`", w(v))
623            }
624            Kind::ISizeOutOfRange(v) => {
625                error_!("signed integer `{}` is out of range", w(v))
626            }
627            Kind::USizeOutOfRange(v) => {
628                error_!("unsigned integer `{}` is out of range", w(v))
629            }
630            Kind::Unsupported(v) => {
631                error_!("unsupported type `{}`", w(v))
632            }
633            Kind::UnsupportedKey(a, e) => {
634                error_!("unsupported type `{}` for key: must be `{}`", w(a), w(e))
635            }
636        }
637
638        if let (Some(ref profile), Some(ref md)) = (&e.profile, &e.metadata) {
639            if !e.path.is_empty() {
640                let key = md.interpolate(profile, &e.path);
641                info_!("for key {}", w(key));
642            }
643        }
644
645        if let Some(md) = e.metadata {
646            if let Some(source) = md.source {
647                info_!("in {} {}", w(source), md.name);
648            } else {
649                info_!("in {}", w(md.name));
650            }
651        }
652    }
653}