figment/value/magic.rs
1//! (De)serializable values that "magically" use information from the extracing
2//! [`Figment`](crate::Figment).
3
4use std::ops::Deref;
5use std::path::{PathBuf, Path};
6
7use serde::{Deserialize, Serialize, de};
8
9use crate::{Error, value::{ConfiguredValueDe, Interpreter, MapDe, Tag}};
10
11/// Marker trait for "magic" values. Primarily for use with [`Either`].
12pub trait Magic: for<'de> Deserialize<'de> {
13 /// The name of the deserialization pseudo-strucure.
14 #[doc(hidden)] const NAME: &'static str;
15
16 /// The fields of the pseudo-structure. The last one should be the value.
17 #[doc(hidden)] const FIELDS: &'static [&'static str];
18
19 #[doc(hidden)] fn deserialize_from<'de: 'c, 'c, V: de::Visitor<'de>, I: Interpreter>(
20 de: ConfiguredValueDe<'c, I>,
21 visitor: V
22 ) -> Result<V::Value, Error>;
23}
24
25/// A [`PathBuf`] that knows the path of the file it was configured in, if any.
26///
27/// Paths in configuration files are often desired to be relative to the
28/// configuration file itself. For example, a path of `a/b.html` configured in a
29/// file `/var/config.toml` might be desired to resolve as `/var/a/b.html`. This
30/// type makes this possible by simply delcaring the configuration value's type
31/// as [`RelativePathBuf`].
32///
33/// # Example
34///
35/// ```rust
36/// use std::path::Path;
37///
38/// use serde::{Deserialize, Serialize};
39/// use figment::{Figment, value::magic::RelativePathBuf, Jail};
40/// use figment::providers::{Env, Format, Toml, Serialized};
41///
42/// #[derive(Debug, PartialEq, Deserialize, Serialize)]
43/// struct Config {
44/// path: RelativePathBuf,
45/// }
46///
47/// Jail::expect_with(|jail| {
48/// // Note that `jail.directory()` is some non-empty path:
49/// assert_ne!(jail.directory(), Path::new("/"));
50///
51/// // When a path is declared in a file and deserialized as
52/// // `RelativePathBuf`, `relative()` will be relative to the file.
53/// jail.create_file("Config.toml", r#"path = "a/b/c.html""#)?;
54/// let c: Config = Figment::from(Toml::file("Config.toml")).extract()?;
55/// assert_eq!(c.path.original(), Path::new("a/b/c.html"));
56/// assert_eq!(c.path.relative(), jail.directory().join("a/b/c.html"));
57/// assert_ne!(c.path.relative(), Path::new("a/b/c.html"));
58///
59/// // Round-tripping a `RelativePathBuf` preserves path-relativity.
60/// let c: Config = Figment::from(Serialized::defaults(&c)).extract()?;
61/// assert_eq!(c.path.original(), Path::new("a/b/c.html"));
62/// assert_eq!(c.path.relative(), jail.directory().join("a/b/c.html"));
63/// assert_ne!(c.path.relative(), Path::new("a/b/c.html"));
64///
65/// // If a path is declared elsewhere, the "relative" path is the original.
66/// jail.set_env("PATH", "a/b/c.html");
67/// let c: Config = Figment::from(Toml::file("Config.toml"))
68/// .merge(Env::raw().only(&["PATH"]))
69/// .extract()?;
70///
71/// assert_eq!(c.path.original(), Path::new("a/b/c.html"));
72/// assert_eq!(c.path.relative(), Path::new("a/b/c.html"));
73///
74/// // Absolute paths remain unchanged.
75/// jail.create_file("Config.toml", r#"path = "/var/c.html""#);
76/// let c: Config = Figment::from(Toml::file("Config.toml")).extract()?;
77/// assert_eq!(c.path.original(), Path::new("/var/c.html"));
78/// assert_eq!(c.path.relative(), Path::new("/var/c.html"));
79///
80/// // You can use the `From<P: AsRef<Path>>` impl to set defaults:
81/// let figment = Figment::from(Serialized::defaults(Config {
82/// path: "some/default/path".into()
83/// }));
84///
85/// let default: Config = figment.extract()?;
86/// assert_eq!(default.path.original(), Path::new("some/default/path"));
87/// assert_eq!(default.path.relative(), Path::new("some/default/path"));
88///
89/// jail.create_file("Config.toml", r#"path = "an/override""#)?;
90/// let overriden: Config = figment.merge(Toml::file("Config.toml")).extract()?;
91/// assert_eq!(overriden.path.original(), Path::new("an/override"));
92/// assert_eq!(overriden.path.relative(), jail.directory().join("an/override"));
93///
94/// Ok(())
95/// });
96/// ```
97///
98/// # Serialization
99///
100/// By default, a `RelativePathBuf` serializes into a structure that can only
101/// deserialize as a `RelativePathBuf`. In particular, a `RelativePathBuf` does
102/// not serialize into a value compatible with `PathBuf`. To serialize into a
103/// `Path`, use [`RelativePathBuf::serialize_original()`] or
104/// [`RelativePathBuf::serialize_relative()`] together with serde's
105/// `serialize_with` field attribute:
106///
107/// ```rust
108/// use std::path::PathBuf;
109///
110/// use serde::{Deserialize, Serialize};
111/// use figment::{Figment, value::magic::RelativePathBuf, Jail};
112/// use figment::providers::{Format, Toml, Serialized};
113///
114/// #[derive(Deserialize, Serialize)]
115/// struct Config {
116/// relative: RelativePathBuf,
117/// #[serde(serialize_with = "RelativePathBuf::serialize_original")]
118/// root: RelativePathBuf,
119/// #[serde(serialize_with = "RelativePathBuf::serialize_relative")]
120/// temp: RelativePathBuf,
121/// }
122///
123/// Jail::expect_with(|jail| {
124/// jail.create_file("Config.toml", r#"
125/// relative = "relative/path"
126/// root = "root/path"
127/// temp = "temp/path"
128/// "#)?;
129///
130/// // Create a figment with a serialize `Config`.
131/// let figment = Figment::from(Toml::file("Config.toml"));
132/// let config = figment.extract::<Config>()?;
133/// let figment = Figment::from(Serialized::defaults(config));
134///
135/// // This fails, as expected.
136/// let relative = figment.extract_inner::<PathBuf>("relative");
137/// assert!(relative.is_err());
138///
139/// // These succeed. This one uses the originally written path.
140/// let root = figment.extract_inner::<PathBuf>("root")?;
141/// assert_eq!(root, PathBuf::from("root/path"));
142///
143/// // This one the magic relative path.
144/// let temp = figment.extract_inner::<PathBuf>("temp")?;
145/// assert_eq!(temp, jail.directory().join("temp/path"));
146///
147/// Ok(())
148/// })
149/// ```
150#[derive(Debug, Clone)]
151// #[derive(Deserialize, Serialize)]
152// #[serde(rename = "___figment_relative_path_buf")]
153pub struct RelativePathBuf {
154 // #[serde(rename = "___figment_relative_metadata_path")]
155 metadata_path: Option<PathBuf>,
156 // #[serde(rename = "___figment_relative_path")]
157 path: PathBuf,
158}
159
160impl PartialEq for RelativePathBuf {
161 fn eq(&self, other: &Self) -> bool {
162 self.relative() == other.relative()
163 }
164}
165
166impl<P: AsRef<Path>> From<P> for RelativePathBuf {
167 fn from(path: P) -> RelativePathBuf {
168 Self { metadata_path: None, path: path.as_ref().into() }
169 }
170}
171
172impl Magic for RelativePathBuf {
173 const NAME: &'static str = "___figment_relative_path_buf";
174
175 const FIELDS: &'static [&'static str] = &[
176 "___figment_relative_metadata_path",
177 "___figment_relative_path"
178 ];
179
180 fn deserialize_from<'de: 'c, 'c, V: de::Visitor<'de>, I: Interpreter>(
181 de: ConfiguredValueDe<'c, I>,
182 visitor: V
183 ) -> Result<V::Value, Error> {
184 // If we have this struct with a non-empty metadata_path, use it.
185 let config = de.config;
186 if let Some(d) = de.value.as_dict() {
187 if let Some(mpv) = d.get(Self::FIELDS[0]) {
188 if mpv.to_empty().is_none() {
189 let map_de = MapDe::new(d, |v| ConfiguredValueDe::<I>::from(config, v));
190 return visitor.visit_map(map_de);
191 }
192 }
193 }
194
195 let metadata_path = config.get_metadata(de.value.tag())
196 .and_then(|metadata| metadata.source.as_ref()
197 .and_then(|s| s.file_path())
198 .map(|path| path.display().to_string()));
199
200 let mut map = crate::value::Map::new();
201 if let Some(path) = metadata_path {
202 map.insert(Self::FIELDS[0].into(), path.into());
203 }
204
205 // If we have this struct with no metadata_path, still use the value.
206 let value = de.value.find_ref(Self::FIELDS[1]).unwrap_or(&de.value);
207 map.insert(Self::FIELDS[1].into(), value.clone());
208 visitor.visit_map(MapDe::new(&map, |v| ConfiguredValueDe::<I>::from(config, v)))
209 }
210}
211
212impl RelativePathBuf {
213 /// Returns the path as it was declared, without modification.
214 ///
215 /// # Example
216 ///
217 /// ```rust
218 /// use std::path::Path;
219 ///
220 /// use figment::{Figment, value::magic::RelativePathBuf, Jail};
221 /// use figment::providers::{Format, Toml};
222 ///
223 /// #[derive(Debug, PartialEq, serde::Deserialize)]
224 /// struct Config {
225 /// path: RelativePathBuf,
226 /// }
227 ///
228 /// Jail::expect_with(|jail| {
229 /// jail.create_file("Config.toml", r#"path = "hello.html""#)?;
230 /// let c: Config = Figment::from(Toml::file("Config.toml")).extract()?;
231 /// assert_eq!(c.path.original(), Path::new("hello.html"));
232 ///
233 /// Ok(())
234 /// });
235 /// ```
236 pub fn original(&self) -> &Path {
237 &self.path
238 }
239
240 /// Returns this path resolved relative to the file it was declared in, if any.
241 ///
242 /// If the configured path was relative and it was configured from a file,
243 /// this function returns that path prefixed with that file's parent
244 /// directory. Otherwise it returns the original path. Where
245 /// `config_file_path` is the location of the configuration file, this
246 /// corresponds to:
247 ///
248 /// ```rust
249 /// # use figment::{Figment, value::magic::RelativePathBuf, Jail};
250 /// # use figment::providers::{Format, Toml};
251 /// # use serde::Deserialize;
252 /// #
253 /// # #[derive(Debug, PartialEq, Deserialize)]
254 /// # struct Config {
255 /// # path: RelativePathBuf,
256 /// # }
257 /// # Jail::expect_with(|jail| {
258 /// # let config_file_path = jail.directory().join("Config.toml");
259 /// # let config_file = jail.create_file("Config.toml", r#"path = "hello.html""#)?;
260 /// # let config: Config = Figment::from(Toml::file("Config.toml")).extract()?;
261 /// # let relative_path_buf = config.path;
262 /// let relative = config_file_path
263 /// .parent()
264 /// .unwrap()
265 /// .join(relative_path_buf.original());
266 /// # assert_eq!(relative_path_buf.relative(), relative);
267 /// # Ok(())
268 /// # });
269 /// ```
270 ///
271 /// # Example
272 ///
273 /// ```rust
274 /// use std::path::Path;
275 ///
276 /// use figment::{Figment, value::magic::RelativePathBuf, Jail};
277 /// use figment::providers::{Env, Format, Toml};
278 ///
279 /// #[derive(Debug, PartialEq, serde::Deserialize)]
280 /// struct Config {
281 /// path: RelativePathBuf,
282 /// }
283 ///
284 /// Jail::expect_with(|jail| {
285 /// jail.create_file("Config.toml", r#"path = "hello.html""#)?;
286 /// let c: Config = Figment::from(Toml::file("Config.toml")).extract()?;
287 /// assert_eq!(c.path.relative(), jail.directory().join("hello.html"));
288 ///
289 /// jail.set_env("PATH", r#"hello.html"#);
290 /// let c: Config = Figment::from(Env::raw().only(&["PATH"])).extract()?;
291 /// assert_eq!(c.path.relative(), Path::new("hello.html"));
292 ///
293 /// Ok(())
294 /// });
295 /// ```
296 pub fn relative(&self) -> PathBuf {
297 if self.original().has_root() {
298 return self.original().into();
299 }
300
301 self.metadata_path()
302 .and_then(|root| match root.is_dir() {
303 true => Some(root),
304 false => root.parent(),
305 })
306 .map(|root| root.join(self.original()))
307 .unwrap_or_else(|| self.original().into())
308 }
309
310 /// Returns the path to the file this path was declared in, if any.
311 ///
312 /// # Example
313 ///
314 /// ```rust
315 /// use std::path::Path;
316 ///
317 /// use figment::{Figment, value::magic::RelativePathBuf, Jail};
318 /// use figment::providers::{Env, Format, Toml};
319 ///
320 /// #[derive(Debug, PartialEq, serde::Deserialize)]
321 /// struct Config {
322 /// path: RelativePathBuf,
323 /// }
324 ///
325 /// Jail::expect_with(|jail| {
326 /// jail.create_file("Config.toml", r#"path = "hello.html""#)?;
327 /// let c: Config = Figment::from(Toml::file("Config.toml")).extract()?;
328 /// assert_eq!(c.path.metadata_path().unwrap(), jail.directory().join("Config.toml"));
329 ///
330 /// jail.set_env("PATH", r#"hello.html"#);
331 /// let c: Config = Figment::from(Env::raw().only(&["PATH"])).extract()?;
332 /// assert_eq!(c.path.metadata_path(), None);
333 ///
334 /// Ok(())
335 /// });
336 /// ```
337 pub fn metadata_path(&self) -> Option<&Path> {
338 self.metadata_path.as_ref().map(|p| p.as_ref())
339 }
340
341 /// Serialize `self` as the [`original`](Self::original()) path.
342 ///
343 /// See [serialization](Self#serialization) for more.
344 ///
345 /// # Example
346 ///
347 /// ```rust
348 /// use std::path::PathBuf;
349 /// use figment::value::magic::RelativePathBuf;
350 /// use serde::Serialize;
351 ///
352 /// #[derive(Serialize)]
353 /// struct Config {
354 /// #[serde(serialize_with = "RelativePathBuf::serialize_original")]
355 /// path: RelativePathBuf,
356 /// }
357 /// ```
358 pub fn serialize_original<S>(&self, ser: S) -> Result<S::Ok, S::Error>
359 where S: serde::Serializer
360 {
361 self.original().serialize(ser)
362 }
363
364 /// Serialize `self` as the [`relative`](Self::relative()) path.
365 ///
366 /// See [serialization](Self#serialization) for more.
367 ///
368 /// # Example
369 ///
370 /// ```rust
371 /// use std::path::PathBuf;
372 /// use figment::value::magic::RelativePathBuf;
373 /// use serde::Serialize;
374 ///
375 /// #[derive(Serialize)]
376 /// struct Config {
377 /// #[serde(serialize_with = "RelativePathBuf::serialize_relative")]
378 /// path: RelativePathBuf,
379 /// }
380 /// ```
381 // FIXME: Make this the default? We need a breaking change for this.
382 pub fn serialize_relative<S>(&self, ser: S) -> Result<S::Ok, S::Error>
383 where S: serde::Serializer
384 {
385 self.relative().serialize(ser)
386 }
387}
388
389// /// MAGIC
390// #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
391// #[serde(rename = "___figment_selected_profile")]
392// pub struct SelectedProfile {
393// profile: crate::Profile,
394// }
395//
396// /// TODO: This doesn't work when it's in a map and the config doesn't contain a
397// /// value for the corresponding field; we never get to call `deserialize` on the
398// /// field's value. We can't fabricate this from no value. We either need to fake
399// /// the field name, somehow, or just not have this.
400// impl Magic for SelectedProfile {
401// const NAME: &'static str = "___figment_selected_profile";
402// const FIELDS: &'static [&'static str] = &["profile"];
403//
404// fn deserialize_from<'de: 'c, 'c, V: de::Visitor<'de>>(
405// de: ConfiguredValueDe<'c>,
406// visitor: V
407// ) -> Result<V::Value, Error>{
408// let mut map = crate::value::Map::new();
409// map.insert(Self::FIELDS[0].into(), de.config.profile().to_string().into());
410// visitor.visit_map(MapDe::new(&map, |v| ConfiguredValueDe::<I>::from(de.config, v)))
411// }
412// }
413//
414// impl Deref for SelectedProfile {
415// type Target = crate::Profile;
416//
417// fn deref(&self) -> &Self::Target {
418// &self.profile
419// }
420// }
421
422/// (De)serializes as either a magic value `A` or any other deserializable value
423/// `B`.
424///
425/// An `Either<A, B>` deserializes as either an `A` or `B`, whichever succeeds
426/// first.
427///
428/// The usual `Either` implementation or an "untagged" enum does not allow its
429/// internal values to provide hints to the deserializer. These hints are
430/// required for magic values to work. By contrast, this `Either` _does_ provide
431/// the appropriate hints.
432///
433/// # Example
434///
435/// ```
436/// use serde::{Serialize, Deserialize};
437/// use figment::{Figment, value::magic::{Either, RelativePathBuf, Tagged}};
438///
439/// #[derive(Debug, PartialEq, Deserialize, Serialize)]
440/// struct Config {
441/// int_or_str: Either<Tagged<usize>, String>,
442/// path_or_bytes: Either<RelativePathBuf, Vec<u8>>,
443/// }
444///
445/// fn figment<A: Serialize, B: Serialize>(a: A, b: B) -> Figment {
446/// Figment::from(("int_or_str", a)).merge(("path_or_bytes", b))
447/// }
448///
449/// let config: Config = figment(10, "/a/b").extract().unwrap();
450/// assert_eq!(config.int_or_str, Either::Left(10.into()));
451/// assert_eq!(config.path_or_bytes, Either::Left("/a/b".into()));
452///
453/// let config: Config = figment("hi", "c/d").extract().unwrap();
454/// assert_eq!(config.int_or_str, Either::Right("hi".into()));
455/// assert_eq!(config.path_or_bytes, Either::Left("c/d".into()));
456///
457/// let config: Config = figment(123, &[1, 2, 3]).extract().unwrap();
458/// assert_eq!(config.int_or_str, Either::Left(123.into()));
459/// assert_eq!(config.path_or_bytes, Either::Right(vec![1, 2, 3].into()));
460///
461/// let config: Config = figment("boo!", &[4, 5, 6]).extract().unwrap();
462/// assert_eq!(config.int_or_str, Either::Right("boo!".into()));
463/// assert_eq!(config.path_or_bytes, Either::Right(vec![4, 5, 6].into()));
464///
465/// let config: Config = Figment::from(figment::providers::Serialized::defaults(Config {
466/// int_or_str: Either::Left(10.into()),
467/// path_or_bytes: Either::Left("a/b/c".into()),
468/// })).extract().unwrap();
469///
470/// assert_eq!(config.int_or_str, Either::Left(10.into()));
471/// assert_eq!(config.path_or_bytes, Either::Left("a/b/c".into()));
472///
473/// let config: Config = Figment::from(figment::providers::Serialized::defaults(Config {
474/// int_or_str: Either::Right("hi".into()),
475/// path_or_bytes: Either::Right(vec![3, 7, 13]),
476/// })).extract().unwrap();
477///
478/// assert_eq!(config.int_or_str, Either::Right("hi".into()));
479/// assert_eq!(config.path_or_bytes, Either::Right(vec![3, 7, 13]));
480/// ```
481#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
482// #[serde(untagged)]
483// #[derive(Serialize)]
484pub enum Either<A, B> {
485 /// The "left" variant.
486 Left(A),
487 /// The "right" variant.
488 Right(B),
489}
490
491impl<'de: 'b, 'b, A, B> Deserialize<'de> for Either<A, B>
492 where A: Magic, B: Deserialize<'b>
493{
494 fn deserialize<D>(de: D) -> Result<Self, D::Error>
495 where D: de::Deserializer<'de>
496 {
497 use crate::value::ValueVisitor;
498
499 // FIXME: propogate the error properly
500 let value = de.deserialize_struct(A::NAME, A::FIELDS, ValueVisitor)?;
501 match A::deserialize(&value) {
502 Ok(value) => Ok(Either::Left(value)),
503 Err(a_err) => {
504 let value = value.as_dict()
505 .and_then(|d| d.get(A::FIELDS[A::FIELDS.len() - 1]))
506 .unwrap_or(&value);
507
508 match B::deserialize(value) {
509 Ok(value) => Ok(Either::Right(value)),
510 Err(b_err) => Err(de::Error::custom(format!("{}; {}", a_err, b_err)))
511 }
512 }
513 }
514
515 // use crate::error::Kind::*;
516 // result.map_err(|e| match e.kind {
517 // InvalidType(Actual, String) => de::Error::invalid_type()
518 // InvalidValue(Actual, String),
519 // UnknownField(String, &'static [&'static str]),
520 // MissingField(Cow<'static, str>),
521 // DuplicateField(&'static str),
522 // InvalidLength(usize, String),
523 // UnknownVariant(String, &'static [&'static str]),
524 // kind => de::Error::custom(kind.to_string()),
525 // })
526 }
527}
528
529/// A wrapper around any value of type `T` and its [`Tag`].
530///
531/// ```rust
532/// use figment::{Figment, value::magic::Tagged, Jail};
533/// use figment::providers::{Format, Toml};
534///
535/// #[derive(Debug, PartialEq, serde::Deserialize)]
536/// struct Config {
537/// number: Tagged<usize>,
538/// }
539///
540/// Jail::expect_with(|jail| {
541/// jail.create_file("Config.toml", r#"number = 10"#)?;
542/// let figment = Figment::from(Toml::file("Config.toml"));
543/// let c: Config = figment.extract()?;
544/// assert_eq!(*c.number, 10);
545///
546/// let tag = c.number.tag();
547/// let metadata = figment.get_metadata(tag).expect("number has tag");
548///
549/// assert!(!tag.is_default());
550/// assert_eq!(metadata.name, "TOML file");
551/// Ok(())
552/// });
553/// ```
554#[derive(Debug, Clone)]
555// #[derive(Deserialize, Serialize)]
556// #[serde(rename = "___figment_tagged_item")]
557pub struct Tagged<T> {
558 // #[serde(rename = "___figment_tagged_tag")]
559 tag: Tag,
560 // #[serde(rename = "___figment_tagged_value")]
561 value: T,
562}
563
564impl<T: PartialEq> PartialEq for Tagged<T> {
565 fn eq(&self, other: &Self) -> bool {
566 self.value == other.value
567 }
568}
569
570impl<T: for<'de> Deserialize<'de>> Magic for Tagged<T> {
571 const NAME: &'static str = "___figment_tagged_item";
572 const FIELDS: &'static [&'static str] = &[
573 "___figment_tagged_tag" , "___figment_tagged_value"
574 ];
575
576 fn deserialize_from<'de: 'c, 'c, V: de::Visitor<'de>, I: Interpreter>(
577 de: ConfiguredValueDe<'c, I>,
578 visitor: V
579 ) -> Result<V::Value, Error>{
580 let config = de.config;
581 let mut map = crate::value::Map::new();
582
583 // If we have this struct with a non-default tag, use it.
584 if let Some(dict) = de.value.as_dict() {
585 if let Some(tagv) = dict.get(Self::FIELDS[0]) {
586 if let Ok(false) = tagv.deserialize::<Tag>().map(|t| t.is_default()) {
587 return visitor.visit_map(MapDe::new(dict, |v| {
588 ConfiguredValueDe::<I>::from(config, v)
589 }));
590 }
591 }
592 }
593
594 // If we have this struct with default tag, use the value.
595 let value = de.value.find_ref(Self::FIELDS[1]).unwrap_or(&de.value);
596 map.insert(Self::FIELDS[0].into(), de.value.tag().into());
597 map.insert(Self::FIELDS[1].into(), value.clone());
598 visitor.visit_map(MapDe::new(&map, |v| ConfiguredValueDe::<I>::from(config, v)))
599 }
600}
601
602impl<T> Tagged<T> {
603 /// Returns the tag of the inner value if it is known. As long `self` is a
604 /// leaf and was extracted from a [`Figment`](crate::Figment), the returned
605 /// value is expected to be `Some`.
606 ///
607 /// # Example
608 ///
609 /// ```rust
610 /// use figment::{Figment, Profile, value::magic::Tagged};
611 ///
612 /// let figment = Figment::from(("key", "value"));
613 /// let tagged = figment.extract_inner::<Tagged<String>>("key").unwrap();
614 ///
615 /// assert!(!tagged.tag().is_default());
616 /// assert_eq!(tagged.tag().profile(), Some(Profile::Global));
617 /// ```
618 pub fn tag(&self) -> Tag {
619 self.tag
620 }
621
622 /// Consumes `self` and returns the inner value.
623 ///
624 /// # Example
625 ///
626 /// ```rust
627 /// use figment::{Figment, value::magic::Tagged};
628 ///
629 /// let tagged = Figment::from(("key", "value"))
630 /// .extract_inner::<Tagged<String>>("key")
631 /// .unwrap();
632 ///
633 /// let value = tagged.into_inner();
634 /// assert_eq!(value, "value");
635 /// ```
636 pub fn into_inner(self) -> T {
637 self.value
638 }
639}
640
641impl<T> Deref for Tagged<T> {
642 type Target = T;
643
644 fn deref(&self) -> &Self::Target {
645 &self.value
646 }
647}
648
649impl<T> From<T> for Tagged<T> {
650 fn from(value: T) -> Self {
651 Tagged { tag: Tag::Default, value, }
652 }
653}
654
655/// These were generated by serde's derive. We don't want to depend on the
656/// 'derive' feature, so we simply expand it and copy the impls here.
657mod _serde {
658 use super::*;
659
660 #[allow(unused_imports)]
661 pub mod export {
662 // These are re-reexports used by serde's codegen.
663 pub use std::clone::Clone;
664 pub use std::convert::{From, Into};
665 pub use std::default::Default;
666 pub use std::fmt::{self, Formatter};
667 pub use std::marker::PhantomData;
668 pub use std::option::Option::{self, None, Some};
669 pub use std::result::Result::{self, Err, Ok};
670
671 pub fn missing_field<'de, V, E>(field: &'static str) -> Result<V, E>
672 where V: serde::de::Deserialize<'de>,
673 E: serde::de::Error,
674 {
675 struct MissingFieldDeserializer<E>(&'static str, PhantomData<E>);
676
677 impl<'de, E> serde::de::Deserializer<'de> for MissingFieldDeserializer<E>
678 where E: serde::de::Error
679 {
680 type Error = E;
681
682 fn deserialize_any<V>(self, _visitor: V) -> Result<V::Value, E>
683 where V: serde::de::Visitor<'de>,
684 {
685 Err(serde::de::Error::missing_field(self.0))
686 }
687
688 fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, E>
689 where V: serde::de::Visitor<'de>,
690 {
691 visitor.visit_none()
692 }
693
694 serde::forward_to_deserialize_any! {
695 bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str
696 string bytes byte_buf unit unit_struct newtype_struct seq tuple
697 tuple_struct map struct enum identifier ignored_any
698 }
699 }
700
701 let deserializer = MissingFieldDeserializer(field, PhantomData);
702 serde::de::Deserialize::deserialize(deserializer)
703 }
704 }
705
706 #[doc(hidden)]
707 #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)]
708 const _: () = {
709 #[allow(rust_2018_idioms, clippy::useless_attribute)]
710 extern crate serde as _serde;
711
712 #[automatically_derived]
713 impl<'de> _serde::Deserialize<'de> for RelativePathBuf {
714 fn deserialize<__D>(__deserializer: __D) -> export::Result<Self, __D::Error>
715 where
716 __D: _serde::Deserializer<'de>,
717 {
718 #[allow(non_camel_case_types)]
719 enum __Field {
720 __field0,
721 __field1,
722 __ignore,
723 }
724 struct __FieldVisitor;
725 impl<'de> _serde::de::Visitor<'de> for __FieldVisitor {
726 type Value = __Field;
727 fn expecting(
728 &self,
729 __formatter: &mut export::Formatter,
730 ) -> export::fmt::Result {
731 export::Formatter::write_str(__formatter, "field identifier")
732 }
733 fn visit_u64<__E>(
734 self,
735 __value: u64,
736 ) -> export::Result<Self::Value, __E>
737 where
738 __E: _serde::de::Error,
739 {
740 match __value {
741 0u64 => export::Ok(__Field::__field0),
742 1u64 => export::Ok(__Field::__field1),
743 _ => export::Err(_serde::de::Error::invalid_value(
744 _serde::de::Unexpected::Unsigned(__value),
745 &"field index 0 <= i < 2",
746 )),
747 }
748 }
749 fn visit_str<__E>(
750 self,
751 __value: &str,
752 ) -> export::Result<Self::Value, __E>
753 where
754 __E: _serde::de::Error,
755 {
756 match __value {
757 "___figment_relative_metadata_path" => {
758 export::Ok(__Field::__field0)
759 }
760 "___figment_relative_path" => export::Ok(__Field::__field1),
761 _ => export::Ok(__Field::__ignore),
762 }
763 }
764 fn visit_bytes<__E>(
765 self,
766 __value: &[u8],
767 ) -> export::Result<Self::Value, __E>
768 where
769 __E: _serde::de::Error,
770 {
771 match __value {
772 b"___figment_relative_metadata_path" => {
773 export::Ok(__Field::__field0)
774 }
775 b"___figment_relative_path" => {
776 export::Ok(__Field::__field1)
777 }
778 _ => export::Ok(__Field::__ignore),
779 }
780 }
781 }
782 impl<'de> _serde::Deserialize<'de> for __Field {
783 #[inline]
784 fn deserialize<__D>(
785 __deserializer: __D,
786 ) -> export::Result<Self, __D::Error>
787 where
788 __D: _serde::Deserializer<'de>,
789 {
790 _serde::Deserializer::deserialize_identifier(
791 __deserializer,
792 __FieldVisitor,
793 )
794 }
795 }
796 struct __Visitor<'de> {
797 marker: export::PhantomData<RelativePathBuf>,
798 lifetime: export::PhantomData<&'de ()>,
799 }
800 impl<'de> _serde::de::Visitor<'de> for __Visitor<'de> {
801 type Value = RelativePathBuf;
802 fn expecting(
803 &self,
804 __formatter: &mut export::Formatter,
805 ) -> export::fmt::Result {
806 export::Formatter::write_str(
807 __formatter,
808 "struct RelativePathBuf",
809 )
810 }
811 #[inline]
812 fn visit_seq<__A>(
813 self,
814 mut __seq: __A,
815 ) -> export::Result<Self::Value, __A::Error>
816 where
817 __A: _serde::de::SeqAccess<'de>,
818 {
819 let __field0 = match match _serde::de::SeqAccess::next_element::<
820 Option<PathBuf>,
821 >(&mut __seq)
822 {
823 export::Ok(__val) => __val,
824 export::Err(__err) => {
825 return export::Err(__err);
826 }
827 } {
828 export::Some(__value) => __value,
829 export::None => {
830 return export::Err(_serde::de::Error::invalid_length(
831 0usize,
832 &"struct RelativePathBuf with 2 elements",
833 ));
834 }
835 };
836 let __field1 = match match _serde::de::SeqAccess::next_element::<PathBuf>(
837 &mut __seq,
838 ) {
839 export::Ok(__val) => __val,
840 export::Err(__err) => {
841 return export::Err(__err);
842 }
843 } {
844 export::Some(__value) => __value,
845 export::None => {
846 return export::Err(_serde::de::Error::invalid_length(
847 1usize,
848 &"struct RelativePathBuf with 2 elements",
849 ));
850 }
851 };
852 export::Ok(RelativePathBuf {
853 metadata_path: __field0,
854 path: __field1,
855 })
856 }
857 #[inline]
858 fn visit_map<__A>(
859 self,
860 mut __map: __A,
861 ) -> export::Result<Self::Value, __A::Error>
862 where
863 __A: _serde::de::MapAccess<'de>,
864 {
865 let mut __field0: export::Option<Option<PathBuf>> =
866 export::None;
867 let mut __field1: export::Option<PathBuf> =
868 export::None;
869 while let export::Some(__key) =
870 match _serde::de::MapAccess::next_key::<__Field>(&mut __map) {
871 export::Ok(__val) => __val,
872 export::Err(__err) => {
873 return export::Err(__err);
874 }
875 }
876 {
877 match __key {
878 __Field::__field0 => {
879 if export::Option::is_some(&__field0) {
880 return export::Err(
881 <__A::Error as _serde::de::Error>::duplicate_field(
882 "___figment_relative_metadata_path",
883 ),
884 );
885 }
886 __field0 = export::Some(
887 match _serde::de::MapAccess::next_value::<Option<PathBuf>>(
888 &mut __map,
889 ) {
890 export::Ok(__val) => __val,
891 export::Err(__err) => {
892 return export::Err(__err);
893 }
894 },
895 );
896 }
897 __Field::__field1 => {
898 if export::Option::is_some(&__field1) {
899 return export::Err(
900 <__A::Error as _serde::de::Error>::duplicate_field(
901 "___figment_relative_path",
902 ),
903 );
904 }
905 __field1 = export::Some(
906 match _serde::de::MapAccess::next_value::<PathBuf>(
907 &mut __map,
908 ) {
909 export::Ok(__val) => __val,
910 export::Err(__err) => {
911 return export::Err(__err);
912 }
913 },
914 );
915 }
916 _ => {
917 let _ = match _serde::de::MapAccess::next_value::<
918 _serde::de::IgnoredAny,
919 >(
920 &mut __map
921 ) {
922 export::Ok(__val) => __val,
923 export::Err(__err) => {
924 return export::Err(__err);
925 }
926 };
927 }
928 }
929 }
930 let __field0 = match __field0 {
931 export::Some(__field0) => __field0,
932 export::None => match export::missing_field(
933 "___figment_relative_metadata_path",
934 ) {
935 export::Ok(__val) => __val,
936 export::Err(__err) => {
937 return export::Err(__err);
938 }
939 },
940 };
941 let __field1 = match __field1 {
942 export::Some(__field1) => __field1,
943 export::None => match export::missing_field(
944 "___figment_relative_path",
945 ) {
946 export::Ok(__val) => __val,
947 export::Err(__err) => {
948 return export::Err(__err);
949 }
950 },
951 };
952 export::Ok(RelativePathBuf {
953 metadata_path: __field0,
954 path: __field1,
955 })
956 }
957 }
958 const FIELDS: &[&str] = &[
959 "___figment_relative_metadata_path",
960 "___figment_relative_path",
961 ];
962 _serde::Deserializer::deserialize_struct(
963 __deserializer,
964 "___figment_relative_path_buf",
965 FIELDS,
966 __Visitor {
967 marker: export::PhantomData::<RelativePathBuf>,
968 lifetime: export::PhantomData,
969 },
970 )
971 }
972 }
973 };
974
975 #[doc(hidden)]
976 #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)]
977 const _: () = {
978 #[allow(rust_2018_idioms, clippy::useless_attribute)]
979 extern crate serde as _serde;
980 #[automatically_derived]
981 impl _serde::Serialize for RelativePathBuf {
982 fn serialize<__S>(
983 &self,
984 __serializer: __S,
985 ) -> export::Result<__S::Ok, __S::Error>
986 where
987 __S: _serde::Serializer,
988 {
989 let mut __serde_state = match _serde::Serializer::serialize_struct(
990 __serializer,
991 "___figment_relative_path_buf",
992 false as usize + 1 + 1,
993 ) {
994 export::Ok(__val) => __val,
995 export::Err(__err) => {
996 return export::Err(__err);
997 }
998 };
999 match _serde::ser::SerializeStruct::serialize_field(
1000 &mut __serde_state,
1001 "___figment_relative_metadata_path",
1002 &self.metadata_path,
1003 ) {
1004 export::Ok(__val) => __val,
1005 export::Err(__err) => {
1006 return export::Err(__err);
1007 }
1008 };
1009 match _serde::ser::SerializeStruct::serialize_field(
1010 &mut __serde_state,
1011 "___figment_relative_path",
1012 &self.path,
1013 ) {
1014 export::Ok(__val) => __val,
1015 export::Err(__err) => {
1016 return export::Err(__err);
1017 }
1018 };
1019 _serde::ser::SerializeStruct::end(__serde_state)
1020 }
1021 }
1022 };
1023
1024 #[doc(hidden)]
1025 #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)]
1026 const _: () = {
1027 #[allow(rust_2018_idioms, clippy::useless_attribute)]
1028 extern crate serde as _serde;
1029 #[automatically_derived]
1030 impl<A, B> _serde::Serialize for Either<A, B>
1031 where
1032 A: _serde::Serialize,
1033 B: _serde::Serialize,
1034 {
1035 fn serialize<__S>(
1036 &self,
1037 __serializer: __S,
1038 ) -> export::Result<__S::Ok, __S::Error>
1039 where
1040 __S: _serde::Serializer,
1041 {
1042 match *self {
1043 Either::Left(ref __field0) => {
1044 _serde::Serialize::serialize(__field0, __serializer)
1045 }
1046 Either::Right(ref __field0) => {
1047 _serde::Serialize::serialize(__field0, __serializer)
1048 }
1049 }
1050 }
1051 }
1052 };
1053
1054 #[doc(hidden)]
1055 #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)]
1056 const _: () = {
1057 #[allow(rust_2018_idioms, clippy::useless_attribute)]
1058 extern crate serde as _serde;
1059 #[automatically_derived]
1060 impl<'de, T> _serde::Deserialize<'de> for Tagged<T>
1061 where
1062 T: _serde::Deserialize<'de>,
1063 {
1064 fn deserialize<__D>(__deserializer: __D) -> export::Result<Self, __D::Error>
1065 where
1066 __D: _serde::Deserializer<'de>,
1067 {
1068 #[allow(non_camel_case_types)]
1069 enum __Field {
1070 __field0,
1071 __field1,
1072 __ignore,
1073 }
1074 struct __FieldVisitor;
1075 impl<'de> _serde::de::Visitor<'de> for __FieldVisitor {
1076 type Value = __Field;
1077 fn expecting(
1078 &self,
1079 __formatter: &mut export::Formatter,
1080 ) -> export::fmt::Result {
1081 export::Formatter::write_str(__formatter, "field identifier")
1082 }
1083 fn visit_u64<__E>(
1084 self,
1085 __value: u64,
1086 ) -> export::Result<Self::Value, __E>
1087 where
1088 __E: _serde::de::Error,
1089 {
1090 match __value {
1091 0u64 => export::Ok(__Field::__field0),
1092 1u64 => export::Ok(__Field::__field1),
1093 _ => export::Err(_serde::de::Error::invalid_value(
1094 _serde::de::Unexpected::Unsigned(__value),
1095 &"field index 0 <= i < 2",
1096 )),
1097 }
1098 }
1099 fn visit_str<__E>(
1100 self,
1101 __value: &str,
1102 ) -> export::Result<Self::Value, __E>
1103 where
1104 __E: _serde::de::Error,
1105 {
1106 match __value {
1107 "___figment_tagged_tag" => export::Ok(__Field::__field0),
1108 "___figment_tagged_value" => export::Ok(__Field::__field1),
1109 _ => export::Ok(__Field::__ignore),
1110 }
1111 }
1112 fn visit_bytes<__E>(
1113 self,
1114 __value: &[u8],
1115 ) -> export::Result<Self::Value, __E>
1116 where
1117 __E: _serde::de::Error,
1118 {
1119 match __value {
1120 b"___figment_tagged_tag" => export::Ok(__Field::__field0),
1121 b"___figment_tagged_value" => export::Ok(__Field::__field1),
1122 _ => export::Ok(__Field::__ignore),
1123 }
1124 }
1125 }
1126 impl<'de> _serde::Deserialize<'de> for __Field {
1127 #[inline]
1128 fn deserialize<__D>(
1129 __deserializer: __D,
1130 ) -> export::Result<Self, __D::Error>
1131 where
1132 __D: _serde::Deserializer<'de>,
1133 {
1134 _serde::Deserializer::deserialize_identifier(
1135 __deserializer,
1136 __FieldVisitor,
1137 )
1138 }
1139 }
1140 struct __Visitor<'de, T>
1141 where
1142 T: _serde::Deserialize<'de>,
1143 {
1144 marker: export::PhantomData<Tagged<T>>,
1145 lifetime: export::PhantomData<&'de ()>,
1146 }
1147 impl<'de, T> _serde::de::Visitor<'de> for __Visitor<'de, T>
1148 where
1149 T: _serde::Deserialize<'de>,
1150 {
1151 type Value = Tagged<T>;
1152 fn expecting(
1153 &self,
1154 __formatter: &mut export::Formatter,
1155 ) -> export::fmt::Result {
1156 export::Formatter::write_str(__formatter, "struct Tagged")
1157 }
1158 #[inline]
1159 fn visit_seq<__A>(
1160 self,
1161 mut __seq: __A,
1162 ) -> export::Result<Self::Value, __A::Error>
1163 where
1164 __A: _serde::de::SeqAccess<'de>,
1165 {
1166 let __field0 = match match _serde::de::SeqAccess::next_element::<Tag>(
1167 &mut __seq,
1168 ) {
1169 export::Ok(__val) => __val,
1170 export::Err(__err) => {
1171 return export::Err(__err);
1172 }
1173 } {
1174 export::Some(__value) => __value,
1175 export::None => {
1176 return export::Err(_serde::de::Error::invalid_length(
1177 0usize,
1178 &"struct Tagged with 2 elements",
1179 ));
1180 }
1181 };
1182 let __field1 =
1183 match match _serde::de::SeqAccess::next_element::<T>(&mut __seq) {
1184 export::Ok(__val) => __val,
1185 export::Err(__err) => {
1186 return export::Err(__err);
1187 }
1188 } {
1189 export::Some(__value) => __value,
1190 export::None => {
1191 return export::Err(
1192 _serde::de::Error::invalid_length(
1193 1usize,
1194 &"struct Tagged with 2 elements",
1195 ),
1196 );
1197 }
1198 };
1199 export::Ok(Tagged {
1200 tag: __field0,
1201 value: __field1,
1202 })
1203 }
1204 #[inline]
1205 fn visit_map<__A>(
1206 self,
1207 mut __map: __A,
1208 ) -> export::Result<Self::Value, __A::Error>
1209 where
1210 __A: _serde::de::MapAccess<'de>,
1211 {
1212 let mut __field0: export::Option<Tag> = export::None;
1213 let mut __field1: export::Option<T> = export::None;
1214 while let export::Some(__key) =
1215 match _serde::de::MapAccess::next_key::<__Field>(&mut __map) {
1216 export::Ok(__val) => __val,
1217 export::Err(__err) => {
1218 return export::Err(__err);
1219 }
1220 }
1221 {
1222 match __key {
1223 __Field::__field0 => {
1224 if export::Option::is_some(&__field0) {
1225 return export::Err(
1226 <__A::Error as _serde::de::Error>::duplicate_field(
1227 "___figment_tagged_tag",
1228 ),
1229 );
1230 }
1231 __field0 = export::Some(
1232 match _serde::de::MapAccess::next_value::<Tag>(
1233 &mut __map,
1234 ) {
1235 export::Ok(__val) => __val,
1236 export::Err(__err) => {
1237 return export::Err(__err);
1238 }
1239 },
1240 );
1241 }
1242 __Field::__field1 => {
1243 if export::Option::is_some(&__field1) {
1244 return export::Err(
1245 <__A::Error as _serde::de::Error>::duplicate_field(
1246 "___figment_tagged_value",
1247 ),
1248 );
1249 }
1250 __field1 = export::Some(
1251 match _serde::de::MapAccess::next_value::<T>(&mut __map)
1252 {
1253 export::Ok(__val) => __val,
1254 export::Err(__err) => {
1255 return export::Err(__err);
1256 }
1257 },
1258 );
1259 }
1260 _ => {
1261 let _ = match _serde::de::MapAccess::next_value::<
1262 _serde::de::IgnoredAny,
1263 >(
1264 &mut __map
1265 ) {
1266 export::Ok(__val) => __val,
1267 export::Err(__err) => {
1268 return export::Err(__err);
1269 }
1270 };
1271 }
1272 }
1273 }
1274 let __field0 = match __field0 {
1275 export::Some(__field0) => __field0,
1276 export::None => match export::missing_field(
1277 "___figment_tagged_tag",
1278 ) {
1279 export::Ok(__val) => __val,
1280 export::Err(__err) => {
1281 return export::Err(__err);
1282 }
1283 },
1284 };
1285 let __field1 = match __field1 {
1286 export::Some(__field1) => __field1,
1287 export::None => match export::missing_field(
1288 "___figment_tagged_value",
1289 ) {
1290 export::Ok(__val) => __val,
1291 export::Err(__err) => {
1292 return export::Err(__err);
1293 }
1294 },
1295 };
1296 export::Ok(Tagged {
1297 tag: __field0,
1298 value: __field1,
1299 })
1300 }
1301 }
1302 const FIELDS: &[&str] =
1303 &["___figment_tagged_tag", "___figment_tagged_value"];
1304 _serde::Deserializer::deserialize_struct(
1305 __deserializer,
1306 "___figment_tagged_item",
1307 FIELDS,
1308 __Visitor {
1309 marker: export::PhantomData::<Tagged<T>>,
1310 lifetime: export::PhantomData,
1311 },
1312 )
1313 }
1314 }
1315 };
1316
1317 #[doc(hidden)]
1318 #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)]
1319 const _: () = {
1320 #[allow(rust_2018_idioms, clippy::useless_attribute)]
1321 extern crate serde as _serde;
1322 #[automatically_derived]
1323 impl<T> _serde::Serialize for Tagged<T>
1324 where
1325 T: _serde::Serialize,
1326 {
1327 fn serialize<__S>(
1328 &self,
1329 __serializer: __S,
1330 ) -> export::Result<__S::Ok, __S::Error>
1331 where
1332 __S: _serde::Serializer,
1333 {
1334 let mut __serde_state = match _serde::Serializer::serialize_struct(
1335 __serializer,
1336 "___figment_tagged_item",
1337 false as usize + 1 + 1,
1338 ) {
1339 export::Ok(__val) => __val,
1340 export::Err(__err) => {
1341 return export::Err(__err);
1342 }
1343 };
1344 match _serde::ser::SerializeStruct::serialize_field(
1345 &mut __serde_state,
1346 "___figment_tagged_tag",
1347 &self.tag,
1348 ) {
1349 export::Ok(__val) => __val,
1350 export::Err(__err) => {
1351 return export::Err(__err);
1352 }
1353 };
1354 match _serde::ser::SerializeStruct::serialize_field(
1355 &mut __serde_state,
1356 "___figment_tagged_value",
1357 &self.value,
1358 ) {
1359 export::Ok(__val) => __val,
1360 export::Err(__err) => {
1361 return export::Err(__err);
1362 }
1363 };
1364 _serde::ser::SerializeStruct::end(__serde_state)
1365 }
1366 }
1367 };
1368}
1369
1370#[cfg(test)]
1371mod tests {
1372 use crate::Figment;
1373
1374 #[test]
1375 fn test_relative_path_buf() {
1376 use super::RelativePathBuf;
1377 use crate::providers::{Format, Toml};
1378 use std::path::Path;
1379
1380 crate::Jail::expect_with(|jail| {
1381 jail.set_env("foo", "bar");
1382 jail.create_file("Config.toml", r###"
1383 [debug]
1384 file_path = "hello.js"
1385 another = "whoops/hi/there"
1386 absolute = "/tmp/foo"
1387 "###)?;
1388
1389 let path: RelativePathBuf = Figment::new()
1390 .merge(Toml::file("Config.toml").nested())
1391 .select("debug")
1392 .extract_inner("file_path")?;
1393
1394 assert_eq!(path.original(), Path::new("hello.js"));
1395 assert_eq!(path.metadata_path().unwrap(), jail.directory().join("Config.toml"));
1396 assert_eq!(path.relative(), jail.directory().join("hello.js"));
1397
1398 let path: RelativePathBuf = Figment::new()
1399 .merge(Toml::file("Config.toml").nested())
1400 .select("debug")
1401 .extract_inner("another")?;
1402
1403 assert_eq!(path.original(), Path::new("whoops/hi/there"));
1404 assert_eq!(path.metadata_path().unwrap(), jail.directory().join("Config.toml"));
1405 assert_eq!(path.relative(), jail.directory().join("whoops/hi/there"));
1406
1407 let path: RelativePathBuf = Figment::new()
1408 .merge(Toml::file("Config.toml").nested())
1409 .select("debug")
1410 .extract_inner("absolute")?;
1411
1412 assert_eq!(path.original(), Path::new("/tmp/foo"));
1413 assert_eq!(path.metadata_path().unwrap(), jail.directory().join("Config.toml"));
1414 assert_eq!(path.relative(), Path::new("/tmp/foo"));
1415
1416 jail.create_file("Config.toml", r###"
1417 [debug.inner.container]
1418 inside = "inside_path/a.html"
1419 "###)?;
1420
1421 #[derive(serde::Deserialize)]
1422 struct Testing { inner: Container, }
1423
1424 #[derive(serde::Deserialize)]
1425 struct Container { container: Inside, }
1426
1427 #[derive(serde::Deserialize)]
1428 struct Inside { inside: RelativePathBuf, }
1429
1430 let testing: Testing = Figment::new()
1431 .merge(Toml::file("Config.toml").nested())
1432 .select("debug")
1433 .extract()?;
1434
1435 let path = testing.inner.container.inside;
1436 assert_eq!(path.original(), Path::new("inside_path/a.html"));
1437 assert_eq!(path.metadata_path().unwrap(), jail.directory().join("Config.toml"));
1438 assert_eq!(path.relative(), jail.directory().join("inside_path/a.html"));
1439
1440 Ok(())
1441 })
1442 }
1443
1444 // #[test]
1445 // fn test_selected_profile() {
1446 // use super::SelectedProfile;
1447 //
1448 // let profile: SelectedProfile = Figment::new().extract().unwrap();
1449 // assert_eq!(&*profile, crate::Profile::default());
1450 //
1451 // let profile: SelectedProfile = Figment::new().select("foo").extract().unwrap();
1452 // assert_eq!(&*profile, "foo");
1453 //
1454 // let profile: SelectedProfile = Figment::new().select("bar").extract().unwrap();
1455 // assert_eq!(&*profile, "bar");
1456 //
1457 // #[derive(serde::Deserialize)]
1458 // struct Testing {
1459 // #[serde(alias = "other")]
1460 // profile: SelectedProfile,
1461 // value: usize
1462 // }
1463 //
1464 // let testing: Testing = Figment::from(("value", 123))
1465 // .merge(("other", "hi"))
1466 // .select("with-value").extract().unwrap();
1467 //
1468 // assert_eq!(&*testing.profile, "with-value");
1469 // assert_eq!(testing.value, 123);
1470 // }
1471
1472 // #[test]
1473 // fn test_selected_profile_kink() {
1474 // use super::SelectedProfile;
1475 //
1476 // #[derive(serde::Deserialize)]
1477 // struct Base {
1478 // profile: SelectedProfile,
1479 // }
1480 //
1481 // #[derive(serde::Deserialize)]
1482 // struct Testing {
1483 // base: Base,
1484 // value: usize
1485 // }
1486 //
1487 // let testing: Testing = Figment::from(("value", 123)).extract().unwrap();
1488 //
1489 // assert_eq!(&*testing.base.profile, "with-value");
1490 // assert_eq!(testing.value, 123);
1491 // }
1492
1493 #[test]
1494 fn test_tagged() {
1495 use super::Tagged;
1496
1497 let val = Figment::from(("foo", "hello"))
1498 .extract_inner::<Tagged<String>>("foo")
1499 .expect("extraction");
1500
1501 let first_tag = val.tag();
1502 assert_eq!(val.value, "hello");
1503
1504 let val = Figment::from(("bar", "hi"))
1505 .extract_inner::<Tagged<String>>("bar")
1506 .expect("extraction");
1507
1508 let second_tag = val.tag();
1509 assert_eq!(val.value, "hi");
1510 assert!(second_tag != first_tag);
1511
1512 #[derive(serde::Deserialize)]
1513 struct TwoVals {
1514 foo: Tagged<String>,
1515 bar: Tagged<u16>,
1516 }
1517
1518 let two = Figment::new()
1519 .merge(("foo", "hey"))
1520 .merge(("bar", 10))
1521 .extract::<TwoVals>()
1522 .expect("extraction");
1523
1524 let tag3 = two.foo.tag();
1525 assert_eq!(two.foo.value, "hey");
1526 assert!(tag3 != second_tag);
1527
1528 let tag4 = two.bar.tag();
1529 assert_eq!(two.bar.value, 10);
1530 assert!(tag4 != tag3);
1531
1532 let val = Figment::new()
1533 .merge(("foo", "hey"))
1534 .merge(("bar", 10))
1535 .extract::<Tagged<TwoVals>>()
1536 .expect("extraction");
1537
1538 assert!(val.tag().is_default());
1539
1540 let tag5 = val.value.foo.tag();
1541 assert_eq!(val.value.foo.value, "hey");
1542 assert!(tag4 != tag5);
1543
1544 let tag6 = val.value.bar.tag();
1545 assert_eq!(val.value.bar.value, 10);
1546 assert!(tag6 != tag5)
1547 }
1548}