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}