figment-0.10.19/.cargo_vcs_info.json0000644000000001360000000000100126350ustar { "git": { "sha1": "dae213f0f31d764a0c00aeb39f145222050bc657" }, "path_in_vcs": "" }figment-0.10.19/.gitattributes000064400000000000000000000000161046102023000143150ustar 00000000000000* text eol=lf figment-0.10.19/.github/workflows/ci.yml000064400000000000000000000031141046102023000161370ustar 00000000000000name: CI on: [push, pull_request] env: CARGO_TERM_COLOR: always jobs: test: name: "${{ matrix.os.name }} ${{ matrix.test.name }} (${{ matrix.toolchain }})" continue-on-error: false runs-on: ${{ matrix.os.distro }} strategy: fail-fast: false matrix: os: - { name: Linux, distro: ubuntu-latest } - { name: Windows, distro: windows-latest } - { name: macOS, distro: macOS-latest } toolchain: [nightly, stable] steps: - name: Checkout Sources uses: actions/checkout@v4 - name: Install Native Dependencies (macOS) if: matrix.os.name == 'macOS' run: brew install coreutils - name: Install Rust uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.toolchain }} - name: Check Features run: ./scripts/test.sh --core shell: bash - name: Run Tests run: ./scripts/test.sh shell: bash test_cross: name: Linux ARMv7 (${{ matrix.toolchain }}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: toolchain: [nightly, stable] steps: - name: Checkout Sources uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.toolchain }} targets: armv7-unknown-linux-gnueabihf - name: Build uses: actions-rs/cargo@v1 with: use-cross: true command: build args: --target armv7-unknown-linux-gnueabihf --all-features figment-0.10.19/.gitignore000064400000000000000000000001411046102023000134110ustar 00000000000000target/ **/*.rs.bk # Added by cargo # # already existing elements were commented out #/target figment-0.10.19/Cargo.toml0000644000000047720000000000100106450ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "figment" version = "0.10.19" authors = ["Sergio Benitez "] build = "build.rs" autobins = false autoexamples = false autotests = false autobenches = false description = "A configuration library so con-free, it's unreal." documentation = "https://docs.rs/figment/0.10" readme = "README.md" keywords = [ "config", "configuration", "toml", "json", "yaml", ] categories = ["config"] license = "MIT OR Apache-2.0" repository = "https://github.com/SergioBenitez/Figment" [package.metadata.docs.rs] all-features = true [lib] name = "figment" path = "src/lib.rs" [[test]] name = "tuple-struct" path = "tests/tuple-struct.rs" [[test]] name = "lossy_values" path = "tests/lossy_values.rs" [[test]] name = "cargo" path = "tests/cargo.rs" [[test]] name = "empty-env-vars" path = "tests/empty-env-vars.rs" [[test]] name = "camel-case" path = "tests/camel-case.rs" [[test]] name = "enum" path = "tests/enum.rs" [[test]] name = "yaml-enum" path = "tests/yaml-enum.rs" [[test]] name = "tagged" path = "tests/tagged.rs" [dependencies.parking_lot] version = "0.12" optional = true [dependencies.pear] version = "0.2" optional = true [dependencies.serde] version = "1.0" [dependencies.serde_json] version = "1.0" optional = true [dependencies.serde_yaml] version = "0.9" optional = true [dependencies.tempfile] version = "3" optional = true [dependencies.toml] version = "0.8" optional = true [dependencies.uncased] version = "0.9.3" [dev-dependencies.clap] version = "4" features = ["derive"] [dev-dependencies.parking_lot] version = "0.12" [dev-dependencies.serde] version = "1.0" features = ["derive"] [dev-dependencies.tempfile] version = "3" [build-dependencies.version_check] version = "0.9" [features] env = [ "pear", "parse-value", ] json = ["serde_json"] parse-value = ["pear"] test = [ "tempfile", "parking_lot", ] yaml = ["serde_yaml"] [target."cfg(any(target_pointer_width = \"8\", target_pointer_width = \"16\", target_pointer_width = \"32\"))".dependencies.atomic] version = "0.6.0" figment-0.10.19/Cargo.toml.orig000064400000000000000000000024351046102023000143200ustar 00000000000000[package] name = "figment" version = "0.10.19" authors = ["Sergio Benitez "] edition = "2018" documentation = "https://docs.rs/figment/0.10" description = "A configuration library so con-free, it's unreal." repository = "https://github.com/SergioBenitez/Figment" readme = "README.md" keywords = ["config", "configuration", "toml", "json", "yaml"] license = "MIT OR Apache-2.0" categories = ["config"] [features] env = ["pear", "parse-value"] json = ["serde_json"] yaml = ["serde_yaml"] parse-value = ["pear"] test = ["tempfile", "parking_lot"] # toml = ["toml"] [dependencies] serde = { version = "1.0" } uncased = "0.9.3" pear = { version = "0.2", optional = true } toml = { version = "0.8", optional = true } serde_json = { version = "1.0", optional = true } serde_yaml = { version = "0.9", optional = true } tempfile = { version = "3", optional = true } parking_lot = { version = "0.12", optional = true } [target.'cfg(any(target_pointer_width = "8", target_pointer_width = "16", target_pointer_width = "32"))'.dependencies] atomic = "0.6.0" [dev-dependencies] tempfile = "3" parking_lot = "0.12" serde = { version = "1.0", features = ["derive"] } clap = { version = "4", features = ["derive"] } [build-dependencies] version_check = "0.9" [package.metadata.docs.rs] all-features = true figment-0.10.19/LICENSE-APACHE000064400000000000000000000227731046102023000133640ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS figment-0.10.19/LICENSE-MIT000064400000000000000000000020701046102023000130600ustar 00000000000000The MIT License (MIT) Copyright (c) 2020 Sergio Benitez Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. figment-0.10.19/README.md000064400000000000000000000040101046102023000126770ustar 00000000000000# Figment   [![ci.svg]][ci] [![crates.io]][crate] [![docs.rs]][docs] [crates.io]: https://img.shields.io/crates/v/figment.svg [crate]: https://crates.io/crates/figment [docs.rs]: https://docs.rs/figment/badge.svg [docs]: https://docs.rs/figment [ci.svg]: https://github.com/SergioBenitez/Figment/workflows/CI/badge.svg [ci]: https://github.com/SergioBenitez/Figment/actions Figment is a semi-hierarchical configuration library for Rust so con-free, it's _unreal_. ```rust use serde::Deserialize; use figment::{Figment, providers::{Format, Toml, Json, Env}}; #[derive(Deserialize)] struct Package { name: String, authors: Vec, publish: Option, // ... and so on ... } #[derive(Deserialize)] struct Config { package: Package, rustc: Option, // ... and so on ... } let config: Config = Figment::new() .merge(Toml::file("Cargo.toml")) .merge(Env::prefixed("CARGO_")) .merge(Env::raw().only(&["RUSTC", "RUSTDOC"])) .join(Json::file("Cargo.json")) .extract()?; ``` See the [documentation](https://docs.rs/figment) for a detailed usage guide and information. ## Usage Add the following to your `Cargo.toml`, enabling the desired built-in providers: ```toml [dependencies] figment = { version = "0.10", features = ["toml", "env"] } ``` #### Third-Party Providers The following external libraries implement Figment providers: - [`figment_file_provider_adapter`](https://crates.io/crates/figment_file_provider_adapter) Wraps existing providers. For any key ending in `_FILE` (configurable), emits a key without the `_FILE` suffix with a value corresponding to the contents of the file whose path is the original key's value. Please submit a pull request to add your library to this list. ## License Figment is licensed under either of the following, at your option: * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT License ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) figment-0.10.19/build.rs000064400000000000000000000002011046102023000130630ustar 00000000000000fn main() { if let Some(true) = version_check::is_feature_flaggable() { println!("cargo:rustc-cfg=nightly"); } } figment-0.10.19/scripts/test.sh000075500000000000000000000043601046102023000144350ustar 00000000000000#!/usr/bin/env bash set -e SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" function relative() { local full_path="${SCRIPT_DIR}/../${1}" if [ -d "${full_path}" ]; then # Try to use readlink as a fallback to readpath for cross-platform compat. if command -v realpath >/dev/null 2>&1; then realpath "${full_path}" elif ! (readlink -f 2>&1 | grep illegal > /dev/null); then readlink -f "${full_path}" else echo "Figment's scripts require 'realpath' or 'readlink -f' support." >&2 echo "Install realpath or GNU readlink via your package manager." >&2 echo "Aborting." >&2 exit 1 fi else # when the directory doesn't exist, fallback to this. echo "${full_path}" fi } # Root of workspace-like directories. PROJECT_ROOT=$(relative "") || exit $? # Add Cargo to PATH. export PATH=${HOME}/.cargo/bin:${PATH} CARGO="cargo" # Ensures there are no tabs in any file. function ensure_tab_free() { local tab=$(printf '\t') local matches=$(git grep -PIn "${tab}" "${PROJECT_ROOT}" | grep -v 'LICENSE') if ! [ -z "${matches}" ]; then echo "Tab characters were found in the following:" echo "${matches}" exit 1 fi } # Ensures there are no files with trailing whitespace. function ensure_trailing_whitespace_free() { local matches=$(git grep -PIn "\s+$" "${PROJECT_ROOT}" | grep -v -F '.stderr:') if ! [ -z "${matches}" ]; then echo "Trailing whitespace was found in the following:" echo "${matches}" exit 1 fi } if [[ $1 == +* ]]; then CARGO="$CARGO $1" shift fi echo ":: Checking for tabs..." ensure_tab_free echo ":: Checking for trailing whitespace..." ensure_trailing_whitespace_free echo ":: Updating dependencies..." if ! $CARGO update ; then echo " WARNING: Update failed! Proceeding with possibly outdated deps..." fi if [ "$1" = "--core" ]; then FEATURES=( env json yaml test ) echo ":: Building and testing core [no features]..." $CARGO check --no-default-features for feature in "${FEATURES[@]}"; do echo ":: Building and testing core [${feature}]..." $CARGO check --no-default-features --features "${feature}" done else echo ":: Building and testing libraries..." $CARGO test --all-features --all $@ fi figment-0.10.19/src/coalesce.rs000064400000000000000000000030241046102023000143370ustar 00000000000000use crate::Profile; use crate::value::{Value, Map}; #[derive(Debug, Clone, Copy, PartialEq)] pub enum Order { Merge, Join, Adjoin, Admerge, } pub trait Coalescible: Sized { fn coalesce(self, other: Self, order: Order) -> Self; fn merge(self, other: Self) -> Self { self.coalesce(other, Order::Merge) } } impl Coalescible for Profile { fn coalesce(self, other: Self, order: Order) -> Self { match order { Order::Join | Order::Adjoin => self, Order::Merge | Order::Admerge => other, } } } impl Coalescible for Value { fn coalesce(self, other: Self, o: Order) -> Self { use {Value::Dict as D, Value::Array as A, Order::*}; match (self, other, o) { (D(t, a), D(_, b), Join | Adjoin) | (D(_, a), D(t, b), Merge | Admerge) => D(t, a.coalesce(b, o)), (A(t, mut a), A(_, b), Adjoin | Admerge) => A(t, { a.extend(b); a }), (v, _, Join | Adjoin) | (_, v, Merge | Admerge) => v, } } } impl Coalescible for Map { fn coalesce(self, mut other: Self, order: Order) -> Self { let mut joined = Map::new(); for (a_key, a_val) in self { match other.remove(&a_key) { Some(b_val) => joined.insert(a_key, a_val.coalesce(b_val, order)), None => joined.insert(a_key, a_val), }; } // `b` contains `b - a`, i.e, additions. keep them all. joined.extend(other); joined } } figment-0.10.19/src/error.rs000064400000000000000000000401071046102023000137150ustar 00000000000000//! Error values produces when extracting configurations. use std::fmt::{self, Display}; use std::borrow::Cow; use serde::{ser, de}; use crate::{Figment, Profile, Metadata, value::Tag}; /// A simple alias to `Result` with an error type of [`Error`]. pub type Result = std::result::Result; /// An error that occured while producing data or extracting a configuration. /// /// # Constructing Errors /// /// An `Error` will generally be constructed indirectly via its implementations /// of serde's [`de::Error`] and [`ser::Error`], that is, as a result of /// serialization or deserialization errors. When implementing [`Provider`], /// however, it may be necessary to construct an `Error` directly. /// /// [`Provider`]: crate::Provider /// /// Broadly, there are two ways to construct an `Error`: /// /// * With an error message, as `Error` impls `From` and `From<&str>`: /// /// ``` /// use figment::Error; /// /// Error::from(format!("{} is invalid", 1)); /// /// Error::from("whoops, something went wrong!"); /// ``` /// /// * With a [`Kind`], as `Error` impls `From`: /// /// ``` /// use figment::{error::{Error, Kind}, value::Value}; /// /// let value = Value::serialize(&100).unwrap(); /// if !value.as_str().is_some() { /// let kind = Kind::InvalidType(value.to_actual(), "string".into()); /// let error = Error::from(kind); /// } /// ``` /// /// As always, `?` can be used to automatically convert into an `Error` using /// the available `From` implementations: /// /// ``` /// use std::fs::File; /// /// fn try_read() -> Result<(), figment::Error> { /// let x = File::open("/tmp/foo.boo").map_err(|e| e.to_string())?; /// Ok(()) /// } /// ``` /// /// # Display /// /// By default, `Error` uses all of the available information about the error, /// including the `Metadata`, `path`, and `profile` to display a message that /// resembles the following, where `$` is `error.` for some `error: Error`: /// /// ```text /// $kind: `$metadata.interpolate($path)` in $($metadata.sources())* /// ``` /// /// Concretely, such an error may look like: /// /// ```text /// invalid type: found sequence, expected u16: `staging.port` in TOML file Config.toml /// ``` /// /// # Iterator /// /// An `Error` may contain more than one error. To process all errors, iterate /// over an `Error`: /// /// ```rust /// fn with_error(error: figment::Error) { /// for error in error { /// println!("error: {}", error); /// } /// } /// ``` #[derive(Clone, Debug, PartialEq)] pub struct Error { /// The tag of the value that errored. We use this to lookup the `metadata`. tag: Tag, /// The profile that was selected when the error occured, if any. pub profile: Option, /// The metadata for the provider of the value that errored, if known. pub metadata: Option, /// The path to the configuration key that errored, if known. pub path: Vec, /// The error kind. pub kind: Kind, prev: Option>, } /// An error kind, encapsulating serde's [`serde::de::Error`]. #[derive(Clone, Debug, PartialEq)] pub enum Kind { /// A custom error message. Message(String), /// An invalid type: (actual, expected). See /// [`serde::de::Error::invalid_type()`]. InvalidType(Actual, String), /// An invalid value: (actual, expected). See /// [`serde::de::Error::invalid_value()`]. InvalidValue(Actual, String), /// Too many or too few items: (actual, expected). See /// [`serde::de::Error::invalid_length()`]. InvalidLength(usize, String), /// A variant with an unrecognized name: (actual, expected). See /// [`serde::de::Error::unknown_variant()`]. UnknownVariant(String, &'static [&'static str]), /// A field with an unrecognized name: (actual, expected). See /// [`serde::de::Error::unknown_field()`]. UnknownField(String, &'static [&'static str]), /// A field was missing: (name). See [`serde::de::Error::missing_field()`]. MissingField(Cow<'static, str>), /// A field appeared more than once: (name). See /// [`serde::de::Error::duplicate_field()`]. DuplicateField(&'static str), /// The `isize` was not in range of any known sized signed integer. ISizeOutOfRange(isize), /// The `usize` was not in range of any known sized unsigned integer. USizeOutOfRange(usize), /// The serializer or deserializer does not support the `Actual` type. Unsupported(Actual), /// The type `.0` cannot be used for keys, need a `.1`. UnsupportedKey(Actual, Cow<'static, str>), } impl Error { pub(crate) fn prefixed(mut self, key: &str) -> Self { self.path.insert(0, key.into()); self } pub(crate) fn retagged(mut self, tag: Tag) -> Self { if self.tag.is_default() { self.tag = tag; } self } pub(crate) fn resolved(mut self, config: &Figment) -> Self { let mut error = Some(&mut self); while let Some(e) = error { e.metadata = config.get_metadata(e.tag).cloned(); e.profile = e.tag.profile() .or_else(|| Some(config.profile().clone())); error = e.prev.as_deref_mut(); } self } } impl Error { /// Returns `true` if the error's kind is `MissingField`. /// /// # Example /// /// ```rust /// use figment::error::{Error, Kind}; /// /// let error = Error::from(Kind::MissingField("path".into())); /// assert!(error.missing()); /// ``` pub fn missing(&self) -> bool { matches!(self.kind, Kind::MissingField(..)) } /// Append the string `path` to the error's path. /// /// # Example /// /// ```rust /// use figment::Error; /// /// let error = Error::from("an error message").with_path("some_path"); /// assert_eq!(error.path, vec!["some_path"]); /// /// let error = Error::from("an error message").with_path("some.path"); /// assert_eq!(error.path, vec!["some", "path"]); /// ``` pub fn with_path(mut self, path: &str) -> Self { let paths = path.split('.') .filter(|v| !v.is_empty()) .map(|v| v.to_string()); self.path.extend(paths); self } /// Prepends `self` to `error` and returns `error`. /// /// ```rust /// use figment::error::Error; /// /// let e1 = Error::from("1"); /// let e2 = Error::from("2"); /// let e3 = Error::from("3"); /// /// let error = e1.chain(e2).chain(e3); /// assert_eq!(error.count(), 3); /// /// let unchained = error.into_iter() /// .map(|e| e.to_string()) /// .collect::>(); /// assert_eq!(unchained, vec!["3", "2", "1"]); /// /// let e1 = Error::from("1"); /// let e2 = Error::from("2"); /// let e3 = Error::from("3"); /// let error = e3.chain(e2).chain(e1); /// assert_eq!(error.count(), 3); /// /// let unchained = error.into_iter() /// .map(|e| e.to_string()) /// .collect::>(); /// assert_eq!(unchained, vec!["1", "2", "3"]); /// ``` pub fn chain(self, mut error: Error) -> Self { error.prev = Some(Box::new(self)); error } /// Returns the number of errors represented by `self`. /// /// # Example /// /// ```rust /// use figment::{Figment, providers::{Format, Toml}}; /// /// figment::Jail::expect_with(|jail| { /// jail.create_file("Base.toml", r#" /// # oh no, an unclosed array! /// cat = [1 /// "#)?; /// /// jail.create_file("Release.toml", r#" /// # and now an unclosed string!? /// cat = " /// "#)?; /// /// let figment = Figment::from(Toml::file("Base.toml")) /// .merge(Toml::file("Release.toml")); /// /// let error = figment.extract_inner::("cat").unwrap_err(); /// assert_eq!(error.count(), 2); /// /// Ok(()) /// }); /// ``` pub fn count(&self) -> usize { 1 + self.prev.as_ref().map_or(0, |e| e.count()) } } /// An iterator over all errors in an [`Error`]. pub struct IntoIter(Option); impl Iterator for IntoIter { type Item = Error; fn next(&mut self) -> Option { if let Some(mut error) = self.0.take() { self.0 = error.prev.take().map(|e| *e); Some(error) } else { None } } } impl IntoIterator for Error { type Item = Error; type IntoIter = IntoIter; fn into_iter(self) -> Self::IntoIter { IntoIter(Some(self)) } } /// A type that enumerates all of serde's types, used to indicate that a value /// of the given type was received. #[allow(missing_docs)] #[derive(Clone, Debug, PartialEq)] pub enum Actual { Bool(bool), Unsigned(u128), Signed(i128), Float(f64), Char(char), Str(String), Bytes(Vec), Unit, Option, NewtypeStruct, Seq, Map, Enum, UnitVariant, NewtypeVariant, TupleVariant, StructVariant, Other(String), } impl fmt::Display for Actual { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Actual::Bool(v) => write!(f, "bool {}", v), Actual::Unsigned(v) => write!(f, "unsigned int `{}`", v), Actual::Signed(v) => write!(f, "signed int `{}`", v), Actual::Float(v) => write!(f, "float `{}`", v), Actual::Char(v) => write!(f, "char {:?}", v), Actual::Str(v) => write!(f, "string {:?}", v), Actual::Bytes(v) => write!(f, "bytes {:?}", v), Actual::Unit => write!(f, "unit"), Actual::Option => write!(f, "option"), Actual::NewtypeStruct => write!(f, "new-type struct"), Actual::Seq => write!(f, "sequence"), Actual::Map => write!(f, "map"), Actual::Enum => write!(f, "enum"), Actual::UnitVariant => write!(f, "unit variant"), Actual::NewtypeVariant => write!(f, "new-type variant"), Actual::TupleVariant => write!(f, "tuple variant"), Actual::StructVariant => write!(f, "struct variant"), Actual::Other(v) => v.fmt(f), } } } impl From> for Actual { fn from(value: de::Unexpected<'_>) -> Actual { match value { de::Unexpected::Bool(v) => Actual::Bool(v), de::Unexpected::Unsigned(v) => Actual::Unsigned(v as u128), de::Unexpected::Signed(v) => Actual::Signed(v as i128), de::Unexpected::Float(v) => Actual::Float(v), de::Unexpected::Char(v) => Actual::Char(v), de::Unexpected::Str(v) => Actual::Str(v.into()), de::Unexpected::Bytes(v) => Actual::Bytes(v.into()), de::Unexpected::Unit => Actual::Unit, de::Unexpected::Option => Actual::Option, de::Unexpected::NewtypeStruct => Actual::NewtypeStruct, de::Unexpected::Seq => Actual::Seq, de::Unexpected::Map => Actual::Map, de::Unexpected::Enum => Actual::Enum, de::Unexpected::UnitVariant => Actual::UnitVariant, de::Unexpected::NewtypeVariant => Actual::NewtypeVariant, de::Unexpected::TupleVariant => Actual::TupleVariant, de::Unexpected::StructVariant => Actual::StructVariant, de::Unexpected::Other(v) => Actual::Other(v.into()) } } } impl de::Error for Error { fn custom(msg: T) -> Self { Kind::Message(msg.to_string()).into() } fn invalid_type(unexp: de::Unexpected, exp: &dyn de::Expected) -> Self { Kind::InvalidType(unexp.into(), exp.to_string()).into() } fn invalid_value(unexp: de::Unexpected, exp: &dyn de::Expected) -> Self { Kind::InvalidValue(unexp.into(), exp.to_string()).into() } fn invalid_length(len: usize, exp: &dyn de::Expected) -> Self { Kind::InvalidLength(len, exp.to_string()).into() } fn unknown_variant(variant: &str, expected: &'static [&'static str]) -> Self { Kind::UnknownVariant(variant.into(), expected).into() } fn unknown_field(field: &str, expected: &'static [&'static str]) -> Self { Kind::UnknownField(field.into(), expected).into() } fn missing_field(field: &'static str) -> Self { Kind::MissingField(field.into()).into() } fn duplicate_field(field: &'static str) -> Self { Kind::DuplicateField(field).into() } } impl ser::Error for Error { fn custom(msg: T) -> Self { Kind::Message(msg.to_string()).into() } } impl From for Error { fn from(kind: Kind) -> Error { Error { tag: Tag::Default, path: vec![], profile: None, metadata: None, prev: None, kind, } } } impl From<&str> for Error { fn from(string: &str) -> Error { Kind::Message(string.into()).into() } } impl From for Error { fn from(string: String) -> Error { Kind::Message(string).into() } } impl Display for Kind { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Kind::Message(msg) => f.write_str(&msg), Kind::InvalidType(v, exp) => { write!(f, "invalid type: found {}, expected {}", v, exp) } Kind::InvalidValue(v, exp) => { write!(f, "invalid value {}, expected {}", v, exp) }, Kind::InvalidLength(v, exp) => { write!(f, "invalid length {}, expected {}", v, exp) }, Kind::UnknownVariant(v, exp) => { write!(f, "unknown variant: found `{}`, expected `{}`", v, OneOf(exp)) } Kind::UnknownField(v, exp) => { write!(f, "unknown field: found `{}`, expected `{}`", v, OneOf(exp)) } Kind::MissingField(v) => { write!(f, "missing field `{}`", v) } Kind::DuplicateField(v) => { write!(f, "duplicate field `{}`", v) } Kind::ISizeOutOfRange(v) => { write!(f, "signed integer `{}` is out of range", v) } Kind::USizeOutOfRange(v) => { write!(f, "unsigned integer `{}` is out of range", v) } Kind::Unsupported(v) => { write!(f, "unsupported type `{}`", v) } Kind::UnsupportedKey(a, e) => { write!(f, "unsupported type `{}` for key: must be `{}`", a, e) } } } } impl Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.kind.fmt(f)?; if let (Some(profile), Some(md)) = (&self.profile, &self.metadata) { if !self.path.is_empty() { let key = md.interpolate(profile, &self.path); write!(f, " for key {:?}", key)?; } } if let Some(md) = &self.metadata { if let Some(source) = &md.source { write!(f, " in {} {}", source, md.name)?; } else { write!(f, " in {}", md.name)?; } } if let Some(prev) = &self.prev { write!(f, "\n{}", prev)?; } Ok(()) } } impl std::error::Error for Error {} /// A structure that implements [`de::Expected`] signaling that one of the types /// in the slice was expected. pub struct OneOf(pub &'static [&'static str]); impl fmt::Display for OneOf { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.0.len() { 0 => write!(f, "none"), 1 => write!(f, "`{}`", self.0[0]), 2 => write!(f, "`{}` or `{}`", self.0[0], self.0[1]), _ => { write!(f, "one of ")?; for (i, alt) in self.0.iter().enumerate() { if i > 0 { write!(f, ", ")?; } write!(f, "`{}`", alt)?; } Ok(()) } } } } impl de::Expected for OneOf { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { Display::fmt(self, f) } } figment-0.10.19/src/figment.rs000064400000000000000000000746311046102023000142260ustar 00000000000000use std::panic::Location; use serde::de::Deserialize; use crate::{Profile, Provider, Metadata}; use crate::error::{Kind, Result}; use crate::value::{Value, Map, Dict, Tag, ConfiguredValueDe, DefaultInterpreter, LossyInterpreter}; use crate::coalesce::{Coalescible, Order}; /// Combiner of [`Provider`]s for configuration value extraction. /// /// # Overview /// /// A `Figment` combines providers by merging or joining their provided data. /// The combined value or a subset of the combined value can be extracted into /// any type that implements [`Deserialize`]. Additionally, values can be nested /// in _profiles_, and a profile can be selected via [`Figment::select()`] for /// extraction; the profile to be extracted can be retrieved with /// [`Figment::profile()`] and defaults to [`Profile::Default`]. The [top-level /// docs](crate) contain a broad overview of these topics. /// /// ## Conflict Resolution /// /// Conflicts arising from two providers providing values for the same key are /// resolved via one of four strategies: [`join`], [`adjoin`], [`merge`], and /// [`admerge`]. In general, `join` and `adjoin` prefer existing values while /// `merge` and `admerge` prefer later values. The `ad-` strategies additionally /// concatenate conflicting arrays whereas the non-`ad-` strategies treat arrays /// as non-composite values. /// /// The table below summarizes these strategies and their behavior, with the /// column label referring to the type of the value pointed to by the /// conflicting keys: /// /// | Strategy | Dictionaries | Arrays | All Others | /// |-------------|----------------|---------------|---------------| /// | [`join`] | Union, Recurse | Keep Existing | Keep Existing | /// | [`adjoin`] | Union, Recurse | Concatenate | Keep Existing | /// | [`merge`] | Union, Recurse | Use Incoming | Use Incoming | /// | [`admerge`] | Union, Recurse | Concatenate | Use Incoming | /// /// ### Description /// /// If both keys point to a **dictionary**, the dictionaries are always unioned, /// irrespective of the strategy, and conflict resolution proceeds recursively /// with each key in the union. /// /// If both keys point to an **array**: /// /// * `join` uses the existing value /// * `merge` uses the incoming value /// * `adjoin` and `admerge` concatenate the arrays /// /// If both keys point to a **non-composite** (`String`, `Num`, etc.) or values /// of different kinds (i.e, **array** and **num**): /// /// * `join` and `adjoin` use the existing value /// * `merge` and `admerge` use the incoming value /// /// [`join`]: Figment::join() /// [`adjoin`]: Figment::adjoin() /// [`merge`]: Figment::merge() /// [`admerge`]: Figment::admerge() /// /// For examples, refer to each strategy's documentation. /// /// ## Extraction /// /// The configuration or a subset thereof can be extracted from a `Figment` in /// one of several ways: /// /// * [`Figment::extract()`], which extracts the complete value into any `T: /// Deserialize`. /// * [`Figment::extract_inner()`], which extracts a subset of the value for a /// given key path. /// * [`Figment::find_value()`], which returns the raw, serialized [`Value`] /// for a given key path. /// /// A "key path" is a string of the form `a.b.c` (e.g, `item`, `item.fruits`, /// etc.) where each component delimited by a `.` is a key for the dictionary of /// the preceding key in the path, or the root dictionary if it is the first key /// in the path. See [`Value::find()`] for examples. /// /// ## Metadata /// /// Every value collected by a `Figment` is accompanied by the metadata produced /// by the value's provider. Additionally, [`Metadata::provide_location`] is set /// by `from`, `merge` and `join` to the caller's location. `Metadata` can be /// retrieved in one of several ways: /// /// * [`Figment::metadata()`], which returns an iterator over all of the /// metadata for all values. /// * [`Figment::find_metadata()`], which returns the metadata for a value at /// a given key path. /// * [`Figment::get_metadata()`], which returns the metadata for a given /// [`Tag`], itself retrieved via [`Tagged`] or [`Value::tag()`]. /// /// [`Tagged`]: crate::value::magic::Tagged #[derive(Clone, Debug)] pub struct Figment { pub(crate) profile: Profile, pub(crate) metadata: Map, pub(crate) value: Result>, } impl Figment { /// Creates a new `Figment` with the default profile selected and no /// providers. /// /// ```rust /// use figment::Figment; /// /// let figment = Figment::new(); /// # assert_eq!(figment.profile(), "default"); /// assert_eq!(figment.metadata().count(), 0); /// ``` pub fn new() -> Self { Figment { metadata: Map::new(), profile: Profile::Default, value: Ok(Map::new()), } } /// Creates a new `Figment` with the default profile selected and an initial /// `provider`. /// /// ```rust /// use figment::Figment; /// use figment::providers::Env; /// /// let figment = Figment::from(Env::raw()); /// # assert_eq!(figment.profile(), "default"); /// assert_eq!(figment.metadata().count(), 1); /// ``` #[track_caller] pub fn from(provider: T) -> Self { Figment::new().merge(provider) } #[track_caller] fn provide(mut self, provider: T, order: Order) -> Self { if let Some(map) = provider.__metadata_map() { self.metadata.extend(map); } if let Some(profile) = provider.profile() { self.profile = self.profile.coalesce(profile, order); } let mut metadata = provider.metadata(); metadata.provide_location = Some(Location::caller()); let tag = Tag::next(); self.metadata.insert(tag, metadata); self.value = match (provider.data(), self.value) { (Ok(_), e@Err(_)) => e, (Err(e), Ok(_)) => Err(e.retagged(tag)), (Err(e), Err(prev)) => Err(e.retagged(tag).chain(prev)), (Ok(mut new), Ok(old)) => { new.iter_mut() .map(|(p, map)| std::iter::repeat(p).zip(map.values_mut())) .flatten() .for_each(|(p, v)| v.map_tag(|t| *t = tag.for_profile(p))); Ok(old.coalesce(new, order)) } }; self } /// Joins `provider` into the current figment. /// See [conflict resolution](#conflict-resolution) for details. /// /// ```rust /// use figment::Figment; /// use figment::util::map; /// use figment::value::{Dict, Map}; /// /// let figment = Figment::new() /// .join(("string", "original")) /// .join(("vec", vec!["item 1"])) /// .join(("map", map!["string" => "inner original"])); /// /// let new_figment = Figment::new() /// .join(("string", "replaced")) /// .join(("vec", vec!["item 2"])) /// .join(("map", map!["string" => "inner replaced", "new" => "value"])) /// .join(("new", "value")); /// /// let figment = figment.join(new_figment); // **join** /// /// let string: String = figment.extract_inner("string").unwrap(); /// assert_eq!(string, "original"); // existing value retained /// /// let vec: Vec = figment.extract_inner("vec").unwrap(); /// assert_eq!(vec, vec!["item 1"]); // existing value retained /// /// let map: Map = figment.extract_inner("map").unwrap(); /// assert_eq!(map, map! { /// "string".into() => "inner original".into(), // existing value retained /// "new".into() => "value".into(), // new key added /// }); /// /// let new: String = figment.extract_inner("new").unwrap(); /// assert_eq!(new, "value"); // new key added /// ``` #[track_caller] pub fn join(self, provider: T) -> Self { self.provide(provider, Order::Join) } /// Joins `provider` into the current figment while concatenating vectors. /// See [conflict resolution](#conflict-resolution) for details. /// /// ```rust /// use figment::Figment; /// use figment::util::map; /// use figment::value::{Dict, Map}; /// /// let figment = Figment::new() /// .join(("string", "original")) /// .join(("vec", vec!["item 1"])) /// .join(("map", map!["vec" => vec!["inner item 1"]])); /// /// let new_figment = Figment::new() /// .join(("string", "replaced")) /// .join(("vec", vec!["item 2"])) /// .join(("map", map!["vec" => vec!["inner item 2"], "new" => vec!["value"]])) /// .join(("new", "value")); /// /// let figment = figment.adjoin(new_figment); // **adjoin** /// /// let string: String = figment.extract_inner("string").unwrap(); /// assert_eq!(string, "original"); // existing value retained /// /// let vec: Vec = figment.extract_inner("vec").unwrap(); /// assert_eq!(vec, vec!["item 1", "item 2"]); // arrays concatenated /// /// let map: Map> = figment.extract_inner("map").unwrap(); /// assert_eq!(map, map! { /// "vec".into() => vec!["inner item 1".into(), "inner item 2".into()], // arrays concatenated /// "new".into() => vec!["value".into()], // new key added /// }); /// /// let new: String = figment.extract_inner("new").unwrap(); /// assert_eq!(new, "value"); // new key added /// ``` #[track_caller] pub fn adjoin(self, provider: T) -> Self { self.provide(provider, Order::Adjoin) } /// Merges `provider` into the current figment. /// See [conflict resolution](#conflict-resolution) for details. /// /// ```rust /// use figment::Figment; /// use figment::util::map; /// use figment::value::{Dict, Map}; /// /// let figment = Figment::new() /// .join(("string", "original")) /// .join(("vec", vec!["item 1"])) /// .join(("map", map!["string" => "inner original"])); /// /// let new_figment = Figment::new() /// .join(("string", "replaced")) /// .join(("vec", vec!["item 2"])) /// .join(("map", map!["string" => "inner replaced", "new" => "value"])) /// .join(("new", "value")); /// /// let figment = figment.merge(new_figment); // **merge** /// /// let string: String = figment.extract_inner("string").unwrap(); /// assert_eq!(string, "replaced"); // incoming value replaced existing /// /// let vec: Vec = figment.extract_inner("vec").unwrap(); /// assert_eq!(vec, vec!["item 2"]); // incoming value replaced existing /// /// let map: Map = figment.extract_inner("map").unwrap(); /// assert_eq!(map, map! { /// "string".into() => "inner replaced".into(), // incoming value replaced existing /// "new".into() => "value".into(), // new key added /// }); /// /// let new: String = figment.extract_inner("new").unwrap(); /// assert_eq!(new, "value"); // new key added /// ``` #[track_caller] pub fn merge(self, provider: T) -> Self { self.provide(provider, Order::Merge) } /// Merges `provider` into the current figment while concatenating vectors. /// See [conflict resolution](#conflict-resolution) for details. /// /// ```rust /// use figment::Figment; /// use figment::util::map; /// use figment::value::{Dict, Map}; /// /// let figment = Figment::new() /// .join(("string", "original")) /// .join(("vec", vec!["item 1"])) /// .join(("map", map!["vec" => vec!["inner item 1"]])); /// /// let new_figment = Figment::new() /// .join(("string", "replaced")) /// .join(("vec", vec!["item 2"])) /// .join(("map", map!["vec" => vec!["inner item 2"], "new" => vec!["value"]])) /// .join(("new", "value")); /// /// let figment = figment.admerge(new_figment); // **admerge** /// /// let string: String = figment.extract_inner("string").unwrap(); /// assert_eq!(string, "replaced"); // incoming value replaced existing /// /// let vec: Vec = figment.extract_inner("vec").unwrap(); /// assert_eq!(vec, vec!["item 1", "item 2"]); // arrays concatenated /// /// let map: Map> = figment.extract_inner("map").unwrap(); /// assert_eq!(map, map! { /// "vec".into() => vec!["inner item 1".into(), "inner item 2".into()], // arrays concatenated /// "new".into() => vec!["value".into()], // new key added /// }); /// /// let new: String = figment.extract_inner("new").unwrap(); /// assert_eq!(new, "value"); // new key added /// ``` #[track_caller] pub fn admerge(self, provider: T) -> Self { self.provide(provider, Order::Admerge) } /// Sets the profile to extract from to `profile`. /// /// # Example /// /// ``` /// use figment::Figment; /// /// let figment = Figment::new().select("staging"); /// assert_eq!(figment.profile(), "staging"); /// ``` pub fn select>(mut self, profile: P) -> Self { self.profile = profile.into(); self } /// Merges the selected profile with the default and global profiles. fn merged(&self) -> Result { let mut map = self.value.clone().map_err(|e| e.resolved(self))?; let def = map.remove(&Profile::Default).unwrap_or_default(); let global = map.remove(&Profile::Global).unwrap_or_default(); let map = match map.remove(&self.profile) { Some(v) if self.profile.is_custom() => def.merge(v).merge(global), _ => def.merge(global) }; Ok(Value::Dict(Tag::Default, map)) } /// Returns a new `Figment` containing only the sub-dictionaries at `key`. /// /// This "sub-figment" is a _focusing_ of `self` with the property that: /// /// * `self.find(key + ".sub")` <=> `focused.find("sub")` /// /// In other words, all values in `self` with a key starting with `key` are /// in `focused` _without_ the prefix and vice-versa. /// /// # Example /// /// ```rust /// use figment::{Figment, providers::{Format, Toml}}; /// /// figment::Jail::expect_with(|jail| { /// jail.create_file("Config.toml", r#" /// cat = [1, 2, 3] /// dog = [4, 5, 6] /// /// [subtree] /// cat = "meow" /// dog = "woof!" /// /// [subtree.bark] /// dog = true /// cat = false /// "#)?; /// /// let root = Figment::from(Toml::file("Config.toml")); /// assert_eq!(root.extract_inner::>("cat").unwrap(), vec![1, 2, 3]); /// assert_eq!(root.extract_inner::>("dog").unwrap(), vec![4, 5, 6]); /// assert_eq!(root.extract_inner::("subtree.cat").unwrap(), "meow"); /// assert_eq!(root.extract_inner::("subtree.dog").unwrap(), "woof!"); /// /// let subtree = root.focus("subtree"); /// assert_eq!(subtree.extract_inner::("cat").unwrap(), "meow"); /// assert_eq!(subtree.extract_inner::("dog").unwrap(), "woof!"); /// assert_eq!(subtree.extract_inner::("bark.cat").unwrap(), false); /// assert_eq!(subtree.extract_inner::("bark.dog").unwrap(), true); /// /// let bark = subtree.focus("bark"); /// assert_eq!(bark.extract_inner::("cat").unwrap(), false); /// assert_eq!(bark.extract_inner::("dog").unwrap(), true); /// /// let not_a_dict = root.focus("cat"); /// assert!(not_a_dict.extract_inner::("cat").is_err()); /// assert!(not_a_dict.extract_inner::("dog").is_err()); /// /// Ok(()) /// }); /// ``` pub fn focus(&self, key: &str) -> Self { fn try_focus(figment: &Figment, key: &str) -> Result> { let map = figment.value.clone().map_err(|e| e.resolved(figment))?; let new_map = map.into_iter() .filter_map(|(k, v)| { let focused = Value::Dict(Tag::Default, v).find(key)?; let dict = focused.into_dict()?; Some((k, dict)) }) .collect(); Ok(new_map) } Figment { profile: self.profile.clone(), metadata: self.metadata.clone(), value: try_focus(self, key) } } /// Deserializes the collected value into `T`. /// /// # Example /// /// ```rust /// use serde::Deserialize; /// /// use figment::{Figment, providers::{Format, Toml, Json, Env}}; /// /// #[derive(Debug, PartialEq, Deserialize)] /// struct Config { /// name: String, /// numbers: Option>, /// debug: bool, /// } /// /// figment::Jail::expect_with(|jail| { /// jail.create_file("Config.toml", r#" /// name = "test" /// numbers = [1, 2, 3, 10] /// "#)?; /// /// jail.set_env("config_name", "env-test"); /// /// jail.create_file("Config.json", r#" /// { /// "name": "json-test", /// "debug": true /// } /// "#)?; /// /// let config: Config = Figment::new() /// .merge(Toml::file("Config.toml")) /// .merge(Env::prefixed("CONFIG_")) /// .join(Json::file("Config.json")) /// .extract()?; /// /// assert_eq!(config, Config { /// name: "env-test".into(), /// numbers: vec![1, 2, 3, 10].into(), /// debug: true /// }); /// /// Ok(()) /// }); /// ``` pub fn extract<'a, T: Deserialize<'a>>(&self) -> Result { let value = self.merged()?; T::deserialize(ConfiguredValueDe::<'_, DefaultInterpreter>::from(self, &value)) } /// As [`extract`](Figment::extract_lossy), but interpret numbers and /// booleans more flexibly. /// /// See [`Value::to_bool_lossy`] and [`Value::to_num_lossy`] for a full /// explanation of the imputs accepted. /// /// /// # Example /// /// ```rust /// use serde::Deserialize; /// /// use figment::{Figment, providers::{Format, Toml, Json, Env}}; /// /// #[derive(Debug, PartialEq, Deserialize)] /// struct Config { /// name: String, /// numbers: Option>, /// debug: bool, /// } /// /// figment::Jail::expect_with(|jail| { /// jail.create_file("Config.toml", r#" /// name = "test" /// numbers = ["1", "2", "3", "10"] /// "#)?; /// /// jail.set_env("config_name", "env-test"); /// /// jail.create_file("Config.json", r#" /// { /// "name": "json-test", /// "debug": "yes" /// } /// "#)?; /// /// let config: Config = Figment::new() /// .merge(Toml::file("Config.toml")) /// .merge(Env::prefixed("CONFIG_")) /// .join(Json::file("Config.json")) /// .extract_lossy()?; /// /// assert_eq!(config, Config { /// name: "env-test".into(), /// numbers: vec![1, 2, 3, 10].into(), /// debug: true /// }); /// /// Ok(()) /// }); /// ``` pub fn extract_lossy<'a, T: Deserialize<'a>>(&self) -> Result { let value = self.merged()?; T::deserialize(ConfiguredValueDe::<'_, LossyInterpreter>::from(self, &value)) } /// Deserializes the value at the `key` path in the collected value into /// `T`. /// /// # Example /// /// ```rust /// use figment::{Figment, providers::{Format, Toml, Json}}; /// /// figment::Jail::expect_with(|jail| { /// jail.create_file("Config.toml", r#" /// numbers = [1, 2, 3, 10] /// "#)?; /// /// jail.create_file("Config.json", r#"{ "debug": true } "#)?; /// /// let numbers: Vec = Figment::new() /// .merge(Toml::file("Config.toml")) /// .join(Json::file("Config.json")) /// .extract_inner("numbers")?; /// /// assert_eq!(numbers, vec![1, 2, 3, 10]); /// /// Ok(()) /// }); /// ``` pub fn extract_inner<'a, T: Deserialize<'a>>(&self, path: &str) -> Result { let value = self.find_value(path)?; let de = ConfiguredValueDe::<'_, DefaultInterpreter>::from(self, &value); T::deserialize(de).map_err(|e| e.with_path(path)) } /// As [`extract`](Figment::extract_lossy), but interpret numbers and /// booleans more flexibly. /// /// See [`Value::to_bool_lossy`] and [`Value::to_num_lossy`] for a full /// explanation of the imputs accepted. /// /// # Example /// /// ```rust /// use figment::{Figment, providers::{Format, Toml, Json}}; /// /// figment::Jail::expect_with(|jail| { /// jail.create_file("Config.toml", r#" /// numbers = ["1", "2", "3", "10"] /// "#)?; /// /// jail.create_file("Config.json", r#"{ "debug": true } "#)?; /// /// let numbers: Vec = Figment::new() /// .merge(Toml::file("Config.toml")) /// .join(Json::file("Config.json")) /// .extract_inner_lossy("numbers")?; /// /// assert_eq!(numbers, vec![1, 2, 3, 10]); /// /// Ok(()) /// }); /// ``` pub fn extract_inner_lossy<'a, T: Deserialize<'a>>(&self, path: &str) -> Result { let value = self.find_value(path)?; let de = ConfiguredValueDe::<'_, LossyInterpreter>::from(self, &value); T::deserialize(de).map_err(|e| e.with_path(path)) } /// Returns an iterator over the metadata for all of the collected values in /// the order in which they were added to `self`. /// /// # Example /// /// ```rust /// use figment::{Figment, providers::{Format, Toml, Json}}; /// /// let figment = Figment::new() /// .merge(Toml::file("Config.toml")) /// .join(Json::file("Config.json")); /// /// assert_eq!(figment.metadata().count(), 2); /// for (i, md) in figment.metadata().enumerate() { /// match i { /// 0 => assert!(md.name.starts_with("TOML")), /// 1 => assert!(md.name.starts_with("JSON")), /// _ => unreachable!(), /// } /// } /// ``` // In fact, the order in which they were added globally. Why? Because // `BTreeMap` returns values in order of keys, and we generate a new ID, // monotonically greater than the previous, each time a new item is // provided. It's important that the IDs are unique globally since we can // allow combining `Figment`s. pub fn metadata(&self) -> impl Iterator { self.metadata.values() } /// Returns the selected profile. /// /// # Example /// /// ``` /// use figment::Figment; /// /// let figment = Figment::new(); /// assert_eq!(figment.profile(), "default"); /// /// let figment = figment.select("staging"); /// assert_eq!(figment.profile(), "staging"); /// ``` pub fn profile(&self) -> &Profile { &self.profile } /// Returns an iterator over profiles with valid configurations in this /// figment. **Note:** this may not include the selected profile if the /// selected profile has no configured values. /// /// # Example /// /// ``` /// use figment::{Figment, providers::Serialized}; /// /// let figment = Figment::new(); /// let profiles = figment.profiles().collect::>(); /// assert_eq!(profiles.len(), 0); /// /// let figment = Figment::new() /// .join(Serialized::default("key", "hi")) /// .join(Serialized::default("key", "hey").profile("debug")); /// /// let mut profiles = figment.profiles().collect::>(); /// profiles.sort(); /// assert_eq!(profiles, &["debug", "default"]); /// /// let figment = Figment::new() /// .join(Serialized::default("key", "hi").profile("release")) /// .join(Serialized::default("key", "hi").profile("testing")) /// .join(Serialized::default("key", "hey").profile("staging")) /// .select("debug"); /// /// let mut profiles = figment.profiles().collect::>(); /// profiles.sort(); /// assert_eq!(profiles, &["release", "staging", "testing"]); /// ``` pub fn profiles(&self) -> impl Iterator { self.value.as_ref() .ok() .map(|v| v.keys()) .into_iter() .flatten() } /// Finds the value at `path` in the combined value. /// /// If there is an error evaluating the combined figment, that error is /// returned. Otherwise if there is a value at `path`, returns `Ok(value)`, /// and if there is no value at `path`, returns `Err` of kind /// `MissingField`. /// /// See [`Value::find()`] for details on the syntax for `path`. /// /// # Example /// /// ```rust /// use serde::Deserialize; /// /// use figment::{Figment, providers::{Format, Toml, Json, Env}}; /// /// figment::Jail::expect_with(|jail| { /// jail.create_file("Config.toml", r#" /// name = "test" /// /// [package] /// name = "my-package" /// "#)?; /// /// jail.create_file("Config.json", r#" /// { /// "author": { "name": "Bob" } /// } /// "#)?; /// /// let figment = Figment::new() /// .merge(Toml::file("Config.toml")) /// .join(Json::file("Config.json")); /// /// let name = figment.find_value("name")?; /// assert_eq!(name.as_str(), Some("test")); /// /// let package_name = figment.find_value("package.name")?; /// assert_eq!(package_name.as_str(), Some("my-package")); /// /// let author_name = figment.find_value("author.name")?; /// assert_eq!(author_name.as_str(), Some("Bob")); /// /// Ok(()) /// }); /// ``` pub fn find_value(&self, path: &str) -> Result { self.merged()? .find(path) .ok_or_else(|| Kind::MissingField(path.to_string().into()).into()) } /// Returns `true` if the combined figment evaluates successfully and /// contains a value at `path`. /// /// See [`Value::find()`] for details on the syntax for `path`. /// /// # Example /// /// ```rust /// use serde::Deserialize; /// /// use figment::{Figment, providers::{Format, Toml, Json, Env}}; /// /// figment::Jail::expect_with(|jail| { /// jail.create_file("Config.toml", r#" /// name = "test" /// /// [package] /// name = "my-package" /// "#)?; /// /// jail.create_file("Config.json", r#" /// { /// "author": { "name": "Bob" } /// } /// "#)?; /// /// let figment = Figment::new() /// .merge(Toml::file("Config.toml")) /// .join(Json::file("Config.json")); /// /// assert!(figment.contains("name")); /// assert!(figment.contains("package")); /// assert!(figment.contains("package.name")); /// assert!(figment.contains("author")); /// assert!(figment.contains("author.name")); /// assert!(!figment.contains("author.title")); /// Ok(()) /// }); /// ``` pub fn contains(&self, path: &str) -> bool { self.merged().map_or(false, |v| v.find_ref(path).is_some()) } /// Finds the metadata for the value at `key` path. See [`Value::find()`] /// for details on the syntax for `key`. /// /// # Example /// /// ```rust /// use serde::Deserialize; /// /// use figment::{Figment, providers::{Format, Toml, Json, Env}}; /// /// figment::Jail::expect_with(|jail| { /// jail.create_file("Config.toml", r#" name = "test" "#)?; /// jail.set_env("CONF_AUTHOR", "Bob"); /// /// let figment = Figment::new() /// .merge(Toml::file("Config.toml")) /// .join(Env::prefixed("CONF_").only(&["author"])); /// /// let name_md = figment.find_metadata("name").unwrap(); /// assert!(name_md.name.starts_with("TOML")); /// /// let author_md = figment.find_metadata("author").unwrap(); /// assert!(author_md.name.contains("CONF_")); /// assert!(author_md.name.contains("environment")); /// /// Ok(()) /// }); /// ``` pub fn find_metadata(&self, key: &str) -> Option<&Metadata> { self.metadata.get(&self.find_value(key).ok()?.tag()) } /// Returns the metadata with the given `tag` if this figment contains a /// value with said metadata. /// /// # Example /// /// ```rust /// use serde::Deserialize; /// /// use figment::{Figment, providers::{Format, Toml, Json, Env}}; /// /// figment::Jail::expect_with(|jail| { /// jail.create_file("Config.toml", r#" name = "test" "#)?; /// jail.create_file("Config.json", r#" { "author": "Bob" } "#)?; /// /// let figment = Figment::new() /// .merge(Toml::file("Config.toml")) /// .join(Json::file("Config.json")); /// /// let name = figment.find_value("name").unwrap(); /// let metadata = figment.get_metadata(name.tag()).unwrap(); /// assert!(metadata.name.starts_with("TOML")); /// /// let author = figment.find_value("author").unwrap(); /// let metadata = figment.get_metadata(author.tag()).unwrap(); /// assert!(metadata.name.starts_with("JSON")); /// /// Ok(()) /// }); /// ``` pub fn get_metadata(&self, tag: Tag) -> Option<&Metadata> { self.metadata.get(&tag) } } impl Provider for Figment { fn metadata(&self) -> Metadata { Metadata::default() } fn data(&self) -> Result> { self.value.clone() } fn profile(&self) -> Option { Some(self.profile.clone()) } fn __metadata_map(&self) -> Option> { Some(self.metadata.clone()) } } impl Default for Figment { fn default() -> Self { Figment::new() } } #[test] #[cfg(test)] fn is_send_sync() { fn check_for_send_sync() {} check_for_send_sync::(); } figment-0.10.19/src/jail.rs000064400000000000000000000270561046102023000135130ustar 00000000000000use std::fs::{File, self}; use std::io::{Write, BufWriter}; use std::path::{Path, PathBuf}; use std::fmt::Display; use std::ffi::{OsStr, OsString}; use std::collections::HashMap; use tempfile::TempDir; use parking_lot::Mutex; use crate::error::Result; // TODO: Clear environment variables before entering this? Will they mess with // anything else? /// A "sandboxed" environment with isolated env and file system namespace. /// /// `Jail` creates a pseudo-sandboxed (not _actually_ sandboxed) environment for /// testing configurations. Specifically, `Jail`: /// /// * Synchronizes all calls to [`Jail::expect_with()`] and /// [`Jail::try_with()`] to prevent environment variables races. /// * Switches into a fresh temporary directory ([`Jail::directory()`]) where /// files can be created with [`Jail::create_file()`]. /// * Keeps track of environment variables created with [`Jail::set_env()`] /// and clears them when the `Jail` exits. /// * Deletes the temporary directory and all of its contents when exiting. /// /// Additionally, because `Jail` expects functions that return a [`Result`], /// the `?` operator can be used liberally in a jail: /// /// ```rust /// use figment::{Figment, Jail, providers::{Format, Toml, Env}}; /// # #[derive(serde::Deserialize)] /// # struct Config { /// # name: String, /// # authors: Vec, /// # publish: bool /// # } /// /// figment::Jail::expect_with(|jail| { /// jail.create_file("Cargo.toml", r#" /// name = "test" /// authors = ["bob"] /// publish = false /// "#)?; /// /// jail.set_env("CARGO_NAME", "env-test"); /// /// let config: Config = Figment::new() /// .merge(Toml::file("Cargo.toml")) /// .merge(Env::prefixed("CARGO_")) /// .extract()?; /// /// Ok(()) /// }); /// ``` #[cfg_attr(nightly, doc(cfg(feature = "test")))] pub struct Jail { _directory: TempDir, canonical_dir: PathBuf, saved_env_vars: HashMap>, saved_cwd: PathBuf, } /// Convert a `T: Display` to a `String`. fn as_string(s: S) -> String { s.to_string() } /// Remove any dots from the path by popping as needed. fn dedot(path: &Path) -> PathBuf { use std::path::Component::*; let mut comps = vec![]; for component in path.components() { match component { p@Prefix(_) => comps = vec![p], r@RootDir if comps.iter().all(|c| matches!(c, Prefix(_))) => comps.push(r), r@RootDir => comps = vec![r], CurDir => { }, ParentDir if comps.iter().all(|c| matches!(c, Prefix(_) | RootDir)) => { }, ParentDir => { comps.pop(); }, c@Normal(_) => comps.push(c), } } comps.iter().map(|c| c.as_os_str()).collect() } static LOCK: Mutex<()> = parking_lot::const_mutex(()); impl Jail { /// Creates a new jail that calls `f`, passing itself to `f`. /// /// # Panics /// /// Panics if `f` panics or if [`Jail::try_with(f)`](Jail::try_with) returns /// an `Err`; prints the error message. /// /// # Example /// /// ```rust /// figment::Jail::expect_with(|jail| { /// /* in the jail */ /// /// Ok(()) /// }); /// ``` #[track_caller] pub fn expect_with Result<()>>(f: F) { if let Err(e) = Jail::try_with(f) { panic!("jail failed: {}", e) } } /// Creates a new jail that calls `f`, passing itself to `f`. Returns the /// result from `f` if `f` does not panic. /// /// # Panics /// /// Panics if `f` panics. /// /// # Example /// /// ```rust /// let result = figment::Jail::try_with(|jail| { /// /* in the jail */ /// /// Ok(()) /// }); /// ``` #[track_caller] pub fn try_with Result<()>>(f: F) -> Result<()> { let _lock = LOCK.lock(); let directory = TempDir::new().map_err(as_string)?; let mut jail = Jail { canonical_dir: directory.path().canonicalize().map_err(as_string)?, _directory: directory, saved_cwd: std::env::current_dir().map_err(as_string)?, saved_env_vars: HashMap::new(), }; std::env::set_current_dir(jail.directory()).map_err(as_string)?; f(&mut jail) } /// Returns the directory the jail has switched into. The contents of this /// directory will be cleared when `Jail` is dropped. /// /// # Example /// /// ```rust /// figment::Jail::expect_with(|jail| { /// let tmp_directory = jail.directory(); /// /// Ok(()) /// }); /// ``` pub fn directory(&self) -> &Path { &self.canonical_dir } fn safe_jailed_path(&self, path: &Path) -> Result { let path = dedot(path); if path.is_absolute() && path.starts_with(self.directory()) { return Ok(path); } if !path.is_relative() { return Err("Jail: input path is outside of jail directory".to_string().into()); } Ok(path) } /// Creates a file with contents `contents` within the jail's directory. The /// file is deleted when the jail is dropped. /// /// # Errors /// /// An error is returned if `path` is not relative or is outside of the /// jail's directory. I/O errors while creating the file are returned. /// /// # Example /// /// ```rust /// figment::Jail::expect_with(|jail| { /// jail.create_file("MyConfig.json", "contents...")?; /// Ok(()) /// }); /// ``` pub fn create_file>(&self, path: P, contents: &str) -> Result { self.create_binary(path.as_ref(), contents.as_bytes()) } /// Creates a file with binary contents `bytes` within the jail's directory. /// The file is deleted when the jail is dropped. /// /// # Errors /// /// An error is returned if `path` is not relative or is outside of the /// jail's directory. I/O errors while creating the file are returned. /// /// # Example /// /// ```rust /// figment::Jail::expect_with(|jail| { /// jail.create_binary("file.bin", &[0xFF, 0x4F, 0xFF, 0x51])?; /// Ok(()) /// }); /// ``` pub fn create_binary>(&self, path: P, bytes: &[u8]) -> Result { let path = self.safe_jailed_path(path.as_ref())?; let file = File::create(path).map_err(as_string)?; let mut writer = BufWriter::new(file); writer.write_all(bytes).map_err(as_string)?; Ok(writer.into_inner().map_err(as_string)?) } /// Creates a directory at `path` within the jail's directory and returns /// the relative path to the subdirectory in the jail. Recursively creates /// directories for all of its parent components if they are missing. /// /// The directory and all of its contents are deleted when the jail is /// dropped. /// /// # Errors /// /// An error is returned if `path` is not relative or is outside of the /// jail's directory. Any I/O errors encountered while creating the /// subdirectory are returned. /// /// # Example /// /// ```rust /// use std::path::Path; /// /// figment::Jail::expect_with(|jail| { /// let dir = jail.create_dir("subdir")?; /// jail.create_file(dir.join("config.json"), "{ foo: 123 }")?; /// /// let dir = jail.create_dir("subdir/1/2")?; /// jail.create_file(dir.join("secret.toml"), "secret = 1337")?; /// /// Ok(()) /// }); /// ``` pub fn create_dir>(&self, path: P) -> Result { let path = self.safe_jailed_path(path.as_ref())?; fs::create_dir_all(&path).map_err(as_string)?; Ok(path) } /// Sets the jail's current working directory to `path` if `path` is within /// [`Jail::directory()`]. Otherwise returns an error. /// /// # Errors /// /// An error is returned if `path` is not relative or is outside of the /// jail's directory. Any I/O errors encountered while creating the /// subdirectory are returned. /// /// # Example /// /// ```rust /// use std::path::Path; /// /// figment::Jail::expect_with(|jail| { /// assert_eq!(std::env::current_dir().unwrap(), jail.directory()); /// /// let subdir = jail.create_dir("subdir")?; /// jail.change_dir(&subdir)?; /// assert_eq!(std::env::current_dir().unwrap(), jail.directory().join(subdir)); /// /// let file = jail.create_file("foo.txt", "contents")?; /// assert!(!jail.directory().join("foo.txt").exists()); /// assert!(jail.directory().join("subdir").join("foo.txt").exists()); /// /// jail.change_dir(jail.directory())?; /// assert_eq!(std::env::current_dir().unwrap(), jail.directory()); /// /// Ok(()) /// }); /// ``` pub fn change_dir>(&self, path: P) -> Result { let path = self.safe_jailed_path(path.as_ref())?; std::env::set_current_dir(&path).map_err(as_string)?; Ok(path) } /// Remove all environment variables. All variables will be restored when /// the jail is dropped. /// /// # Example /// /// ```rust /// let init_count = std::env::vars_os().count(); /// /// figment::Jail::expect_with(|jail| { /// // We start with _something_ in the env vars. /// assert!(std::env::vars_os().count() != 0); /// /// // Clear them all, and it's empty! /// jail.clear_env(); /// assert!(std::env::vars_os().count() == 0); /// /// // Set a value. /// jail.set_env("FIGMENT_SPECIAL_JAIL_VALUE", "value"); /// assert!(std::env::vars_os().count() == 1); /// /// // If we clear again, the new values are removed. /// jail.clear_env(); /// assert!(std::env::vars_os().count() == 0); /// /// Ok(()) /// }); /// /// // After the drop, we have our original env vars. /// assert!(std::env::vars_os().count() == init_count); /// assert!(std::env::var("FIGMENT_SPECIAL_JAIL_VALUE").is_err()); /// ``` pub fn clear_env(&mut self) { for (key, val) in std::env::vars_os() { std::env::remove_var(&key); if !self.saved_env_vars.contains_key(&key) { self.saved_env_vars.insert(key, Some(val)); } } } /// Set the environment variable `k` to value `v`. The variable will be /// removed when the jail is dropped. /// /// # Example /// /// ```rust /// const VAR_NAME: &str = "my-very-special-figment-var"; /// /// assert!(std::env::var(VAR_NAME).is_err()); /// /// figment::Jail::expect_with(|jail| { /// jail.set_env(VAR_NAME, "value"); /// assert!(std::env::var(VAR_NAME).is_ok()); /// Ok(()) /// }); /// /// assert!(std::env::var(VAR_NAME).is_err()); /// ``` pub fn set_env, V: Display>(&mut self, k: K, v: V) { let key = k.as_ref(); if !self.saved_env_vars.contains_key(OsStr::new(key)) { self.saved_env_vars.insert(key.into(), std::env::var_os(key)); } std::env::set_var(key, v.to_string()); } } impl Drop for Jail { fn drop(&mut self) { for (key, value) in self.saved_env_vars.iter() { match value { Some(val) => std::env::set_var(key, val), None => std::env::remove_var(key) } } let _ = std::env::set_current_dir(&self.saved_cwd); } } figment-0.10.19/src/lib.rs000064400000000000000000000522421046102023000133350ustar 00000000000000#![cfg_attr(nightly, feature(doc_cfg))] #![deny(missing_docs)] //! Semi-hierarchical configuration so con-free, it's unreal. //! //! ```rust //! use serde::Deserialize; //! use figment::{Figment, providers::{Format, Toml, Json, Env}}; //! //! #[derive(Deserialize)] //! struct Package { //! name: String, //! description: Option, //! authors: Vec, //! publish: Option, //! // ... and so on ... //! } //! //! #[derive(Deserialize)] //! struct Config { //! package: Package, //! rustc: Option, //! rustdoc: Option, //! // ... and so on ... //! } //! //! # figment::Jail::expect_with(|jail| { //! # jail.create_file("Cargo.toml", r#" //! # [package] //! # name = "test" //! # authors = ["bob"] //! # publish = false //! # "#)?; //! let config: Config = Figment::new() //! .merge(Toml::file("Cargo.toml")) //! .merge(Env::prefixed("CARGO_")) //! .merge(Env::raw().only(&["RUSTC", "RUSTDOC"])) //! .join(Json::file("Cargo.json")) //! .extract()?; //! # Ok(()) //! # }); //! ``` //! //! # Table of Contents //! //! * [Overview](#overview) - A brief overview of the entire crate. //! * [Metadata](#metadata) - Figment's value metadata tracking. //! * [Extracting and Profiles](#extracting-and-profiles) - Semi-hierarchical //! "profiles", profile selection, nesting, and extraction. //! * [Crate Feature Flags](#crate-feature-flags) - Feature flags and what //! they enable. //! * [Available Providers](#available-providers) - Table of providers //! provided by this and other crates. //! * [For `Provider` Authors](#for-provider-authors) - Tips for writing //! [`Provider`]s. //! * [For Library Authors](#for-library-authors) - Brief guide for authors //! wishing to use Figment in their libraries or frameworks. //! * [For Application Authors](#for-application-authors) - Brief guide for //! authors of applications that use libraries that use Figment. //! * [For CLI Application Authors](#for-cli-application-authors) - Brief //! guide for authors of applications with a CLI and other configuration //! sources. //! * [Tips](#tips) - Things to remember when working with Figment. //! * [Type Index](#modules) - The real rustdocs. //! //! # Overview //! //! Figment is a library for declaring and combining configuration sources and //! extracting typed values from the combined sources. It distinguishes itself //! from other libraries with similar motives by seamlessly and comprehensively //! tracking configuration value provenance, even in the face of myriad sources. //! This means that error values and messages are precise and know exactly where //! and how misconfiguration arose. //! //! There are two prevailing concepts: //! //! * **Providers:** Types implementing the [`Provider`] trait, which //! implement a configuration source. //! * **Figments:** The [`Figment`] type, which combines providers via //! [`merge`](Figment::merge()) or [`join`](Figment::join) and allows //! typed [`extraction`](Figment::extract()). Figments are also providers //! themselves. //! //! Defining a configuration consists of constructing a `Figment` and merging or //! joining any number of [`Provider`]s. Values for duplicate keys from a //! _merged_ provider replace those from previous providers, while no //! replacement occurs for _joined_ providers. Sources are read eagerly, //! immediately upon merging and joining. //! //! The simplest useful figment has one provider. The figment below will use all //! environment variables prefixed with `MY_APP_` as configuration values, after //! removing the prefix: //! //! ``` //! use figment::{Figment, providers::Env}; //! //! let figment = Figment::from(Env::prefixed("MY_APP_")); //! ``` //! //! Most figments will use more than one provider, merging and joining as //! necessary. The figment below reads `App.toml`, environment variables //! prefixed with `APP_` and fills any holes (but does not replace existing //! values) with values from `App.json`: //! //! ``` //! use figment::{Figment, providers::{Format, Toml, Json, Env}}; //! //! let figment = Figment::new() //! .merge(Toml::file("App.toml")) //! .merge(Env::prefixed("APP_")) //! .join(Json::file("App.json")); //! ``` //! //! Values can be [`extracted`](Figment::extract()) into any value that //! implements [`Deserialize`](serde::Deserialize). The [`Jail`] type allows for //! semi-sandboxed configuration testing. The example below showcases //! extraction and testing: //! //! ```rust //! use serde::Deserialize; //! use figment::{Figment, providers::{Format, Toml, Json, Env}}; //! //! #[derive(Debug, PartialEq, Deserialize)] //! struct AppConfig { //! name: String, //! count: usize, //! authors: Vec, //! } //! //! figment::Jail::expect_with(|jail| { //! jail.create_file("App.toml", r#" //! name = "Just a TOML App!" //! count = 100 //! "#)?; //! //! jail.create_file("App.json", r#" //! { //! "name": "Just a JSON App", //! "authors": ["figment", "developers"] //! } //! "#)?; //! //! jail.set_env("APP_COUNT", 250); //! //! // Sources are read _eagerly_: sources are read as soon as they are //! // merged/joined into a figment. //! let figment = Figment::new() //! .merge(Toml::file("App.toml")) //! .merge(Env::prefixed("APP_")) //! .join(Json::file("App.json")); //! //! let config: AppConfig = figment.extract()?; //! assert_eq!(config, AppConfig { //! name: "Just a TOML App!".into(), //! count: 250, //! authors: vec!["figment".into(), "developers".into()], //! }); //! //! Ok(()) //! }); //! ``` //! //! # Metadata //! //! Figment takes _great_ care to propagate as much information as possible //! about configuration sources. All values extracted from a figment are //! [tagged](crate::value::Tag) with the originating [`Metadata`] and //! [`Profile`]. The tag is preserved across merges, joins, and errors, which //! also include the [`path`](Error::path) of the offending key. Precise //! tracking allows for rich error messages as well as ["magic"] values like //! [`RelativePathBuf`], which automatically creates a path relative to the //! configuration file in which it was declared. //! //! A [`Metadata`] consists of: //! //! * The name of the configuration source. //! * An ["interpolater"](Metadata::interpolate()) that takes a path to a key //! and converts it into a provider-native key. //! * A [`Source`] specifying where the value was sourced from. //! * A code source [`Location`] where the value's provider was added to a //! [`Figment`]. //! //! Along with the information in an [`Error`], this means figment can produce //! rich error values and messages: //! //! ```text //! error: invalid type: found string "hi", expected u16 //! --> key `debug.port` in TOML file App.toml //! ``` //! //! [`RelativePathBuf`]: value::magic::RelativePathBuf //! ["magic"]: value::magic //! [`Location`]: std::panic::Location //! //! # Extracting and Profiles //! //! Providers _always_ [produce](Provider::data()) [`Dict`](value::Dict)s nested //! in [`Profile`]s. A profile is [`selected`](Figment::select()) when //! extracting, and the dictionary corresponding to that profile is deserialized //! into the requested type. If no profile is selected, the //! [`Default`](Profile::Default) profile is used. //! //! There are two built-in profiles: the aforementioned default profile and the //! [`Global`](Profile::Global) profile. As the name implies, the default //! profile contains default values for all profiles. The global profile _also_ //! contains values that correspond to all profiles, but those values supersede //! values of any other profile _except_ the global profile, even when another //! source is merged. //! //! Some providers can be configured as `nested`, which allows top-level keys in //! dictionaries produced by the source to be treated as profiles. The following //! example showcases profiles and nesting: //! //! ```rust //! use serde::Deserialize; //! use figment::{Figment, providers::{Format, Toml, Json, Env}}; //! //! #[derive(Debug, PartialEq, Deserialize)] //! struct Config { //! name: String, //! } //! //! impl Config { //! // Note the `nested` option on both `file` providers. This makes each //! // top-level dictionary act as a profile. //! fn figment() -> Figment { //! Figment::new() //! .merge(Toml::file("Base.toml").nested()) //! .merge(Toml::file("App.toml").nested()) //! } //! } //! //! figment::Jail::expect_with(|jail| { //! jail.create_file("Base.toml", r#" //! [default] //! name = "Base-Default" //! //! [debug] //! name = "Base-Debug" //! "#)?; //! //! // The default profile is used...by default. //! let config: Config = Config::figment().extract()?; //! assert_eq!(config, Config { name: "Base-Default".into(), }); //! //! // A different profile can be selected with `select`. //! let config: Config = Config::figment().select("debug").extract()?; //! assert_eq!(config, Config { name: "Base-Debug".into(), }); //! //! // Selecting non-existent profiles is okay as long as we have defaults. //! let config: Config = Config::figment().select("undefined").extract()?; //! assert_eq!(config, Config { name: "Base-Default".into(), }); //! //! // Replace the previous `Base.toml`. This one has a `global` profile. //! jail.create_file("Base.toml", r#" //! [default] //! name = "Base-Default" //! //! [debug] //! name = "Base-Debug" //! //! [global] //! name = "Base-Global" //! "#)?; //! //! // Global values override all profile values. //! let config_def: Config = Config::figment().extract()?; //! let config_deb: Config = Config::figment().select("debug").extract()?; //! assert_eq!(config_def, Config { name: "Base-Global".into(), }); //! assert_eq!(config_deb, Config { name: "Base-Global".into(), }); //! //! // Merges from succeeding providers take precedence, even for globals. //! jail.create_file("App.toml", r#" //! [debug] //! name = "App-Debug" //! //! [global] //! name = "App-Global" //! "#)?; //! //! let config_def: Config = Config::figment().extract()?; //! let config_deb: Config = Config::figment().select("debug").extract()?; //! assert_eq!(config_def, Config { name: "App-Global".into(), }); //! assert_eq!(config_deb, Config { name: "App-Global".into(), }); //! //! Ok(()) //! }); //! ``` //! //! # Crate Feature Flags //! //! To help with compilation times, types, modules, and providers are gated by //! features. They are: //! //! | feature | gated namespace | description | //! |---------|-----------------------------|-------------------------------------------| //! | `test` | [`Jail`] | Semi-sandboxed environment for testing. | //! | `env` | [`providers::Env`] | Environment variable [`Provider`]. | //! | `toml` | [`providers::Toml`] | TOML file/string [`Provider`]. | //! | `json` | [`providers::Json`] | JSON file/string [`Provider`]. | //! | `yaml` | [`providers::Yaml`] | YAML file/string [`Provider`]. | //! | `yaml` | [`providers::YamlExtended`] | [YAML Extended] file/string [`Provider`]. | //! //! [YAML Extended]: providers::YamlExtended::from_str() //! //! # Available Providers //! //! In addition to the four gated providers above, figment provides the //! following providers out-of-the-box: //! //! | provider | description | //! |---------------------------------------|----------------------------------------| //! | [`providers::Serialized`] | Source from any [`Serialize`] type. | //! | [`(impl AsRef, impl Serialize)`] | Global source from a `("key", value)`. | //! | [`&T` _where_ `T: Provider`] | Source from `T` as a reference. | //! //! //! //! Note: `key` in `(key, value)` is a _key path_, e.g. `"a"` or `"a.b.c"`, //! where the latter indicates a nested value `c` in `b` in `a`. //! //! See [`Figment#extraction`] and [Data //! (keyed)](providers::Serialized#provider-details) for key path details. //! //! //! //! [`Serialize`]: serde::Serialize //! [`(impl AsRef, impl Serialize)`]: Provider#impl-Provider-for-(K%2C%20V) //! [`&T` _where_ `T: Provider`]: Provider#impl-Provider-for-%26%27_%20T //! //! ### Third-Party Providers //! //! The following external libraries implement Figment providers: //! //! - [`figment_file_provider_adapter`](https://crates.io/crates/figment_file_provider_adapter) //! //! Wraps existing providers. For any key ending in `_FILE` (configurable), //! emits a key without the `_FILE` suffix with a value corresponding to the //! contents of the file whose path is the original key's value. //! //! # For Provider Authors //! //! The [`Provider`] trait documentation details extensively how to implement a //! provider for Figment. For data format based providers, the [`Format`] trait //! allows for even simpler implementations. //! //! [`Format`]: providers::Format //! //! # For Library Authors //! //! For libraries and frameworks that wish to expose customizable configuration, //! we encourage the following structure: //! //! ```rust //! use serde::{Serialize, Deserialize}; //! //! use figment::{Figment, Provider, Error, Metadata, Profile}; //! //! // The library's required configuration. //! #[derive(Debug, Deserialize, Serialize)] //! struct Config { /* the library's required/expected values */ } //! //! // The default configuration. //! impl Default for Config { //! fn default() -> Self { //! Config { /* default values */ } //! } //! } //! //! impl Config { //! // Allow the configuration to be extracted from any `Provider`. //! fn from(provider: T) -> Result { //! Figment::from(provider).extract() //! } //! //! // Provide a default provider, a `Figment`. //! fn figment() -> Figment { //! use figment::providers::Env; //! //! // In reality, whatever the library desires. //! Figment::from(Config::default()).merge(Env::prefixed("APP_")) //! } //! } //! //! use figment::value::{Map, Dict}; //! //! // Make `Config` a provider itself for composability. //! impl Provider for Config { //! fn metadata(&self) -> Metadata { //! Metadata::named("Library Config") //! } //! //! fn data(&self) -> Result, Error> { //! figment::providers::Serialized::defaults(Config::default()).data() //! } //! //! fn profile(&self) -> Option { //! // Optionally, a profile that's selected by default. //! # None //! } //! } //! ``` //! //! This structure has the following properties: //! //! * The library provides a `Config` structure that clearly indicates which //! values the library requires. //! * Users can completely customize configuration via their own [`Provider`]. //! * The library's `Config` is itself a [`Provider`] for composability. //! * The library provides a `Figment` which it will use as the default //! configuration provider. //! //! `Config::from(Config::figment())` can be used as the library default while //! allowing complete customization of the configuration sources. Developers //! building on the library can base their figments on `Config::default()`, //! `Config::figment()`, both or neither. //! //! For frameworks, a top-level structure should expose the `Figment` that was //! used to extract the `Config`, allowing other libraries making use of the //! framework to also extract values from the same `Figment`: //! //! ```rust,no_run //! use figment::{Figment, Provider, Error}; //! # struct Config; //! # impl Config { //! # fn figment() -> Figment { panic!() } //! # fn from(_: T) -> Result { panic!() } //! # } //! //! struct App { //! /// The configuration. //! pub config: Config, //! /// The figment used to extract the configuration. //! pub figment: Figment, //! } //! //! impl App { //! pub fn new() -> Result { //! App::custom(Config::figment()) //! } //! //! pub fn custom(provider: T) -> Result { //! let figment = Figment::from(provider); //! Ok(App { config: Config::from(&figment)?, figment }) //! } //! } //! ``` //! //! # For Application Authors //! //! As an application author, you'll need to make at least the following //! decisions: //! //! 1. The sources you'll accept configuration from. //! 2. The precedence you'll apply to each source. //! 3. Whether you'll use profiles or not. //! //! For special sources, you may find yourself needing to implement a custom //! [`Provider`]. As with libraries, you'll likely want to provide default //! values where possible either by providing it to the figment or by using //! [serde's defaults](https://serde.rs/attr-default.html). Then, it's simply a //! matter of declaring a figment and extracting the configuration from it. //! //! A reasonable starting point might be: //! //! ```rust //! use serde::{Serialize, Deserialize}; //! use figment::{Figment, providers::{Env, Format, Toml, Serialized}}; //! //! #[derive(Deserialize, Serialize)] //! struct Config { //! key: String, //! another: u32 //! } //! //! impl Default for Config { //! fn default() -> Config { //! Config { //! key: "default".into(), //! another: 100, //! } //! } //! } //! //! Figment::from(Serialized::defaults(Config::default())) //! .merge(Toml::file("App.toml")) //! .merge(Env::prefixed("APP_")); //! ``` //! //! # For CLI Application Authors //! //! As an author of an application with a CLI, you may want to use Figment in //! combination with a library like [`clap`] if: //! //! * You want to read configuration from sources outside of the CLI. //! * You want flexibility in how configuration sources are combined. //! * You want great error messages irrespective of how the application is //! configured. //! //! [`clap`]: https://docs.rs/clap/latest/clap/ //! //! If any of these conditions apply, Figment is a great choice. //! //! If you are already using a library like [`clap`], you'll likely have a //! configuration structure defined: //! //! ```rust //! use clap::Parser; //! //! #[derive(Parser, Debug)] //! struct Config { //! /// Name of the person to greet. //! #[clap(short, long, value_parser)] //! name: String, //! //! /// Number of times to greet //! #[clap(short, long, value_parser, default_value_t = 1)] //! count: u8, //! } //! ``` //! //! To enable the structure to be combined with other Figment sources, derive //! `Serialize` and `Deserialize` for the structure: //! //! ```diff //! + use serde::{Serialize, Deserialize}; //! //! - #[derive(Parser, Debug)] //! + #[derive(Parser, Debug, Serialize, Deserialize)] //! struct Config { //! ``` //! //! It can then be combined with other sources via the //! [`Serialized`](providers::Serialized) provider: //! //! ```rust //! use clap::Parser; //! use figment::{Figment, providers::{Serialized, Toml, Env, Format}}; //! use serde::{Serialize, Deserialize}; //! //! #[derive(Parser, Debug, Serialize, Deserialize)] //! struct Config { //! // ... //! } //! //! # figment::Jail::try_with(|_| { //! // Parse CLI arguments. Override CLI config values with those in //! // `Config.toml` and `APP_`-prefixed environment variables. //! let config: Config = Figment::new() //! .merge(Serialized::defaults(Config::parse())) //! .merge(Toml::file("Config.toml")) //! .merge(Env::prefixed("APP_")) //! .extract()?; //! # Ok(()) //! # }); //! ``` //! //! See [For Application Authors](#for-application-authors) for further, general //! guidance on using Figment for application configuration. //! //! # Tips //! //! Some things to remember when working with Figment: //! //! * Merging and joining are _eager_: sources are read immediately. It's //! useful to define a function that returns a `Figment`. //! * The [`util`] modules contains helpful serialize and deserialize //! implementations for defining `Config` structures. //! * The [`Format`] trait makes implementing data-format based [`Provider`]s //! straight-forward. //! * [`Magic`](value::magic) values can significantly reduce the need to //! inspect a `Figment` directly. //! * [`Jail`] makes testing configurations straight-forward and much less //! error-prone. //! * [`Error`] may contain more than one error: iterate over it to retrieve //! all errors. //! * Using `#[serde(flatten)]` [can break error attribution], so it's best to //! avoid using it when possible. //! //! [can break error attribution]: //! https://github.com/SergioBenitez/Figment/issues/80#issuecomment-1701946622 pub mod value; pub mod providers; pub mod error; pub mod util; mod figment; mod profile; mod coalesce; mod metadata; mod provider; #[cfg(any(test, feature = "test"))] mod jail; #[cfg(any(test, feature = "test"))] pub use jail::Jail; #[doc(inline)] pub use error::{Error, Result}; pub use self::figment::Figment; pub use profile::Profile; pub use provider::*; pub use metadata::*; figment-0.10.19/src/metadata.rs000064400000000000000000000244261046102023000143520ustar 00000000000000use std::fmt; use std::borrow::Cow; use std::path::{Path, PathBuf}; use std::panic::Location; use crate::Profile; /// Metadata about a configuration value: its source's name and location. /// /// # Overview /// /// Every [`Value`] produced by a [`Figment`] is [`Tag`]ed with `Metadata` /// by its producing [`Provider`]. The metadata consists of: /// /// * A name for the source, e.g. "TOML File". /// * The [`Source`] itself, if it is known. /// * A default or custom [interpolater](#interpolation). /// * A source [`Location`] where a value's provider was added to the /// containing figment, if it is known. /// /// This information is used to produce insightful error messages as well as to /// generate values like [`RelativePathBuf`] that know about their configuration /// source. /// /// [`Location`]: std::panic::Location /// /// ## Errors /// /// [`Error`]s produced by [`Figment`]s contain the `Metadata` for the value /// that caused the error. The `Display` implementation for `Error` uses the /// metadata's interpolater to display the path to the key for the value that /// caused the error. /// /// ## Interpolation /// /// Interpolation takes a figment profile and key path (`a.b.c`) and turns it /// into a source-native path. The default interpolater returns a figment key /// path prefixed with the profile if the profile is custom: /// /// ```text /// ${profile}.${a}.${b}.${c} /// ``` /// /// Providers are free to implement any interpolater for their metadata. For /// example, the interpolater for [`Env`] uppercases each path key: /// /// ```rust /// use figment::Metadata; /// /// let metadata = Metadata::named("environment variable(s)") /// .interpolater(|profile, path| { /// let keys: Vec<_> = path.iter() /// .map(|k| k.to_ascii_uppercase()) /// .collect(); /// /// format!("{}", keys.join(".")) /// }); /// /// let profile = figment::Profile::Default; /// let interpolated = metadata.interpolate(&profile, &["key", "path"]); /// assert_eq!(interpolated, "KEY.PATH"); /// ``` /// /// [`Provider`]: crate::Provider /// [`Error`]: crate::Error /// [`Figment`]: crate::Figment /// [`RelativePathBuf`]: crate::value::magic::RelativePathBuf /// [`value`]: crate::value::Value /// [`Tag`]: crate::value::Tag /// [`Env`]: crate::providers::Env #[derive(Debug, Clone)] pub struct Metadata { /// The name of the configuration source for a given value. pub name: Cow<'static, str>, /// The source of the configuration value, if it is known. pub source: Option, /// The source location where this value's provider was added to the /// containing figment, if it is known. pub provide_location: Option<&'static Location<'static>>, interpolater: Box, } impl Metadata { /// Creates a new `Metadata` with the given `name` and `source`. /// /// # Example /// /// ```rust /// use figment::Metadata; /// /// let metadata = Metadata::from("AWS Config Store", "path/to/value"); /// assert_eq!(metadata.name, "AWS Config Store"); /// assert_eq!(metadata.source.unwrap().custom(), Some("path/to/value")); /// ``` #[inline(always)] pub fn from(name: N, source: S) -> Self where N: Into>, S: Into { Metadata::named(name).source(source) } /// Creates a new `Metadata` with the given `name` and no source. /// /// # Example /// /// ```rust /// use figment::Metadata; /// /// let metadata = Metadata::named("AWS Config Store"); /// assert_eq!(metadata.name, "AWS Config Store"); /// assert!(metadata.source.is_none()); /// ``` #[inline] pub fn named>>(name: T) -> Self { Metadata { name: name.into(), ..Metadata::default() } } /// Sets the `source` of `self` to `Some(source)`. /// /// # Example /// /// ```rust /// use figment::Metadata; /// /// let metadata = Metadata::named("AWS Config Store").source("config/path"); /// assert_eq!(metadata.name, "AWS Config Store"); /// assert_eq!(metadata.source.unwrap().custom(), Some("config/path")); /// ``` #[inline(always)] pub fn source>(mut self, source: S) -> Self { self.source = Some(source.into()); self } /// Sets the `interpolater` of `self` to the function `f`. The interpolater /// can be invoked via [`Metadata::interpolate()`]. /// /// # Example /// /// ```rust /// use figment::Metadata; /// /// let metadata = Metadata::named("environment variable(s)") /// .interpolater(|profile, path| { /// let keys: Vec<_> = path.iter() /// .map(|k| k.to_ascii_uppercase()) /// .collect(); /// /// format!("{}", keys.join(".")) /// }); /// /// let profile = figment::Profile::Default; /// let interpolated = metadata.interpolate(&profile, &["key", "path"]); /// assert_eq!(interpolated, "KEY.PATH"); /// ``` #[inline(always)] pub fn interpolater(mut self, f: I) -> Self where I: Fn(&Profile, &[&str]) -> String { self.interpolater = Box::new(f); self } /// Runs the interpolater in `self` on `profile` and `keys`. /// /// # Example /// /// ```rust /// use figment::{Metadata, Profile}; /// /// let url = "ftp://config.dev"; /// let md = Metadata::named("Network").source(url) /// .interpolater(move |profile, keys| match profile.is_custom() { /// true => format!("{}/{}/{}", url, profile, keys.join("/")), /// false => format!("{}/{}", url, keys.join("/")), /// }); /// /// let interpolated = md.interpolate(&Profile::Default, &["key", "path"]); /// assert_eq!(interpolated, "ftp://config.dev/key/path"); /// /// let profile = Profile::new("static"); /// let interpolated = md.interpolate(&profile, &["key", "path"]); /// assert_eq!(interpolated, "ftp://config.dev/static/key/path"); /// ``` pub fn interpolate>(&self, profile: &Profile, keys: &[K]) -> String { let keys: Vec<_> = keys.iter().map(|k| k.as_ref()).collect(); (self.interpolater)(profile, &keys) } } impl PartialEq for Metadata { fn eq(&self, other: &Self) -> bool { self.name == other.name && self.source == other.source } } impl Default for Metadata { fn default() -> Self { Self { name: "Default".into(), source: None, provide_location: None, interpolater: Box::new(default_interpolater), } } } /// The source for a configuration value. /// /// The `Source` of a given value can be determined via that value's /// [`Metadata.source`](Metadata#structfield.source) retrievable via the value's /// [`Tag`] (via [`Value::tag()`] or via the magic value [`Tagged`]) and /// [`Figment::get_metadata()`]. /// /// [`Tag`]: crate::value::Tag /// [`Value::tag()`]: crate::value::Value::tag() /// [`Tagged`]: crate::value::magic::Tagged /// [`Figment::get_metadata()`]: crate::Figment::get_metadata() #[non_exhaustive] #[derive(PartialEq, Debug, Clone)] pub enum Source { /// A file: the path to the file. File(PathBuf), /// Some programatic value: the source location. Code(&'static Location<'static>), /// A custom source all-together. Custom(String), } impl Source { /// Returns the path to the source file if `self.kind` is `Kind::File`. /// /// # Example /// /// ```rust /// use std::path::Path; /// use figment::Source; /// /// let source = Source::from(Path::new("a/b/c.txt")); /// assert_eq!(source.file_path(), Some(Path::new("a/b/c.txt"))); /// ``` pub fn file_path(&self) -> Option<&Path> { match self { Source::File(ref p) => Some(p), _ => None, } } /// Returns the location to the source code if `self` is `Source::Code`. /// /// # Example /// /// ```rust /// use std::panic::Location; /// /// use figment::Source; /// /// let location = Location::caller(); /// let source = Source::Code(location); /// assert_eq!(source.code_location(), Some(location)); /// ``` pub fn code_location(&self) -> Option<&'static Location<'static>> { match self { Source::Code(s) => Some(s), _ => None } } /// Returns the custom source location if `self` is `Source::Custom`. /// /// # Example /// /// ```rust /// use figment::Source; /// /// let source = Source::Custom("ftp://foo".into()); /// assert_eq!(source.custom(), Some("ftp://foo")); /// ``` pub fn custom(&self) -> Option<&str> { match self { Source::Custom(ref c) => Some(c), _ => None, } } } /// Displays the source. Location and custom sources are displayed directly. /// File paths are displayed relative to the current working directory if the /// relative path is shorter than the complete path. impl fmt::Display for Source { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Source::File(p) => { use {std::env::current_dir, crate::util::diff_paths}; match current_dir().ok().and_then(|cwd| diff_paths(p, &cwd)) { Some(r) if r.iter().count() < p.iter().count() => r.display().fmt(f), Some(_) | None => p.display().fmt(f) } } Source::Code(l) => l.fmt(f), Source::Custom(c) => c.fmt(f), } } } impl From<&Path> for Source { fn from(path: &Path) -> Source { Source::File(path.into()) } } impl From<&'static Location<'static>> for Source { fn from(location: &'static Location<'static>) -> Source { Source::Code(location) } } impl From<&str> for Source { fn from(string: &str) -> Source { Source::Custom(string.into()) } } impl From for Source { fn from(string: String) -> Source { Source::Custom(string) } } crate::util::cloneable_fn_trait!( Interpolator: Fn(&Profile, &[&str]) -> String + Send + Sync + 'static ); fn default_interpolater(profile: &Profile, keys: &[&str]) -> String { format!("{}.{}", profile, keys.join(".")) } figment-0.10.19/src/profile.rs000064400000000000000000000176701046102023000142350ustar 00000000000000use serde::{de, ser}; use uncased::{Uncased, UncasedStr}; use crate::value::{Dict, Map}; /// A configuration profile: effectively a case-insensitive string. /// /// See [the top-level docs](crate#extracting-and-profiles) for details. #[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)] pub struct Profile(Uncased<'static>); impl Default for Profile { fn default() -> Self { Profile::Default } } /// 2-bit tags used by the top bits of `Tag`. #[repr(u8)] #[derive(Debug, Copy, Clone, PartialEq)] pub(crate) enum ProfileTag { Default = 0b00, Global = 0b01, Custom = 0b11, } impl From for ProfileTag { fn from(bits: u8) -> ProfileTag { if bits == ProfileTag::Default as u8 { ProfileTag::Default } else if bits == ProfileTag::Global as u8 { ProfileTag::Global } else { ProfileTag::Custom } } } impl From for Option { fn from(tag: ProfileTag) -> Self { match tag { ProfileTag::Default => Some(Profile::Default), ProfileTag::Global => Some(Profile::Global), ProfileTag::Custom => None, } } } impl From<&Profile> for ProfileTag { fn from(profile: &Profile) -> Self { match profile { p if p == Profile::Default => ProfileTag::Default, p if p == Profile::Global => ProfileTag::Global, _ => ProfileTag::Custom } } } impl Profile { /// The default profile: `"default"`. #[allow(non_upper_case_globals)] pub const Default: Profile = Profile::const_new("default"); /// The global profile: `"global"`. #[allow(non_upper_case_globals)] pub const Global: Profile = Profile::const_new("global"); /// Constructs a profile with the name `name`. /// /// # Example /// /// ```rust /// use figment::Profile; /// /// let profile = Profile::new("staging"); /// assert_eq!(profile, "staging"); /// assert_eq!(profile, "STAGING"); /// ``` pub fn new(name: &str) -> Profile { Profile(name.to_string().into()) } /// A `const` to construct a profile with the name `name`. /// /// # Example /// /// ```rust /// use figment::Profile; /// /// const STAGING: Profile = Profile::const_new("staging"); /// /// assert_eq!(STAGING, "staging"); /// assert_eq!(STAGING, "STAGING"); /// ``` pub const fn const_new(name: &'static str) -> Profile { Profile(Uncased::from_borrowed(name)) } /// Constructs a profile from the value of the environment variable with /// name `key`, if one is present. The search for `key` is case-insensitive. /// /// # Example /// /// ```rust /// use figment::{Profile, Jail}; /// /// Jail::expect_with(|jail| { /// jail.set_env("MY_PROFILE", "secret"); /// /// assert_eq!(Profile::from_env("MY_PROFILE"), Some("secret".into())); /// assert_eq!(Profile::from_env("MY_PROFILE"), Some("secret".into())); /// assert_eq!(Profile::from_env("MY_profile"), Some("secret".into())); /// assert_eq!(Profile::from_env("other_profile"), None); /// Ok(()) /// }); /// ``` pub fn from_env(key: &str) -> Option { for (env_key, val) in std::env::vars_os() { let env_key = env_key.to_string_lossy(); if uncased::eq(env_key.trim(), key) { return Some(Profile::new(&val.to_string_lossy())); } } None } /// Constructs a profile from the value of the environment variable with /// name `var`, if one is present, or `default` if one is not. The search /// for `var` is case-insensitive. /// /// # Example /// /// ```rust /// use figment::{Profile, Jail}; /// /// Jail::expect_with(|jail| { /// jail.set_env("MY_PROFILE", "secret"); /// /// assert_eq!(Profile::from_env_or("MY_PROFILE", "default"), "secret"); /// assert_eq!(Profile::from_env_or("MY_profile", "default"), "secret"); /// assert_eq!(Profile::from_env_or("other_prof", "default"), "default"); /// Ok(()) /// }); /// ``` pub fn from_env_or>(var: &str, default: P) -> Self { Profile::from_env(var).unwrap_or_else(|| default.into()) } /// Converts `self` into an `&UncasedStr`. /// /// # Example /// /// ```rust /// use figment::Profile; /// /// let profile = Profile::new("static"); /// let string = profile.as_str(); /// ``` pub fn as_str(&self) -> &UncasedStr { &self.0 } /// Returns `true` iff `self` case-insensitively starts with `prefix`. /// /// # Example /// /// ```rust /// use figment::Profile; /// /// let profile = Profile::new("static"); /// assert!(profile.starts_with("STAT")); /// assert!(profile.starts_with("stat")); /// assert!(profile.starts_with("static")); /// ``` pub fn starts_with(&self, prefix: &str) -> bool { self.as_str().starts_with(prefix) } /// Returns `true` iff `self` is neither "default" nor "global". /// /// # Example /// /// ```rust /// use figment::Profile; /// /// let profile = Profile::new("static"); /// assert!(profile.is_custom()); /// /// assert!(!Profile::Default.is_custom()); /// assert!(!Profile::Global.is_custom()); /// ``` pub fn is_custom(&self) -> bool { self != Profile::Default && self != Profile::Global } /// Creates a new map with a single key of `*self` and a value of `dict`. /// /// # Example /// /// ```rust /// use figment::{Profile, util::map}; /// /// let profile = Profile::new("static"); /// let map = profile.collect(map!["hi".into() => 123.into()]); /// ``` pub fn collect(&self, dict: Dict) -> Map { let mut map = Map::new(); map.insert(self.clone(), dict); map } } impl> From for Profile { fn from(string: T) -> Profile { Profile::new(string.as_ref()) } } impl From for String { fn from(profile: Profile) -> String { profile.0.to_string() } } impl std::ops::Deref for Profile { type Target = UncasedStr; fn deref(&self) -> &UncasedStr { self.as_str() } } impl std::fmt::Display for Profile { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.as_str().fmt(f) } } impl PartialEq for Profile { fn eq(&self, other: &str) -> bool { self.as_str() == other } } impl PartialEq<&str> for Profile { fn eq(&self, other: &&str) -> bool { self.as_str() == other } } impl PartialEq for str { fn eq(&self, other: &Profile) -> bool { self == other.as_str() } } impl PartialEq for &str { fn eq(&self, other: &Profile) -> bool { self == other.as_str() } } impl PartialEq for &Profile { fn eq(&self, other: &Profile) -> bool { self.as_str() == other.as_str() } } impl PartialEq<&Profile> for Profile { fn eq(&self, other: &&Profile) -> bool { self.as_str() == other.as_str() } } impl<'de> de::Deserialize<'de> for Profile { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de> { struct Visitor; impl<'de> de::Visitor<'de> for Visitor { type Value = Profile; fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { formatter.write_str("a string") } fn visit_str(self, v: &str) -> Result { Ok(Profile::from(v)) } } deserializer.deserialize_str(Visitor) } } impl ser::Serialize for Profile { fn serialize(&self, s: S) -> Result { s.serialize_str(self.as_str().as_str()) } } figment-0.10.19/src/provider.rs000064400000000000000000000117511046102023000144210ustar 00000000000000use crate::{Profile, Error, Metadata}; use crate::value::{Tag, Map, Dict}; /// Trait implemented by configuration source providers. /// /// For an overview of built-in providers, see the [top-level /// docs](crate#built-in-providers). /// /// # Overview /// /// A [`Provider`] reads from a source to provide configuration data for /// [`Figment`]s ([`Provider::data()`]). A `Provider` also provides [`Metadata`] /// to identify the source of its configuration data ([`Provider::metadata()`]). /// A provider may also optionally set a `Profile` for the `Figment` it is /// merged (but not joined) into by implementing [`Provider::profile()`]. /// /// # Nesting /// /// A [`Provider`] meant to be consumed externally should allow for optional /// [nesting](crate#extracting-and-profiles) when sensible. The general pattern /// is to allow a `Profile` to be specified. If one is not, read the /// configuration data as a `Map`, thus using the top-level keys /// as profiles. If one _is_ specified, read the data as `Dict` and /// [`Profile::collect()`] into the specified profile. /// /// # Example /// /// Implementing a `Provider` requires implementing methods that provide both of /// these pieces of data. The first, [`Provider::metadata()`] identifies the /// provider's configuration sources, if any, and allows the provider to /// customize how paths to keys are interpolated. The second, /// [`Provider::data()`], actually reads the configuration and returns the data. /// /// As an example, consider a provider that reads configuration from a /// networked store at some `Url`. A `Provider` implementation for such a /// provider may resemble the following: /// /// ```rust,no_run /// # use serde::Deserialize; /// use figment::{Provider, Metadata, Profile, Error, value::{Map, Dict}}; /// /// # type Url = String; /// /// A provider that fetches its data from a given URL. /// struct NetProvider { /// /// The profile to emit data to if nesting is disabled. /// profile: Option, /// /// The url to fetch data from. /// url: Url /// }; /// /// impl Provider for NetProvider { /// /// Returns metadata with kind `Network`, custom source `self.url`, /// /// and interpolator that returns a URL of `url/a/b/c` for key `a.b.c`. /// fn metadata(&self) -> Metadata { /// let url = self.url.clone(); /// Metadata::named("Network") /// .source(self.url.as_str()) /// .interpolater(move |profile, keys| match profile.is_custom() { /// true => format!("{}/{}/{}", url, profile, keys.join("/")), /// false => format!("{}/{}", url, keys.join("/")), /// }) /// } /// /// /// Fetches the data from `self.url`. Note that `Dict`, `Map`, and /// /// `Profile` are `Deserialize`, so we can deserialized to them. /// fn data(&self) -> Result, Error> { /// fn fetch<'a, T: Deserialize<'a>>(url: &Url) -> Result { /// /* fetch from the network, deserialize into `T` */ /// # todo!() /// } /// /// match &self.profile { /// // Don't nest: `fetch` into a `Dict`. /// Some(profile) => Ok(profile.collect(fetch(&self.url)?)), /// // Nest: `fetch` into a `Map`. /// None => fetch(&self.url), /// } /// } /// } /// ``` /// /// [`Figment`]: crate::Figment pub trait Provider { /// Returns the [`Metadata`] for this provider, identifying itself and its /// configuration sources. fn metadata(&self) -> Metadata; /// Returns the configuration data. fn data(&self) -> Result, Error>; /// Optionally returns a profile to set on the [`Figment`](crate::Figment) /// this provider is merged into. The profile is only set if `self` is /// _merged_. fn profile(&self) -> Option { None } /// This is used internally! Please, please don't use this externally. If /// you have a good usecase for this, let me know! #[doc(hidden)] fn __metadata_map(&self) -> Option> { None } } /// This is exactly ``. impl Provider for &T { fn metadata(&self) -> Metadata { T::metadata(self) } fn data(&self) -> Result, Error> { T::data(self) } fn profile(&self) -> Option { T::profile(self) } #[doc(hidden)] fn __metadata_map(&self) -> Option> { T::__metadata_map(self) } } /// This is exactly equivalent to [`Serialized::global(K, V)`]. /// /// [`Serialized::global(K, V)`]: crate::providers::Serialized::global() impl, V: serde::Serialize> Provider for (K, V) { fn metadata(&self) -> Metadata { use std::any::type_name; Metadata::named(format!("({}, {})", type_name::(), type_name::())) } fn data(&self) -> Result, Error> { use crate::providers::Serialized; Serialized::global(self.0.as_ref(), &self.1).data() } } figment-0.10.19/src/providers/data.rs000064400000000000000000000452761046102023000155260ustar 00000000000000use std::marker::PhantomData; use std::path::{Path, PathBuf}; use serde::de::{self, DeserializeOwned}; use crate::value::{Map, Dict}; use crate::{Error, Profile, Provider, Metadata}; #[derive(Debug, Clone)] enum Source { File(Option), String(String) } /// A `Provider` that sources values from a file or string in a given /// [`Format`]. /// /// # Constructing /// /// A `Data` provider is typically constructed indirectly via a type that /// implements the [`Format`] trait via the [`Format::file()`] and /// [`Format::string()`] methods which in-turn defer to [`Data::file()`] and /// [`Data::string()`] by default: /// /// ```rust /// // The `Format` trait must be in-scope to use its methods. /// use figment::providers::{Format, Data, Json}; /// /// // These two are equivalent, except the former requires the explicit type. /// let json = Data::::file("foo.json"); /// let json = Json::file("foo.json"); /// ``` /// /// # Provider Details /// /// * **Profile** /// /// This provider does not set a profile. /// /// * **Metadata** /// /// This provider is named `${NAME} file` (when constructed via /// [`Data::file()`]) or `${NAME} source string` (when constructed via /// [`Data::string()`]), where `${NAME}` is [`Format::NAME`]. When /// constructed from a file, the file's path is specified as file /// [`Source`](crate::Source). Path interpolation is unchanged from the /// default. /// /// * **Data (Unnested, _default_)** /// /// When nesting is _not_ specified, the source file or string is read and /// parsed, and the parsed dictionary is emitted into the profile /// configurable via [`Data::profile()`], which defaults to /// [`Profile::Default`]. If the source is a file and the file is not /// present, an empty dictionary is emitted. /// /// * **Data (Nested)** /// /// When nesting is specified, the source value is expected to be a /// dictionary. It's top-level keys are emitted as profiles, and the value /// corresponding to each key as the profile data. #[derive(Debug, Clone)] pub struct Data { source: Source, /// The profile data will be emitted to if nesting is disabled. Defaults to /// [`Profile::Default`]. pub profile: Option, _format: PhantomData, } impl Data { fn new(source: Source, profile: Option) -> Self { Data { source, profile, _format: PhantomData } } /// Returns a `Data` provider that sources its values by parsing the file at /// `path` as format `F`. If `path` is relative, the file is searched for in /// the current working directory and all parent directories until the root, /// and the first hit is used. If you don't want parent directories to be /// searched, use [`Data::file_exact()`] instead. /// /// Nesting is disabled by default. Use [`Data::nested()`] to enable it. /// /// ```rust /// use serde::Deserialize; /// use figment::{Figment, Jail, providers::{Format, Toml}, value::Map}; /// /// #[derive(Debug, PartialEq, Deserialize)] /// struct Config { /// numbers: Vec, /// untyped: Map, /// } /// /// Jail::expect_with(|jail| { /// jail.create_file("Config.toml", r#" /// numbers = [1, 2, 3] /// /// [untyped] /// key = 1 /// other = 4 /// "#)?; /// /// let config: Config = Figment::from(Toml::file("Config.toml")).extract()?; /// assert_eq!(config, Config { /// numbers: vec![1, 2, 3], /// untyped: figment::util::map!["key".into() => 1, "other".into() => 4], /// }); /// /// Ok(()) /// }); /// ``` pub fn file>(path: P) -> Self { fn find(path: &Path) -> Option { if path.is_absolute() { match path.is_file() { true => return Some(path.to_path_buf()), false => return None } } let cwd = std::env::current_dir().ok()?; let mut cwd = cwd.as_path(); loop { let file_path = cwd.join(path); if file_path.is_file() { return Some(file_path); } cwd = cwd.parent()?; } } Data::new(Source::File(find(path.as_ref())), Some(Profile::Default)) } /// Returns a `Data` provider that sources its values by parsing the file at /// `path` as format `F`. If `path` is relative, it is located relative to /// the current working directory. No other directories are searched. /// /// If you want to search parent directories for `path`, use /// [`Data::file()`] instead. /// /// Nesting is disabled by default. Use [`Data::nested()`] to enable it. /// /// ```rust /// use serde::Deserialize; /// use figment::{Figment, Jail, providers::{Format, Toml}}; /// /// #[derive(Debug, PartialEq, Deserialize)] /// struct Config { /// foo: usize, /// } /// /// Jail::expect_with(|jail| { /// // Create 'subdir/config.toml' and set `cwd = subdir`. /// jail.create_file("config.toml", "foo = 123")?; /// jail.change_dir(jail.create_dir("subdir")?)?; /// /// // We are in `subdir`. `config.toml` is in `../`. `file()` finds it. /// let config = Figment::from(Toml::file("config.toml")).extract::()?; /// assert_eq!(config.foo, 123); /// /// // `file_exact()` doesn't search, so it doesn't find it. /// let config = Figment::from(Toml::file_exact("config.toml")).extract::(); /// assert!(config.is_err()); /// Ok(()) /// }); /// ``` pub fn file_exact>(path: P) -> Self { Data::new(Source::File(Some(path.as_ref().to_owned())), Some(Profile::Default)) } /// Returns a `Data` provider that sources its values by parsing the string /// `string` as format `F`. Nesting is not enabled by default; use /// [`Data::nested()`] to enable nesting. /// /// ```rust /// use serde::Deserialize; /// use figment::{Figment, Jail, providers::{Format, Toml}, value::Map}; /// /// #[derive(Debug, PartialEq, Deserialize)] /// struct Config { /// numbers: Vec, /// untyped: Map, /// } /// /// Jail::expect_with(|jail| { /// let source = r#" /// numbers = [1, 2, 3] /// untyped = { key = 1, other = 4 } /// "#; /// /// let config: Config = Figment::from(Toml::string(source)).extract()?; /// assert_eq!(config, Config { /// numbers: vec![1, 2, 3], /// untyped: figment::util::map!["key".into() => 1, "other".into() => 4], /// }); /// /// Ok(()) /// }); /// ``` pub fn string(string: &str) -> Self { Data::new(Source::String(string.into()), Some(Profile::Default)) } /// Enables nesting on `self`, which results in top-level keys of the /// sourced data being treated as profiles. /// /// ```rust /// use serde::Deserialize; /// use figment::{Figment, Jail, providers::{Format, Toml}, value::Map}; /// /// #[derive(Debug, PartialEq, Deserialize)] /// struct Config { /// numbers: Vec, /// untyped: Map, /// } /// /// Jail::expect_with(|jail| { /// jail.create_file("Config.toml", r#" /// [global.untyped] /// global = 0 /// hi = 7 /// /// [staging] /// numbers = [1, 2, 3] /// /// [release] /// numbers = [6, 7, 8] /// "#)?; /// /// // Enable nesting via `nested()`. /// let figment = Figment::from(Toml::file("Config.toml").nested()); /// /// let figment = figment.select("staging"); /// let config: Config = figment.extract()?; /// assert_eq!(config, Config { /// numbers: vec![1, 2, 3], /// untyped: figment::util::map!["global".into() => 0, "hi".into() => 7], /// }); /// /// let config: Config = figment.select("release").extract()?; /// assert_eq!(config, Config { /// numbers: vec![6, 7, 8], /// untyped: figment::util::map!["global".into() => 0, "hi".into() => 7], /// }); /// /// Ok(()) /// }); /// ``` pub fn nested(mut self) -> Self { self.profile = None; self } /// Set the profile to emit data to when nesting is disabled. /// /// ```rust /// use serde::Deserialize; /// use figment::{Figment, Jail, providers::{Format, Toml}, value::Map}; /// /// #[derive(Debug, PartialEq, Deserialize)] /// struct Config { value: u8 } /// /// Jail::expect_with(|jail| { /// let provider = Toml::string("value = 123").profile("debug"); /// let config: Config = Figment::from(provider).select("debug").extract()?; /// assert_eq!(config, Config { value: 123 }); /// /// Ok(()) /// }); /// ``` pub fn profile>(mut self, profile: P) -> Self { self.profile = Some(profile.into()); self } } impl Provider for Data { fn metadata(&self) -> Metadata { use Source::*; match &self.source { String(_) => Metadata::named(format!("{} source string", F::NAME)), File(None) => Metadata::named(format!("{} file", F::NAME)), File(Some(p)) => Metadata::from(format!("{} file", F::NAME), &**p) } } fn data(&self) -> Result, Error> { use Source::*; let map: Result, _> = match (&self.source, &self.profile) { (File(None), _) => return Ok(Map::new()), (File(Some(path)), None) => F::from_path(&path), (String(s), None) => F::from_str(&s), (File(Some(path)), Some(prof)) => F::from_path(&path).map(|v| prof.collect(v)), (String(s), Some(prof)) => F::from_str(&s).map(|v| prof.collect(v)), }; Ok(map.map_err(|e| e.to_string())?) } } /// Trait implementable by text-based [`Data`] format providers. /// /// Instead of implementing [`Provider`] directly, types that refer to data /// formats, such as [`Json`] and [`Toml`], implement this trait. By /// implementing [`Format`], they become [`Provider`]s indirectly via the /// [`Data`] type, which serves as a provider for all `T: Format`. /// /// ```rust /// use figment::providers::Format; /// /// # use serde::de::DeserializeOwned; /// # struct T; /// # impl Format for T { /// # type Error = serde::de::value::Error; /// # const NAME: &'static str = "T"; /// # fn from_str<'de, T: DeserializeOwned>(_: &'de str) -> Result { todo!() } /// # } /// # fn is_provider(_: T) {} /// // If `T` implements `Format`, `T` is a `Provider`. /// // Initialize it with `T::file()` or `T::string()`. /// let provider = T::file("foo.fmt"); /// # is_provider(provider); /// let provider = T::string("some -- format"); /// # is_provider(provider); /// ``` /// /// [`Data`]: Data /// /// # Implementing /// /// There are two primary implementation items: /// /// 1. [`Format::NAME`]: This should be the name of the data format: `"JSON"` /// or `"TOML"`. The string is used in the [metadata for `Data`]. /// /// 2. [`Format::from_str()`]: This is the core string deserialization method. /// A typical implementation will simply call an existing method like /// [`toml::from_str`]. For writing a custom data format, see [serde's /// writing a data format guide]. /// /// The default implementations for [`Format::from_path()`], [`Format::file()`], /// and [`Format::string()`] methods should likely not be overwritten. /// /// [`NAME`]: Format::NAME /// [serde's writing a data format guide]: https://serde.rs/data-format.html pub trait Format: Sized { /// The data format's error type. type Error: de::Error; /// The name of the data format, for instance `"JSON"` or `"TOML"`. const NAME: &'static str; /// Returns a `Data` provider that sources its values by parsing the file at /// `path` as format `Self`. See [`Data::file()`] for more details. The /// default implementation calls `Data::file(path)`. fn file>(path: P) -> Data { Data::file(path) } /// Returns a `Data` provider that sources its values by parsing the file at /// `path` as format `Self`. See [`Data::file_exact()`] for more details. The /// default implementation calls `Data::file_exact(path)`. fn file_exact>(path: P) -> Data { Data::file_exact(path) } /// Returns a `Data` provider that sources its values by parsing `string` as /// format `Self`. See [`Data::string()`] for more details. The default /// implementation calls `Data::string(string)`. fn string(string: &str) -> Data { Data::string(string) } /// Parses `string` as the data format `Self` as a `T` or returns an error /// if the `string` is an invalid `T`. **_Note:_** This method is _not_ /// intended to be called directly. Instead, it is intended to be /// _implemented_ and then used indirectly via the [`Data::file()`] or /// [`Data::string()`] methods. fn from_str<'de, T: DeserializeOwned>(string: &'de str) -> Result; /// Parses the file at `path` as the data format `Self` as a `T` or returns /// an error if the `string` is an invalid `T`. The default implementation /// calls [`Format::from_str()`] with the contents of the file. **_Note:_** /// This method is _not_ intended to be called directly. Instead, it is /// intended to be _implemented on special occasions_ and then used /// indirectly via the [`Data::file()`] or [`Data::string()`] methods. fn from_path(path: &Path) -> Result { let source = std::fs::read_to_string(path).map_err(de::Error::custom)?; Self::from_str(&source) } } #[allow(unused_macros)] macro_rules! impl_format { ($name:ident $NAME:literal/$string:literal: $func:expr, $E:ty, $doc:expr) => ( #[cfg(feature = $string)] #[cfg_attr(nightly, doc(cfg(feature = $string)))] #[doc = $doc] pub struct $name; #[cfg(feature = $string)] impl Format for $name { type Error = $E; const NAME: &'static str = $NAME; fn from_str<'de, T: DeserializeOwned>(s: &'de str) -> Result { $func(s) } } ); ($name:ident $NAME:literal/$string:literal: $func:expr, $E:ty) => ( impl_format!($name $NAME/$string: $func, $E, concat!( "A ", $NAME, " [`Format`] [`Data`] provider.", "\n\n", "Static constructor methods on `", stringify!($name), "` return a [`Data`] value with a generic marker of [`", stringify!($name), "`]. Thus, further use occurs via methods on [`Data`].", "\n```\n", "use figment::providers::{Format, ", stringify!($name), "};", "\n\n// Source directly from a source string...", "\nlet provider = ", stringify!($name), r#"::string("source-string");"#, "\n\n// Or read from a file on disk.", "\nlet provider = ", stringify!($name), r#"::file("path-to-file");"#, "\n\n// Or configured as nested (via Data::nested()):", "\nlet provider = ", stringify!($name), r#"::file("path-to-file").nested();"#, "\n```", "\n\nSee also [`", stringify!($func), "`] for parsing details." )); ) } #[cfg(feature = "yaml")] #[cfg_attr(nightly, doc(cfg(feature = "yaml")))] impl YamlExtended { /// This "YAML Extended" format parser implements the draft ["Merge Key /// Language-Independent Type for YAMLâ„¢ Version /// 1.1"](https://yaml.org/type/merge.html) spec via /// [`serde_yaml::Value::apply_merge()`]. This method is _not_ intended to /// be used directly but rather indirectly by making use of `YamlExtended` /// as a provider. The extension is not part of any officially supported /// YAML release and is deprecated entirely since YAML 1.2. Using /// `YamlExtended` instead of [`Yaml`] enables merge keys, allowing YAML /// like the following to parse with key merges applied: /// /// ```yaml /// tasks: /// build: &webpack_shared /// command: webpack /// args: build /// inputs: /// - 'src/**/*' /// start: /// <<: *webpack_shared /// args: start /// ``` /// /// # Example /// /// ```rust /// use serde::Deserialize; /// use figment::{Figment, Jail, providers::{Format, Yaml, YamlExtended}}; /// /// #[derive(Debug, PartialEq, Deserialize)] /// struct Circle { /// x: usize, /// y: usize, /// r: usize, /// } /// /// #[derive(Debug, PartialEq, Deserialize)] /// struct Config { /// circle1: Circle, /// circle2: Circle, /// circle3: Circle, /// } /// /// Jail::expect_with(|jail| { /// jail.create_file("Config.yaml", r#" /// point: &POINT { x: 1, y: 2 } /// radius: &RADIUS /// r: 10 /// /// circle1: /// <<: *POINT /// r: 3 /// /// circle2: /// <<: [ *POINT, *RADIUS ] /// /// circle3: /// <<: [ *POINT, *RADIUS ] /// y: 14 /// r: 20 /// "#)?; /// /// let config: Config = Figment::from(YamlExtended::file("Config.yaml")).extract()?; /// assert_eq!(config, Config { /// circle1: Circle { x: 1, y: 2, r: 3 }, /// circle2: Circle { x: 1, y: 2, r: 10 }, /// circle3: Circle { x: 1, y: 14, r: 20 }, /// }); /// /// // Note that just `Yaml` would fail, since it doesn't support merge. /// let config = Figment::from(Yaml::file("Config.yaml")); /// assert!(config.extract::().is_err()); /// /// Ok(()) /// }); /// ``` pub fn from_str<'de, T: DeserializeOwned>(s: &'de str) -> serde_yaml::Result { let mut value: serde_yaml::Value = serde_yaml::from_str(s)?; value.apply_merge()?; T::deserialize(value) } } impl_format!(Toml "TOML"/"toml": toml::from_str, toml::de::Error); impl_format!(Yaml "YAML"/"yaml": serde_yaml::from_str, serde_yaml::Error); impl_format!(Json "JSON"/"json": serde_json::from_str, serde_json::error::Error); impl_format!(YamlExtended "YAML Extended"/"yaml": YamlExtended::from_str, serde_yaml::Error); figment-0.10.19/src/providers/env.rs000064400000000000000000000521261046102023000153750ustar 00000000000000use std::fmt; use crate::{Profile, Provider, Metadata}; use crate::coalesce::Coalescible; use crate::value::{Map, Dict}; use crate::error::Error; use crate::util::nest; use uncased::{Uncased, UncasedStr}; crate::util::cloneable_fn_trait!( FilterMap: for<'a> Fn(&'a UncasedStr) -> Option> + 'static ); /// A [`Provider`] that sources its values from environment variables. /// /// All key-lookups and comparisons are case insensitive, facilitated by the /// [`UncasedStr`] and [`Uncased`] types. By default, environment variable names /// are lowercased before being emitted as [key paths] in the provided data, but /// this default can be changed with [`Env::lowercase()`]. Environment variable /// values can contain structured data, parsed as a [`Value`], with syntax /// resembling TOML: /// /// * [`Bool`]: `true`, `false` (e.g, `APP_VAR=true`) /// * [`Num::F64`]: any float containing `.`: (e.g, `APP_VAR=1.2`, `APP_VAR=-0.002`) /// * [`Num::USize`]: any unsigned integer (e.g, `APP_VAR=10`) /// * [`Num::Isize`]: any negative integer (e.g, `APP_VAR=-10`) /// * [`Array`]: delimited by `[]` (e.g, `APP_VAR=[true, 1.0, -1]`) /// * [`Dict`]: in the form `{key=value}` (e.g, `APP_VAR={key="value",num=10}`) /// * [`String`]: delimited by `"` (e.g, `APP_VAR=\"hi\"`) /// * [`String`]: anything else (e.g, `APP_VAR=hi`, `APP_VAR=[hi`) /// /// Additionally, keys and strings delimited with `"` can contain the following /// escaped characters: /// /// ```text /// \b - backspace (U+0008) /// \t - tab (U+0009) /// \n - linefeed (U+000A) /// \f - form feed (U+000C) /// \r - carriage return (U+000D) /// \" - quote (U+0022) /// \\ - backslash (U+005C) /// \uXXXX - unicode (U+XXXX) /// \UXXXXXXXX - unicode (U+XXXXXXXX) /// ``` /// /// For example: /// /// ```sh /// APP_VAR=\"hello\\nthere\" => (what in Rust is) "hello\nthere" /// APP_VAR=\"hi\\u1234there\" => (what in Rust is) "hi\u{1234}there" /// APP_VAR=\"abc\\td\\n\" => (what in Rust is) "abc\td\n" /// /// APP_VAR={\"key\\nkey\"=123}`) /// APP_VAR={\"key.key\"=123}`) /// ``` /// /// Undelimited strings, or strings with invalid escape sequences, are /// interpreted exactly as written without any escaping. /// /// [key paths]: crate::Figment#extraction /// [`Value`]: crate::value::Value /// [`Bool`]: crate::value::Value::Bool /// [`Num::F64`]: crate::value::Num::F64 /// [`Num::USize`]: crate::value::Num::USize /// [`Num::ISize`]: crate::value::Num::ISize /// [`Array`]: crate::value::Value::Array /// [`String`]: crate::value::Value::String /// /// # Key Paths (nesting) /// /// Because environment variables names are emitted as [key paths] in the /// provided data, a nested dictionary is created for every component of the /// name delimited by `.`, each a parent of the next, with the leaf mapping to /// environment variable `Value`. For example, the environment variable /// `a.b.c=3` creates the mapping `a -> b -> c -> 3` in the emitted data. /// /// Environment variable names cannot typically contain the `.` character, but /// another character can be used in its place by replacing that character in /// the name with `.` with [`Env::map()`]. The [`Env::split()`] method is a /// convenience method that does exactly this. /// /// # Provider Details /// /// * **Profile** /// /// This provider does not set a profile. /// /// * **Metadata** /// /// This provider is named `environment variable(s)`. It does not specify a /// [`Source`](crate::Source). Interpolation makes path parts uppercase and /// delimited with a `.`. /// /// * **Data** /// /// The data emitted by this provider is single-level dictionary with the /// keys and values returned by [`Env::iter()`], which reads from the /// currently set environment variables and is customizable via the various /// inherent methods. The dictionary is emitted to the profile /// [`profile`](#structfield.profile), configurable via [`Env::profile()`]. #[derive(Clone)] #[cfg_attr(nightly, doc(cfg(feature = "env")))] pub struct Env { filter_map: Box, /// The profile config data will be emitted to. Defaults to /// [`Profile::Default`]. pub profile: Profile, /// We use this to generate better metadata when available. prefix: Option, /// We use this to generate better metadata when available. lowercase: bool, } impl fmt::Debug for Env { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_set().entries(self.iter()).finish() } } impl Env { fn new() -> Self { Env { filter_map: Box::new(|key| Some(key.into())), profile: Profile::Default, prefix: None, lowercase: true, } } fn chain(self, f: F) -> Self where F: for<'a> Fn(Option>) -> Option> { let filter_map = self.filter_map; Env { filter_map: Box::new(move |key| f(filter_map(key))), profile: self.profile, prefix: self.prefix, lowercase: true, } } /// Constructs and `Env` provider that does not filter or map any /// environment variables. /// /// ```rust /// use serde::Deserialize; /// use figment::{Figment, Jail, providers::Env}; /// /// #[derive(Debug, PartialEq, Deserialize)] /// struct Config { /// numbers: Vec, /// app_bar: String, /// } /// /// Jail::expect_with(|jail| { /// jail.set_env("NUMBERS", "[1, 2, 3]"); /// jail.set_env("APP_BAR", "hi"); /// /// let config: Config = Figment::from(Env::raw()).extract()?; /// assert_eq!(config, Config { /// numbers: vec![1, 2, 3], /// app_bar: "hi".into(), /// }); /// /// Ok(()) /// }); /// ``` #[inline(always)] pub fn raw() -> Self { Env::new() } /// Return an `Env` provider that filters environment variables to those /// with the prefix `prefix` and maps to one without the prefix. /// /// ```rust /// use serde::Deserialize; /// use figment::{Figment, Jail, providers::Env}; /// /// #[derive(Debug, PartialEq, Deserialize)] /// struct Config { /// foo: usize, /// bar: String, /// } /// /// Jail::expect_with(|jail| { /// jail.set_env("APP_FOO", 100); /// jail.set_env("APP_BAR", "hi"); /// /// let config: Config = Figment::from(Env::prefixed("APP_")).extract()?; /// assert_eq!(config, Config { foo: 100, bar: "hi".into() }); /// /// Ok(()) /// }); /// ``` pub fn prefixed(prefix: &str) -> Self { let owned_prefix = prefix.to_string(); let mut env = Env::new() .filter_map(move |key| match key.starts_with(&owned_prefix) { true => Some(key[owned_prefix.len()..].into()), false => None }); env.prefix = Some(prefix.into()); env } /// Applys an additional filter to the keys of environment variables being /// considered. /// /// ```rust /// use figment::{Jail, providers::Env}; /// /// Jail::expect_with(|jail| { /// jail.set_env("FOO_FOO", 100); /// jail.set_env("BAR_BAR", "hi"); /// jail.set_env("foobar", "hi"); /// /// // We'll be left with `FOO_FOO=100` and `foobar=hi`. /// let env = Env::raw().filter(|k| k.starts_with("foo")); /// assert_eq!(env.iter().count(), 2); /// /// // Filters chain, like iterator adapters. `FOO_FOO=100` remains. /// let env = env.filter(|k| k.as_str().contains('_')); /// assert_eq!(env.iter().count(), 1); /// /// Ok(()) /// }); /// ``` pub fn filter(self, filter: F) -> Self where F: Fn(&UncasedStr) -> bool { self.chain(move |prev| prev.filter(|v| filter(&v))) } /// Applys an additional mapping to the keys of environment variables being /// considered. /// /// ```rust /// use figment::{Jail, providers::Env}; /// /// Jail::expect_with(|jail| { /// jail.set_env("FOO_FOO", 100); /// jail.set_env("BAR_FOO", "hi"); /// jail.set_env("foobar", "hi"); /// /// // This is like `prefixed("foo_")` without the filtering. /// let env = Env::raw().map(|k| match k.starts_with("foo_") { /// true => k["foo_".len()..].into(), /// false => k.into() /// }); /// /// // We now have `FOO=100`, `BAR_FOO=hi`, and `bar=hi`. /// assert_eq!(env.clone().filter(|k| k == "foo").iter().count(), 1); /// /// // Mappings chain, like iterator adapters. /// let env = env.map(|k| match k.starts_with("bar_") { /// true => k["bar_".len()..].into(), /// false => k.into() /// }); /// /// // We now have `FOO=100`, `FOO=hi`, and `bar=hi`. /// assert_eq!(env.filter(|k| k == "foo").iter().count(), 2); /// Ok(()) /// }); /// ``` pub fn map(self, mapper: F) -> Self where F: Fn(&UncasedStr) -> Uncased<'_> { self.chain(move |prev| prev.map(|v| mapper(&v).into_owned())) } /// Simultanously filters and maps the keys of environment variables being /// considered. /// /// The returned `Env` only yields values for which `f` returns `Some`. /// /// ```rust /// use std::collections::HashMap; /// use figment::{Jail, providers::Env}; /// use uncased::AsUncased; /// /// Jail::expect_with(|jail| { /// jail.clear_env(); /// jail.set_env("FOO_FOO", 100); /// jail.set_env("BAR_BAR", "hi"); /// jail.set_env("BAZ_BAZ", "200"); /// /// // We starts with all three variables in `Env::raw(); /// let env = Env::raw(); /// assert_eq!(env.iter().count(), 3); /// /// // This is like `prefixed("foo_")` but with two prefixes. /// let env = env.filter_map(|k| { /// if k.starts_with("foo_") { /// Some(k["foo_".len()..].into()) /// } else if k.starts_with("baz_") { /// Some(k["baz_".len()..].into()) /// } else { /// None /// } /// }); /// /// // Now we have `FOO=100`, `BAZ="200"`. /// let values = env.iter().collect::>(); /// assert_eq!(values.len(), 2); /// assert_eq!(values["foo".as_uncased()], "100"); /// assert_eq!(values["baz".as_uncased()], "200"); /// Ok(()) /// }); /// ``` pub fn filter_map(self, f: F) -> Self where F: Fn(&UncasedStr) -> Option> { self.chain(move |prev| prev.and_then(|v| f(&v).map(|v| v.into_owned()))) } /// Whether to lowercase keys before emitting them. Defaults to `true`. /// /// # Example /// /// ```rust /// use std::collections::HashMap; /// /// use figment::{Jail, Profile, Provider}; /// use figment::providers::Env; /// /// Jail::expect_with(|jail| { /// jail.clear_env(); /// jail.set_env("FOO_BAR_BAZ", 1); /// jail.set_env("FOO_barBaz", 2); /// /// // The default is to lower-case variable name keys. /// let env = Env::prefixed("FOO_"); /// let data = env.data().unwrap(); /// assert!(data[&Profile::Default].contains_key("bar_baz")); /// assert!(data[&Profile::Default].contains_key("barbaz")); /// /// // This can be changed with `lowercase(false)`. You'll need to /// // arrange for deserialization to account for casing. /// let env = Env::prefixed("FOO_").lowercase(false); /// let data = env.data().unwrap(); /// assert!(data[&Profile::Default].contains_key("BAR_BAZ")); /// assert!(data[&Profile::Default].contains_key("barBaz")); /// /// Ok(()) /// }); /// ``` pub fn lowercase(mut self, lowercase: bool) -> Self { self.lowercase = lowercase; self } /// Splits each environment variable key at `pattern`, creating nested /// dictionaries for each split. Specifically, nested dictionaries are /// created for components delimited by `pattern` in the environment /// variable string (3 in `A_B_C` if `pattern` is `_`), each dictionary /// mapping to its parent. /// /// This is equivalent to: `self.map(|key| key.replace(pattern, "."))`. /// /// # Example /// /// ```rust /// use serde::Deserialize; /// use figment::{Figment, Jail, util::map, value::Dict, providers::Env}; /// /// #[derive(Debug, PartialEq, Deserialize)] /// struct Foo { /// key: usize, /// } /// /// #[derive(Debug, PartialEq, Deserialize)] /// struct Config { /// foo: Foo, /// map: Dict, /// } /// /// Jail::expect_with(|jail| { /// // Without splitting: using structured data. /// jail.set_env("APP_FOO", "{key=10}"); /// jail.set_env("APP_MAP", "{one=1,two=2.0}"); /// /// let config: Config = Figment::from(Env::prefixed("APP_")).extract()?; /// assert_eq!(config, Config { /// foo: Foo { key: 10 }, /// map: map!["one".into() => 1u8.into(), "two".into() => 2.0.into()], /// }); /// /// // With splitting. /// jail.set_env("APP_FOO_KEY", 20); /// jail.set_env("APP_MAP_ONE", "1.0"); /// jail.set_env("APP_MAP_TWO", "dos"); /// /// let config: Config = Figment::new() /// .merge(Env::prefixed("APP_").split("_")) /// .extract()?; /// /// assert_eq!(config, Config { /// foo: Foo { key: 20 }, /// map: map!["one".into() => 1.0.into(), "two".into() => "dos".into()], /// }); /// /// Ok(()) /// }); /// ``` pub fn split>(self, pattern: P) -> Self { let pattern = pattern.into(); self.map(move |key| key.as_str().replace(&pattern, ".").into()) } /// Filters out all environment variable keys contained in `keys`. /// /// ```rust /// use figment::{Jail, providers::Env}; /// /// Jail::expect_with(|jail| { /// jail.set_env("FOO_FOO", 1); /// jail.set_env("FOO_BAR", 2); /// jail.set_env("FOO_BAZ", 3); /// jail.set_env("FOO_BAM", 4); /// /// let env = Env::prefixed("FOO_").ignore(&["bar", "baz"]); /// assert_eq!(env.clone().iter().count(), 2); /// /// // Ignores chain. /// let env = env.ignore(&["bam"]); /// assert_eq!(env.iter().count(), 1); /// Ok(()) /// }); /// ``` pub fn ignore(self, keys: &[&str]) -> Self { let keys: Vec = keys.iter().map(|s| s.to_string()).collect(); self.filter(move |key| !keys.iter().any(|k| k.as_str() == key)) } /// Filters out all environment variables keys _not_ contained in `keys`. /// /// ```rust /// use figment::{Jail, providers::Env}; /// /// Jail::expect_with(|jail| { /// jail.set_env("FOO_FOO", 1); /// jail.set_env("FOO_BAR", 2); /// jail.set_env("FOO_BAZ_BOB", 3); /// jail.set_env("FOO_BAM_BOP", 4); /// /// let env = Env::prefixed("FOO_").only(&["bar", "baz_bob", "zoo"]); /// assert_eq!(env.iter().count(), 2); /// /// jail.set_env("FOO_ZOO", 5); /// assert_eq!(env.iter().count(), 3); /// /// let env = Env::prefixed("FOO_").split("_"); /// assert_eq!(env.clone().only(&["bar", "baz.bob"]).iter().count(), 2); /// assert_eq!(env.clone().only(&["bar", "bam_bop"]).iter().count(), 1); /// /// Ok(()) /// }); /// ``` pub fn only(self, keys: &[&str]) -> Self { let keys: Vec = keys.iter().map(|s| s.to_string()).collect(); self.filter(move |key| keys.iter().any(|k| k.as_str() == key)) } /// Returns an iterator over all of the environment variable `(key, value)` /// pairs that will be considered by `self`. The order is not specified. /// /// Keys are lower-cased with leading and trailing whitespace removed. Empty /// keys, or partially empty keys, are not emitted. /// /// Any non-Unicode sequences in values are replaced with `U+FFFD /// REPLACEMENT CHARACTER`. Values are otherwise unmodified. /// /// ```rust /// use figment::{Jail, providers::Env}; /// /// Jail::expect_with(|jail| { /// jail.set_env("FOO_B", 2); /// jail.set_env("FOO_A", 1); /// jail.set_env("FOO_C", 3); /// /// let env = Env::prefixed("FOO_"); /// let mut pairs: Vec<_> = env.iter().collect(); /// pairs.sort_by(|(k1, _), (k2, _)| k1.cmp(k2)); /// /// assert_eq!(pairs.len(), 3); /// assert_eq!(pairs[0], ("a".into(), "1".into())); /// assert_eq!(pairs[1], ("b".into(), "2".into())); /// assert_eq!(pairs[2], ("c".into(), "3".into())); /// /// jail.set_env("FOO_D", 4); /// let mut pairs: Vec<_> = env.iter().collect(); /// pairs.sort_by(|(k1, _), (k2, _)| k1.cmp(k2)); /// /// assert_eq!(pairs.len(), 4); /// assert_eq!(pairs[3], ("d".into(), "4".into())); /// /// Ok(()) /// }); /// ``` pub fn iter<'a>(&'a self) -> impl Iterator, String)> + 'a { std::env::vars_os() .filter(|(k, _)| !k.is_empty()) .filter_map(move |(k, v)| { let key = k.to_string_lossy(); let key = (self.filter_map)(UncasedStr::new(key.trim()))?; let key = key.as_str().trim(); if key.split('.').any(|s| s.is_empty()) { return None } let key = match self.lowercase { true => key.to_ascii_lowercase(), false => key.to_owned(), }; Some((key.into(), v.to_string_lossy().to_string())) }) } /// Sets the profile config data will be emitted to. /// /// ```rust /// use figment::{Profile, providers::Env}; /// /// let env = Env::raw(); /// assert_eq!(env.profile, Profile::Default); /// /// let env = env.profile("debug"); /// assert_eq!(env.profile, Profile::from("debug")); /// ``` pub fn profile>(mut self, profile: P) -> Self { self.profile = profile.into(); self } /// Sets the profile config data will be emitted to to `global`. /// /// ```rust /// use figment::{Profile, providers::Env}; /// /// let env = Env::raw(); /// assert_eq!(env.profile, Profile::Default); /// /// let env = env.global(); /// assert_eq!(env.profile, Profile::Global); /// ``` pub fn global(mut self) -> Self { self.profile = Profile::Global; self } /// A convenience method to retrieve the value for an environment variable /// with name `name`. Retrieval is case-insensitive. /// /// ```rust /// use figment::{Jail, providers::Env}; /// /// Jail::expect_with(|jail| { /// jail.set_env("TESTING", 123); /// assert_eq!(Env::var("testing"), Some("123".to_string())); /// Ok(()) /// }); /// ``` pub fn var(name: &str) -> Option { for (env_key, val) in std::env::vars_os() { let env_key = env_key.to_string_lossy(); if uncased::eq(env_key.trim(), name) { return Some(val.to_string_lossy().trim().into()); } } None } /// A convenience method to retrieve the value for an environment variable /// with name `name` or a default `default` if one is not set. Retrieval /// is case-insensitive. /// /// ```rust /// use figment::{Jail, providers::Env}; /// /// Jail::expect_with(|jail| { /// jail.set_env("TESTING", 123); /// assert_eq!(Env::var_or("testing", "whoops"), "123"); /// assert_eq!(Env::var_or("hi", "whoops"), "whoops"); /// Ok(()) /// }); /// ``` pub fn var_or>(name: &str, default: S) -> String { Self::var(name).unwrap_or_else(|| default.into()) } } impl Provider for Env { fn metadata(&self) -> Metadata { let mut md = Metadata::named("environment variable(s)") .interpolater(move |_: &Profile, k: &[&str]| { let keys: Vec<_> = k.iter() .map(|k| k.to_ascii_uppercase()) .collect(); keys.join(".") }); if let Some(prefix) = &self.prefix { md.name = format!("`{}` {}", prefix.to_ascii_uppercase(), md.name).into(); } md } fn data(&self) -> Result, Error> { let mut dict = Dict::new(); for (k, v) in self.iter() { let nested_dict = nest(k.as_str(), v.parse().expect("infallible")) .into_dict() .expect("key is non-empty: must have dict"); dict = dict.merge(nested_dict); } Ok(self.profile.collect(dict)) } } figment-0.10.19/src/providers/mod.rs000064400000000000000000000005461046102023000153630ustar 00000000000000//! Built-in [`Provider`](crate::Provider) implementations for common sources. //! //! The [top-level docs](crate#built-in-providers) contain a list and //! description of each provider. mod serialized; mod data; #[cfg(feature = "env")] mod env; #[cfg(feature = "env")] pub use self::env::Env; pub use self::serialized::Serialized; pub use self::data::*; figment-0.10.19/src/providers/serialized.rs000064400000000000000000000153131046102023000167350ustar 00000000000000use std::panic::Location; use serde::Serialize; use crate::{Profile, Provider, Metadata}; use crate::error::{Error, Kind::InvalidType}; use crate::value::{Value, Map, Dict}; /// A `Provider` that sources values directly from a serialize type. /// /// # Provider Details /// /// * **Profile** /// /// This provider does not set a profile. /// /// * **Metadata** /// /// This provider is named `T` (via [`std::any::type_name`]). The source /// location is set to the call site of the constructor. /// /// * **Data (Unkeyed)** /// /// When data is not keyed, `T` is expected to serialize to a [`Dict`] and /// is emitted directly as the value for the configured profile. /// /// * **Data (Keyed)** /// /// When keyed ([`Serialized::default()`], [`Serialized::global()`], /// [`Serialized::key()`]), `T` can serialize to any [`Value`] and is /// emitted as the value of the configured `key` key path. Nested /// dictionaries are created for every path component delimited by `.` in /// the `key` string, each dictionary mapping the path component to the /// child, with the leaf mapping to the serialized `T`. For instance, /// `a.b.c` results in `{ a: { b: { c: T }}}`. #[derive(Debug, Clone)] pub struct Serialized { /// The value to be serialized and used as the provided data. pub value: T, /// The key path (`a.b.c`) to emit the value to or the root if `None`. pub key: Option, /// The profile to emit the value to. Defaults to [`Profile::Default`]. pub profile: Profile, loc: &'static Location<'static>, } impl Serialized { /// Constructs an (unkeyed) provider that emits `value`, which must /// serialize to a `dict`, to the `profile`. /// /// ```rust /// use serde::Deserialize; /// use figment::{Figment, Jail, providers::Serialized, util::map}; /// /// #[derive(Debug, PartialEq, Deserialize)] /// struct Config { /// numbers: Vec, /// } /// /// Jail::expect_with(|jail| { /// let map = map!["numbers" => &[1, 2, 3]]; /// /// // This is also `Serialized::defaults(&map)`; /// let figment = Figment::from(Serialized::from(&map, "default")); /// let config: Config = figment.extract()?; /// assert_eq!(config, Config { numbers: vec![1, 2, 3] }); /// /// // This is also `Serialized::defaults(&map).profile("debug")`; /// let figment = Figment::from(Serialized::from(&map, "debug")); /// let config: Config = figment.select("debug").extract()?; /// assert_eq!(config, Config { numbers: vec![1, 2, 3] }); /// /// Ok(()) /// }); /// ``` #[track_caller] pub fn from>(value: T, profile: P) -> Serialized { Serialized { value, key: None, profile: profile.into(), loc: Location::caller() } } /// Emits `value`, which must serialize to a [`Dict`], to the `Default` /// profile. /// /// Equivalent to `Serialized::from(value, Profile::Default)`. /// /// See [`Serialized::from()`]. #[track_caller] pub fn defaults(value: T) -> Serialized { Self::from(value, Profile::Default) } /// Emits `value`, which must serialize to a [`Dict`], to the `Global` /// profile. /// /// Equivalent to `Serialized::from(value, Profile::Global)`. /// /// See [`Serialized::from()`]. #[track_caller] pub fn globals(value: T) -> Serialized { Self::from(value, Profile::Global) } /// Emits a nested dictionary to the `Default` profile keyed by `key` /// key path with the final key mapping to `value`. /// /// See [Data (keyed)](#provider-details) for key path details. /// /// Equivalent to `Serialized::from(value, Profile::Default).key(key)`. /// /// See [`Serialized::from()`] and [`Serialized::key()`]. #[track_caller] pub fn default(key: &str, value: T) -> Serialized { Self::from(value, Profile::Default).key(key) } /// Emits a nested dictionary to the `Global` profile keyed by `key` with /// the final key mapping to `value`. /// /// See [Data (keyed)](#provider-details) for key path details. /// /// Equivalent to `Serialized::from(value, Profile::Global).key(key)`. /// /// See [`Serialized::from()`] and [`Serialized::key()`]. #[track_caller] pub fn global(key: &str, value: T) -> Serialized { Self::from(value, Profile::Global).key(key) } /// Sets the profile to emit the serialized value to. /// /// ```rust /// use figment::{Figment, Jail, providers::Serialized}; /// /// Jail::expect_with(|jail| { /// // This is also `Serialized::defaults(&map)`; /// let figment = Figment::new() /// .join(Serialized::default("key", "hey").profile("debug")) /// .join(Serialized::default("key", "hi")); /// /// let value: String = figment.extract_inner("key")?; /// assert_eq!(value, "hi"); /// /// let value: String = figment.select("debug").extract_inner("key")?; /// assert_eq!(value, "hey"); /// /// Ok(()) /// }); /// ``` pub fn profile>(mut self, profile: P) -> Self { self.profile = profile.into(); self } /// Sets the key path to emit the serialized value to. /// /// See [Data (keyed)](#provider-details) for key path details. /// /// ```rust /// use figment::{Figment, Jail, providers::Serialized}; /// /// Jail::expect_with(|jail| { /// // This is also `Serialized::defaults(&map)`; /// let figment = Figment::new() /// .join(Serialized::default("key", "hey").key("other")) /// .join(Serialized::default("key", "hi")); /// /// let value: String = figment.extract_inner("key")?; /// assert_eq!(value, "hi"); /// /// let value: String = figment.extract_inner("other")?; /// assert_eq!(value, "hey"); /// /// Ok(()) /// }); /// ``` pub fn key(mut self, key: &str) -> Self { self.key = Some(key.into()); self } } impl Provider for Serialized { fn metadata(&self) -> Metadata { Metadata::from(std::any::type_name::(), self.loc) } fn data(&self) -> Result, Error> { let value = Value::serialize(&self.value)?; let error = InvalidType(value.to_actual(), "map".into()); let dict = match &self.key { Some(key) => crate::util::nest(key, value).into_dict().ok_or(error)?, None => value.into_dict().ok_or(error)?, }; Ok(self.profile.clone().collect(dict)) } } figment-0.10.19/src/util.rs000064400000000000000000000250401046102023000135400ustar 00000000000000//! Useful functions and macros for writing figments. //! //! # `map!` macro //! //! The `map!` macro constructs a [`Map`](crate::value::Map) from key-value //! pairs and is particularly useful during testing: //! //! ```rust //! use figment::util::map; //! //! let map = map! { //! "name" => "Bob", //! "age" => "100" //! }; //! //! assert_eq!(map.get("name"), Some(&"Bob")); //! assert_eq!(map.get("age"), Some(&"100")); //! //! let map = map! { //! 100 => "one hundred", //! 23 => "twenty-three" //! }; //! //! assert_eq!(map.get(&100), Some(&"one hundred")); //! assert_eq!(map.get(&23), Some(&"twenty-three")); //! //! ``` use std::fmt; use std::path::{Path, PathBuf, Component}; use serde::de::{self, Unexpected, Deserializer}; /// A helper function to determine the relative path to `path` from `base`. /// /// Returns `None` if there is no relative path from `base` to `path`, that is, /// `base` and `path` do not share a common ancestor. `path` and `base` must be /// either both absolute or both relative; returns `None` if one is relative and /// the other absolute. /// /// ``` /// use std::path::Path; /// use figment::util::diff_paths; /// /// // Paths must be both relative or both absolute. /// assert_eq!(diff_paths("/a/b/c", "b/c"), None); /// assert_eq!(diff_paths("a/b/c", "/b/c"), None); /// /// // The root/relative root is always a common ancestor. /// assert_eq!(diff_paths("/a/b/c", "/b/c"), Some("../../a/b/c".into())); /// assert_eq!(diff_paths("c/a", "b/c/a"), Some("../../../c/a".into())); /// /// let bar = "/foo/bar"; /// let baz = "/foo/bar/baz"; /// let quux = "/foo/bar/quux"; /// /// assert_eq!(diff_paths(bar, baz), Some("../".into())); /// assert_eq!(diff_paths(baz, bar), Some("baz".into())); /// assert_eq!(diff_paths(quux, baz), Some("../quux".into())); /// assert_eq!(diff_paths(baz, quux), Some("../baz".into())); /// assert_eq!(diff_paths(bar, quux), Some("../".into())); /// assert_eq!(diff_paths(baz, bar), Some("baz".into())); /// ``` // Copyright 2012-2015 The Rust Project Developers. // Copyright 2017 The Rust Project Developers. // Adapted from `pathdiff`, which itself adapted from rustc's path_relative_from. pub fn diff_paths(path: P, base: B) -> Option where P: AsRef, B: AsRef { let (path, base) = (path.as_ref(), base.as_ref()); if path.has_root() != base.has_root() { return None; } let mut ita = path.components(); let mut itb = base.components(); let mut comps: Vec = vec![]; loop { match (ita.next(), itb.next()) { (None, None) => break, (Some(a), None) => { comps.push(a); comps.extend(ita.by_ref()); break; } (None, _) => comps.push(Component::ParentDir), (Some(a), Some(b)) if comps.is_empty() && a == b => (), (Some(a), Some(b)) if b == Component::CurDir => comps.push(a), (Some(_), Some(b)) if b == Component::ParentDir => return None, (Some(a), Some(_)) => { comps.push(Component::ParentDir); for _ in itb { comps.push(Component::ParentDir); } comps.push(a); comps.extend(ita.by_ref()); break; } } } Some(comps.iter().map(|c| c.as_os_str()).collect()) } /// A helper to deserialize `0/false` as `false` and `1/true` as `true`. /// /// Serde's default deserializer for `bool` only parses the strings `"true"` and /// `"false"` as the booleans `true` and `false`, respectively. By contract, /// this function _case-insensitively_ parses both the strings `"true"/"false"` /// and the integers `1/0` as the booleans `true/false`, respectively. /// /// # Example /// /// ```rust /// use figment::Figment; /// /// #[derive(serde::Deserialize)] /// struct Config { /// #[serde(deserialize_with = "figment::util::bool_from_str_or_int")] /// cli_colors: bool, /// } /// /// let c0: Config = Figment::from(("cli_colors", "true")).extract().unwrap(); /// let c1: Config = Figment::from(("cli_colors", "TRUE")).extract().unwrap(); /// let c2: Config = Figment::from(("cli_colors", 1)).extract().unwrap(); /// assert_eq!(c0.cli_colors, true); /// assert_eq!(c1.cli_colors, true); /// assert_eq!(c2.cli_colors, true); /// /// let c0: Config = Figment::from(("cli_colors", "false")).extract().unwrap(); /// let c1: Config = Figment::from(("cli_colors", "fAlSe")).extract().unwrap(); /// let c2: Config = Figment::from(("cli_colors", 0)).extract().unwrap(); /// assert_eq!(c0.cli_colors, false); /// assert_eq!(c1.cli_colors, false); /// assert_eq!(c2.cli_colors, false); /// ``` pub fn bool_from_str_or_int<'de, D: Deserializer<'de>>(de: D) -> Result { struct Visitor; impl<'de> de::Visitor<'de> for Visitor { type Value = bool; fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("a boolean") } fn visit_str(self, val: &str) -> Result { match val { v if uncased::eq(v, "true") => Ok(true), v if uncased::eq(v, "false") => Ok(false), s => Err(E::invalid_value(Unexpected::Str(s), &"true or false")) } } fn visit_u64(self, n: u64) -> Result { match n { 0 | 1 => Ok(n != 0), n => Err(E::invalid_value(Unexpected::Unsigned(n), &"0 or 1")) } } fn visit_i64(self, n: i64) -> Result { match n { 0 | 1 => Ok(n != 0), n => Err(E::invalid_value(Unexpected::Signed(n), &"0 or 1")) } } fn visit_bool(self, b: bool) -> Result { Ok(b) } } de.deserialize_any(Visitor) } /// A helper to serialize and deserialize a map as a vector of `(key, value)` /// pairs. /// /// ``` /// use figment::{Figment, util::map}; /// use serde::{Serialize, Deserialize}; /// /// #[derive(Debug, Clone, Serialize, Deserialize)] /// pub struct Config { /// #[serde(with = "figment::util::vec_tuple_map")] /// pairs: Vec<(String, usize)> /// } /// /// let map = map!["key" => 1, "value" => 100, "name" => 20]; /// let c: Config = Figment::from(("pairs", map)).extract().unwrap(); /// assert_eq!(c.pairs.len(), 3); /// /// let mut pairs = c.pairs; /// pairs.sort_by_key(|(_, v)| *v); /// /// assert_eq!(pairs[0], ("key".into(), 1)); /// assert_eq!(pairs[1], ("name".into(), 20)); /// assert_eq!(pairs[2], ("value".into(), 100)); /// ``` pub mod vec_tuple_map { use std::fmt; use serde::{de, Deserialize, Serialize, Deserializer, Serializer}; /// The serializer half. pub fn serialize(vec: &[(K, V)], se: S) -> Result where S: Serializer, K: Serialize, V: Serialize { se.collect_map(vec.iter().map(|(ref k, ref v)| (k, v))) } /// The deserializer half. pub fn deserialize<'de, K, V, D>(de: D) -> Result, D::Error> where D: Deserializer<'de>, K: Deserialize<'de>, V: Deserialize<'de> { struct Visitor(std::marker::PhantomData>); impl<'de, K, V> de::Visitor<'de> for Visitor where K: Deserialize<'de>, V: Deserialize<'de>, { type Value = Vec<(K, V)>; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("a map") } fn visit_map(self, mut map: A) -> Result, A::Error> where A: de::MapAccess<'de> { let mut vec = Vec::with_capacity(map.size_hint().unwrap_or(0)); while let Some((k, v)) = map.next_entry()? { vec.push((k, v)); } Ok(vec) } } de.deserialize_map(Visitor(std::marker::PhantomData)) } } use crate::value::{Value, Dict}; /// Given a key path `key` of the form `a.b.c`, creates nested dictionaries for /// for every path component delimited by `.` in the path string (3 in `a.b.c`), /// each a parent of the next, and the leaf mapping to `value` (`a` -> `b` -> /// `c` -> `value`). /// /// If `key` is empty, simply returns `value`. Otherwise, `Value` will be a /// dictionary with the nested mappings. /// /// # Example /// /// ```rust /// use figment::{util::nest, value::Value}; /// /// let leaf = Value::from("I'm a leaf!"); /// /// let dict = nest("tea", leaf.clone()); /// assert_eq!(dict.find_ref("tea").unwrap(), &leaf); /// /// let dict = nest("tea.leaf", leaf.clone()); /// let tea = dict.find_ref("tea").unwrap(); /// let found_leaf = tea.find_ref("leaf").unwrap(); /// assert_eq!(found_leaf, &leaf); /// assert_eq!(dict.find_ref("tea.leaf").unwrap(), &leaf); /// /// let just_leaf = nest("", leaf.clone()); /// assert_eq!(just_leaf, leaf); /// ``` pub fn nest(key: &str, value: Value) -> Value { fn value_from(mut keys: std::str::Split<'_, char>, value: Value) -> Value { match keys.next() { Some(k) if !k.is_empty() => { let mut dict = Dict::new(); dict.insert(k.into(), value_from(keys, value)); dict.into() } Some(_) | None => value } } value_from(key.split('.'), value) } #[doc(hidden)] #[macro_export] /// This is a macro. macro_rules! map { ($($key:expr => $value:expr),* $(,)?) => ({ let mut map = $crate::value::Map::new(); $(map.insert($key, $value);)* map }); } pub use map; #[doc(hidden)] #[macro_export] macro_rules! make_cloneable { ($Trait:path: $Cloneable:ident) => { trait $Cloneable { fn box_clone(&self) -> Box; } impl std::clone::Clone for Box { fn clone(&self) -> Box { (&**self).box_clone() } } impl std::fmt::Debug for Box { fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result { Ok(()) } } impl $Cloneable for T { fn box_clone(&self) -> Box { Box::new(self.clone()) } } } } #[doc(hidden)] #[macro_export] macro_rules! cloneable_fn_trait { ($Name:ident: $($rest:tt)*) => { trait $Name: $($rest)* + Cloneable + 'static { } impl $Name for F where F: $($rest)* { } $crate::make_cloneable!($Name: Cloneable); } } pub(crate) use cloneable_fn_trait; figment-0.10.19/src/value/de.rs000064400000000000000000000421531046102023000142730ustar 00000000000000use std::fmt; use std::result; use std::cell::Cell; use std::marker::PhantomData; use std::borrow::Cow; use serde::Deserialize; use serde::de::{self, Deserializer, IntoDeserializer, Visitor}; use serde::de::{SeqAccess, MapAccess, VariantAccess}; use crate::Figment; use crate::error::{Error, Kind, Result}; use crate::value::{Value, Num, Empty, Dict, Tag}; pub trait Interpreter { fn interpret_as_bool(v: &Value) -> Cow<'_, Value> { Cow::Borrowed(v) } fn interpret_as_num(v: &Value) -> Cow<'_, Value> { Cow::Borrowed(v) } } pub struct DefaultInterpreter; impl Interpreter for DefaultInterpreter { } pub struct LossyInterpreter; impl Interpreter for LossyInterpreter { fn interpret_as_bool(v: &Value) -> Cow<'_, Value> { v.to_bool_lossy() .map(|b| Cow::Owned(Value::Bool(v.tag(), b))) .unwrap_or(Cow::Borrowed(v)) } fn interpret_as_num(v: &Value) -> Cow<'_, Value> { v.to_num_lossy() .map(|n| Cow::Owned(Value::Num(v.tag(), n))) .unwrap_or(Cow::Borrowed(v)) } } pub struct ConfiguredValueDe<'c, I = DefaultInterpreter> { pub config: &'c Figment, pub value: &'c Value, pub readable: Cell, _phantom: PhantomData } impl<'c, I: Interpreter> ConfiguredValueDe<'c, I> { pub fn from(config: &'c Figment, value: &'c Value) -> Self { Self { config, value, readable: Cell::from(true), _phantom: PhantomData } } } /// Like [`serde::forward_to_deserialize_any`] but applies `$apply` to /// `&self` first, then calls `deserialize_any()` on the returned value, and /// finally maps any error produced using `$errmap`: /// - $apply(&self).deserialize_any(visitor).map_err($errmap) macro_rules! apply_then_forward_to_deserialize_any { ( $( $($f:ident),+ => |$this:pat| $apply:expr, $errmap:expr),* $(,)? ) => { $( $( fn $f>(self, visitor: V) -> Result { let $this = &self; $apply.deserialize_any(visitor).map_err($errmap) } )+ )* } } impl<'de: 'c, 'c, I: Interpreter> Deserializer<'de> for ConfiguredValueDe<'c, I> { type Error = Error; fn deserialize_any(self, v: V) -> Result where V: de::Visitor<'de> { let maker = |v| Self::from(self.config, v); let result = match *self.value { Value::String(_, ref s) => v.visit_str(s), Value::Char(_, c) => v.visit_char(c), Value::Bool(_, b) => v.visit_bool(b), Value::Num(_, n) => n.deserialize_any(v), Value::Empty(_, e) => e.deserialize_any(v), Value::Dict(_, ref map) => v.visit_map(MapDe::new(map, maker)), Value::Array(_, ref seq) => v.visit_seq(SeqDe::new(seq, maker)), }; result.map_err(|e| e.retagged(self.value.tag()).resolved(self.config)) } fn deserialize_option(self, visitor: V) -> Result where V: Visitor<'de> { let (config, tag) = (self.config, self.value.tag()); let result = match self.value { Value::Empty(_, val) => val.deserialize_any(visitor), _ => visitor.visit_some(self) }; result.map_err(|e| e.retagged(tag).resolved(&config)) } fn deserialize_struct>( self, name: &'static str, _fields: &'static [&'static str], visitor: V ) -> Result { use crate::value::magic::*; let (config, tag) = (self.config, self.value.tag()); let result = match name { Value::NAME => Value::deserialize_from(self, visitor), RelativePathBuf::NAME => RelativePathBuf::deserialize_from(self, visitor), Tagged::<()>::NAME => Tagged::<()>::deserialize_from(self, visitor), // SelectedProfile::NAME => SelectedProfile::deserialize_from(self, visitor), _ => self.deserialize_any(visitor) }; result.map_err(|e| e.retagged(tag).resolved(config)) } fn deserialize_enum>( self, _: &'static str, _: &'static [&'static str], v: V, ) -> Result { use serde::de::value::MapAccessDeserializer; let (config, tag) = (self.config, self.value.tag()); let result = match self.value { Value::String(_, s) => v.visit_enum((&**s).into_deserializer()), Value::Dict(_, ref map) => { let maker = |v| Self::from(self.config, v); let map_access = MapDe::new(map, maker); v.visit_enum(MapAccessDeserializer::new(map_access)) } Value::Num(_, n) if n.to_u32().is_some() => { let tag = n.to_u32().unwrap(); v.visit_enum(tag.into_deserializer()) } _ => self.deserialize_any(v) }; result.map_err(|e| e.retagged(tag).resolved(&config)) } fn deserialize_newtype_struct>( self, _name: &'static str, visitor: V, ) -> Result { visitor.visit_newtype_struct(self) } fn is_human_readable(&self) -> bool { let val = self.readable.get(); self.readable.set(!val); val } apply_then_forward_to_deserialize_any! { deserialize_bool => |de| I::interpret_as_bool(de.value), |e| e.retagged(de.value.tag()).resolved(de.config), deserialize_u8, deserialize_u16, deserialize_u32, deserialize_u64, deserialize_i8, deserialize_i16, deserialize_i32, deserialize_i64, deserialize_f32, deserialize_f64 => |de| I::interpret_as_num(de.value), |e| e.retagged(de.value.tag()).resolved(de.config), } serde::forward_to_deserialize_any! { char str string seq bytes byte_buf map unit ignored_any unit_struct tuple_struct tuple identifier } } use std::collections::btree_map::Iter; pub struct MapDe<'m, D, F: Fn(&'m Value) -> D> { iter: Iter<'m, String, Value>, pair: Option<(&'m String, &'m Value)>, make_deserializer: F, } impl<'m, D, F: Fn(&'m Value) -> D> MapDe<'m, D, F> { pub fn new(map: &'m Dict, maker: F) -> Self { MapDe { iter: map.iter(), pair: None, make_deserializer: maker } } } impl<'m, 'de, D, F> de::MapAccess<'de> for MapDe<'m, D, F> where D: Deserializer<'de, Error = Error>, F: Fn(&'m Value) -> D, { type Error = Error; fn next_key_seed(&mut self, seed: K) -> Result> where K: de::DeserializeSeed<'de> { if let Some((k, v)) = self.iter.next() { let result = seed.deserialize(k.as_str().into_deserializer()) .map_err(|e: Error| e.prefixed(k).retagged(v.tag())) .map(Some); self.pair = Some((k, v)); result } else { Ok(None) } } fn next_value_seed(&mut self, seed: V) -> Result where V: de::DeserializeSeed<'de> { let (key, value) = self.pair.take().expect("visit_value called before visit_key"); let tag = value.tag(); seed.deserialize((self.make_deserializer)(value)) .map_err(|e: Error| e.prefixed(key).retagged(tag)) } } pub struct SeqDe<'v, D, F: Fn(&'v Value) -> D> { iter: std::iter::Enumerate>, len: usize, make_deserializer: F, } impl<'v, D, F: Fn(&'v Value) -> D> SeqDe<'v, D, F> { pub fn new(seq: &'v [Value], maker: F) -> Self { SeqDe { len: seq.len(), iter: seq.iter().enumerate(), make_deserializer: maker } } } impl<'v, 'de, D, F> de::SeqAccess<'de> for SeqDe<'v, D, F> where D: Deserializer<'de, Error = Error>, F: Fn(&'v Value) -> D, { type Error = Error; fn next_element_seed(&mut self, seed: T) -> Result> where T: de::DeserializeSeed<'de> { if let Some((i, item)) = self.iter.next() { // item.map_tag(|metadata| metadata.path.push(self.count.to_string())); self.len -= 1; seed.deserialize((self.make_deserializer)(item)) .map_err(|e: Error| e.prefixed(&i.to_string())) .map(Some) } else { Ok(None) } } fn size_hint(&self) -> Option { Some(self.len) } } impl<'de> Deserializer<'de> for &Value { type Error = Error; fn deserialize_any(self, v: V) -> Result where V: de::Visitor<'de> { use Value::*; let result = match *self { String(_, ref s) => v.visit_str(s), Char(_, c) => v.visit_char(c), Bool(_, b) => v.visit_bool(b), Num(_, n) => n.deserialize_any(v), Empty(_, e) => e.deserialize_any(v), Dict(_, ref map) => v.visit_map(MapDe::new(map, |v| v)), Array(_, ref seq) => v.visit_seq(SeqDe::new(seq, |v| v)), }; result.map_err(|e: Error| e.retagged(self.tag())) } fn deserialize_option(self, visitor: V) -> Result where V: Visitor<'de> { if let Value::Empty(t, val) = self { return val.deserialize_any(visitor).map_err(|e: Error| e.retagged(*t)); } visitor.visit_some(self) } fn deserialize_enum>( self, _: &'static str, _: &'static [&'static str], v: V, ) -> Result { use serde::de::value::MapAccessDeserializer; let result = match self { Value::String(_, s) => v.visit_enum((&**s).into_deserializer()), Value::Dict(_, ref map) => { let map_access = MapDe::new(map, |v| v); v.visit_enum(MapAccessDeserializer::new(map_access)) } Value::Num(_, n) if n.to_u32().is_some() => { let tag = n.to_u32().unwrap(); v.visit_enum(tag.into_deserializer()) } _ => self.deserialize_any(v) }; result.map_err(|e: Error| e.retagged(self.tag())) } fn deserialize_newtype_struct>( self, _name: &'static str, visitor: V, ) -> Result { visitor.visit_newtype_struct(self) } serde::forward_to_deserialize_any! { bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq bytes byte_buf map unit struct ignored_any unit_struct tuple_struct tuple identifier } } macro_rules! int_try { ($n:expr; $o:ty => $t:ty => $($r:tt)*) => ( if ($n as $t as $o) == $n { return $($r)*($n as $t); } ) } impl<'de> Deserializer<'de> for Num { type Error = Error; fn deserialize_any(self, visitor: V) -> Result where V: de::Visitor<'de> { match self { Num::U8(n) => visitor.visit_u8(n), Num::U16(n) => visitor.visit_u16(n), Num::U32(n) => visitor.visit_u32(n), Num::U64(n) => visitor.visit_u64(n), Num::U128(n) => visitor.visit_u128(n), Num::I8(n) => visitor.visit_i8(n), Num::I16(n) => visitor.visit_i16(n), Num::I32(n) => visitor.visit_i32(n), Num::I64(n) => visitor.visit_i64(n), Num::I128(n) => visitor.visit_i128(n), Num::F32(n) => visitor.visit_f32(n), Num::F64(n) => visitor.visit_f64(n), Num::ISize(n) => { int_try!(n; isize => i8 => visitor.visit_i8); int_try!(n; isize => i16 => visitor.visit_i16); int_try!(n; isize => i32 => visitor.visit_i32); int_try!(n; isize => i64 => visitor.visit_i64); int_try!(n; isize => i128 => visitor.visit_i128); Err(Kind::ISizeOutOfRange(n).into()) } Num::USize(n) => { int_try!(n; usize => u8 => visitor.visit_u8); int_try!(n; usize => u16 => visitor.visit_u16); int_try!(n; usize => u32 => visitor.visit_u32); int_try!(n; usize => u64 => visitor.visit_u64); int_try!(n; usize => u128 => visitor.visit_u128); Err(Kind::USizeOutOfRange(n).into()) } } } serde::forward_to_deserialize_any! { bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq enum bytes byte_buf map struct unit newtype_struct ignored_any unit_struct tuple_struct tuple option identifier } } impl<'de> Deserializer<'de> for Empty { type Error = Error; fn deserialize_any(self, visitor: V) -> Result where V: de::Visitor<'de> { match self { Empty::Unit => visitor.visit_unit(), Empty::None => visitor.visit_none(), } } serde::forward_to_deserialize_any! { bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq enum bytes byte_buf map struct unit newtype_struct ignored_any unit_struct tuple_struct tuple option identifier } } /// Marker trait for "magic" values. Primarily for use with [`Either`]. impl Value { const NAME: &'static str = "___figment_value"; const FIELDS: &'static [&'static str] = &[ "___figment_value_id", "___figment_value_value" ]; fn deserialize_from<'de: 'c, 'c, V: de::Visitor<'de>, I: Interpreter>( de: ConfiguredValueDe<'c, I>, visitor: V ) -> Result { let mut map = Dict::new(); map.insert(Self::FIELDS[0].into(), de.value.tag().into()); map.insert(Self::FIELDS[1].into(), de.value.clone()); visitor.visit_map(MapDe::new(&map, |v| ConfiguredValueDe::<'_, I>::from(de.config, v))) } } #[derive(Debug)] struct RawValue(Value); impl<'de> Deserialize<'de> for RawValue { fn deserialize>(de: D) -> result::Result { de.deserialize_any(ValueVisitor).map(RawValue) } } impl<'de> Deserialize<'de> for Value { fn deserialize>(de: D) -> result::Result { // Total hack to "fingerprint" our deserializer by checking if // human_readable changes, which does for ours but shouldn't for others. let (a, b) = (de.is_human_readable(), de.is_human_readable()); if a != b { de.deserialize_struct(Value::NAME, Value::FIELDS, ValueVisitor) } else { de.deserialize_any(ValueVisitor) } } } pub struct ValueVisitor; macro_rules! visit_fn { ($name:ident: $T:ty => $V:path) => ( #[inline] fn $name(self, v: $T) -> result::Result { Ok(v.into()) } ) } impl<'de> Visitor<'de> for ValueVisitor { type Value = Value; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("any valid figment value") } visit_fn!(visit_bool: bool => Value::Bool); visit_fn!(visit_char: char => Value::Char); visit_fn!(visit_str: &str => Value::String); visit_fn!(visit_string: String => Value::String); visit_fn!(visit_u8: u8 => Num::U8); visit_fn!(visit_u16: u16 => Num::U16); visit_fn!(visit_u32: u32 => Num::U32); visit_fn!(visit_u64: u64 => Num::U64); visit_fn!(visit_u128: u128 => Num::U128); visit_fn!(visit_i8: i8 => Num::I8); visit_fn!(visit_i16: i16 => Num::I16); visit_fn!(visit_i32: i32 => Num::I32); visit_fn!(visit_i64: i64 => Num::I64); visit_fn!(visit_i128: i128 => Num::I128); visit_fn!(visit_f32: f32 => Num::F32); visit_fn!(visit_f64: f64 => Num::F64); fn visit_seq(self, mut seq: A) -> result::Result where A: SeqAccess<'de> { let mut array: Vec = Vec::with_capacity(seq.size_hint().unwrap_or(0)); while let Some(elem) = seq.next_element()? { array.push(elem); } Ok(array.into()) } fn visit_map(self, mut map: A) -> result::Result where A: MapAccess<'de> { let mut dict = Dict::new(); let mut id: Option = None; let mut raw_val: Option = None; while let Some(key) = map.next_key()? { if key == Value::FIELDS[0] { id = Some(map.next_value()?); } else if key == Value::FIELDS[1] { raw_val = Some(map.next_value()?); } else { dict.insert(key, map.next_value()?); } } if let Some(mut value) = raw_val { if let Some(id) = id { value.0.map_tag(|t| *t = id); } return Ok(value.0); } Ok(dict.into()) } fn visit_enum>(self, data: A) -> result::Result { let (tag, variant) = data.variant::()?; Ok(crate::util::nest(&tag, variant.newtype_variant()?)) } fn visit_none(self) -> result::Result { Ok(Empty::None.into()) } fn visit_some(self, deserializer: D) -> result::Result where D: Deserializer<'de>, { deserializer.deserialize_any(self) } fn visit_unit(self) -> result::Result { Ok(Empty::Unit.into()) } } figment-0.10.19/src/value/escape.rs000064400000000000000000000066161046102023000151470ustar 00000000000000// This file is a partial, reduced, extended, and modified reproduction of // `toml-rs`, Copyright (c) 2014 Alex Crichton, The MIT License, reproduced and // relicensed, under the rights granted by The MIT License, under The MIT // License or Apache License, Version 2.0, January 2004, at the user's // discretion, Copyright (c) 2020 Sergio Benitez. // // See README.md, LICENSE-MIT, LICENSE-APACHE. use core::fmt; use std::borrow::Cow; #[derive(Eq, PartialEq, Debug)] pub enum Error { InvalidCharInString(usize, char), InvalidEscape(usize, char), InvalidHexEscape(usize, char), InvalidEscapeValue(usize, u32), UnterminatedString(usize), } pub fn escape(string: &str) -> Result, Error> { let mut chars = string.chars().enumerate(); let mut output = Cow::from(string); while let Some((i, ch)) = chars.next() { match ch { '\\' => { if let Cow::Borrowed(_) = output { output = Cow::Owned(string[..i].into()); } let val = output.to_mut(); match chars.next() { Some((_, '"')) => val.push('"'), Some((_, '\\')) => val.push('\\'), Some((_, 'b')) => val.push('\u{8}'), Some((_, 'f')) => val.push('\u{c}'), Some((_, 'n')) => val.push('\n'), Some((_, 'r')) => val.push('\r'), Some((_, 't')) => val.push('\t'), Some((i, c @ 'u')) | Some((i, c @ 'U')) => { let len = if c == 'u' { 4 } else { 8 }; val.push(hex(&mut chars, i, len)?); } Some((i, c)) => return Err(Error::InvalidEscape(i, c)), None => return Err(Error::UnterminatedString(0)), } }, ch if ch == '\u{09}' || ('\u{20}' <= ch && ch <= '\u{10ffff}' && ch != '\u{7f}') => { // if we haven't allocated, the string contains the value if let Cow::Owned(ref mut val) = output { val.push(ch); } }, _ => return Err(Error::InvalidCharInString(i, ch)), } } Ok(output) } fn hex(mut chars: I, i: usize, len: usize) -> Result where I: Iterator { let mut buf = String::with_capacity(len); for _ in 0..len { match chars.next() { Some((_, ch)) if ch as u32 <= 0x7F && ch.is_digit(16) => buf.push(ch), Some((i, ch)) => return Err(Error::InvalidHexEscape(i, ch)), None => return Err(Error::UnterminatedString(0)), } } let val = u32::from_str_radix(&buf, 16).unwrap(); match std::char::from_u32(val) { Some(ch) => Ok(ch), None => Err(Error::InvalidEscapeValue(i, val)), } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::InvalidCharInString(_, ch) => write!(f, "invalid char `{:?}`", ch), Error::InvalidEscape(_, ch) => write!(f, "invalid escape `\\{:?}`", ch), Error::InvalidHexEscape(_, ch) => write!(f, "invalid hex escape `{:?}`", ch), Error::InvalidEscapeValue(_, ch) => write!(f, "invalid escaped value `{:?}`", ch), Error::UnterminatedString(_) => write!(f, "unterminated"), } } } figment-0.10.19/src/value/magic.rs000064400000000000000000001660401046102023000147650ustar 00000000000000//! (De)serializable values that "magically" use information from the extracing //! [`Figment`](crate::Figment). use std::ops::Deref; use std::path::{PathBuf, Path}; use serde::{Deserialize, Serialize, de}; use crate::{Error, value::{ConfiguredValueDe, Interpreter, MapDe, Tag}}; /// Marker trait for "magic" values. Primarily for use with [`Either`]. pub trait Magic: for<'de> Deserialize<'de> { /// The name of the deserialization pseudo-strucure. #[doc(hidden)] const NAME: &'static str; /// The fields of the pseudo-structure. The last one should be the value. #[doc(hidden)] const FIELDS: &'static [&'static str]; #[doc(hidden)] fn deserialize_from<'de: 'c, 'c, V: de::Visitor<'de>, I: Interpreter>( de: ConfiguredValueDe<'c, I>, visitor: V ) -> Result; } /// A [`PathBuf`] that knows the path of the file it was configured in, if any. /// /// Paths in configuration files are often desired to be relative to the /// configuration file itself. For example, a path of `a/b.html` configured in a /// file `/var/config.toml` might be desired to resolve as `/var/a/b.html`. This /// type makes this possible by simply delcaring the configuration value's type /// as [`RelativePathBuf`]. /// /// # Example /// /// ```rust /// use std::path::Path; /// /// use serde::{Deserialize, Serialize}; /// use figment::{Figment, value::magic::RelativePathBuf, Jail}; /// use figment::providers::{Env, Format, Toml, Serialized}; /// /// #[derive(Debug, PartialEq, Deserialize, Serialize)] /// struct Config { /// path: RelativePathBuf, /// } /// /// Jail::expect_with(|jail| { /// // Note that `jail.directory()` is some non-empty path: /// assert_ne!(jail.directory(), Path::new("/")); /// /// // When a path is declared in a file and deserialized as /// // `RelativePathBuf`, `relative()` will be relative to the file. /// jail.create_file("Config.toml", r#"path = "a/b/c.html""#)?; /// let c: Config = Figment::from(Toml::file("Config.toml")).extract()?; /// assert_eq!(c.path.original(), Path::new("a/b/c.html")); /// assert_eq!(c.path.relative(), jail.directory().join("a/b/c.html")); /// assert_ne!(c.path.relative(), Path::new("a/b/c.html")); /// /// // Round-tripping a `RelativePathBuf` preserves path-relativity. /// let c: Config = Figment::from(Serialized::defaults(&c)).extract()?; /// assert_eq!(c.path.original(), Path::new("a/b/c.html")); /// assert_eq!(c.path.relative(), jail.directory().join("a/b/c.html")); /// assert_ne!(c.path.relative(), Path::new("a/b/c.html")); /// /// // If a path is declared elsewhere, the "relative" path is the original. /// jail.set_env("PATH", "a/b/c.html"); /// let c: Config = Figment::from(Toml::file("Config.toml")) /// .merge(Env::raw().only(&["PATH"])) /// .extract()?; /// /// assert_eq!(c.path.original(), Path::new("a/b/c.html")); /// assert_eq!(c.path.relative(), Path::new("a/b/c.html")); /// /// // Absolute paths remain unchanged. /// jail.create_file("Config.toml", r#"path = "/var/c.html""#); /// let c: Config = Figment::from(Toml::file("Config.toml")).extract()?; /// assert_eq!(c.path.original(), Path::new("/var/c.html")); /// assert_eq!(c.path.relative(), Path::new("/var/c.html")); /// /// // You can use the `From>` impl to set defaults: /// let figment = Figment::from(Serialized::defaults(Config { /// path: "some/default/path".into() /// })); /// /// let default: Config = figment.extract()?; /// assert_eq!(default.path.original(), Path::new("some/default/path")); /// assert_eq!(default.path.relative(), Path::new("some/default/path")); /// /// jail.create_file("Config.toml", r#"path = "an/override""#)?; /// let overriden: Config = figment.merge(Toml::file("Config.toml")).extract()?; /// assert_eq!(overriden.path.original(), Path::new("an/override")); /// assert_eq!(overriden.path.relative(), jail.directory().join("an/override")); /// /// Ok(()) /// }); /// ``` /// /// # Serialization /// /// By default, a `RelativePathBuf` serializes into a structure that can only /// deserialize as a `RelativePathBuf`. In particular, a `RelativePathBuf` does /// not serialize into a value compatible with `PathBuf`. To serialize into a /// `Path`, use [`RelativePathBuf::serialize_original()`] or /// [`RelativePathBuf::serialize_relative()`] together with serde's /// `serialize_with` field attribute: /// /// ```rust /// use std::path::PathBuf; /// /// use serde::{Deserialize, Serialize}; /// use figment::{Figment, value::magic::RelativePathBuf, Jail}; /// use figment::providers::{Format, Toml, Serialized}; /// /// #[derive(Deserialize, Serialize)] /// struct Config { /// relative: RelativePathBuf, /// #[serde(serialize_with = "RelativePathBuf::serialize_original")] /// root: RelativePathBuf, /// #[serde(serialize_with = "RelativePathBuf::serialize_relative")] /// temp: RelativePathBuf, /// } /// /// Jail::expect_with(|jail| { /// jail.create_file("Config.toml", r#" /// relative = "relative/path" /// root = "root/path" /// temp = "temp/path" /// "#)?; /// /// // Create a figment with a serialize `Config`. /// let figment = Figment::from(Toml::file("Config.toml")); /// let config = figment.extract::()?; /// let figment = Figment::from(Serialized::defaults(config)); /// /// // This fails, as expected. /// let relative = figment.extract_inner::("relative"); /// assert!(relative.is_err()); /// /// // These succeed. This one uses the originally written path. /// let root = figment.extract_inner::("root")?; /// assert_eq!(root, PathBuf::from("root/path")); /// /// // This one the magic relative path. /// let temp = figment.extract_inner::("temp")?; /// assert_eq!(temp, jail.directory().join("temp/path")); /// /// Ok(()) /// }) /// ``` #[derive(Debug, Clone)] // #[derive(Deserialize, Serialize)] // #[serde(rename = "___figment_relative_path_buf")] pub struct RelativePathBuf { // #[serde(rename = "___figment_relative_metadata_path")] metadata_path: Option, // #[serde(rename = "___figment_relative_path")] path: PathBuf, } impl PartialEq for RelativePathBuf { fn eq(&self, other: &Self) -> bool { self.relative() == other.relative() } } impl> From

for RelativePathBuf { fn from(path: P) -> RelativePathBuf { Self { metadata_path: None, path: path.as_ref().into() } } } impl Magic for RelativePathBuf { const NAME: &'static str = "___figment_relative_path_buf"; const FIELDS: &'static [&'static str] = &[ "___figment_relative_metadata_path", "___figment_relative_path" ]; fn deserialize_from<'de: 'c, 'c, V: de::Visitor<'de>, I: Interpreter>( de: ConfiguredValueDe<'c, I>, visitor: V ) -> Result { // If we have this struct with a non-empty metadata_path, use it. let config = de.config; if let Some(d) = de.value.as_dict() { if let Some(mpv) = d.get(Self::FIELDS[0]) { if mpv.to_empty().is_none() { let map_de = MapDe::new(d, |v| ConfiguredValueDe::::from(config, v)); return visitor.visit_map(map_de); } } } let metadata_path = config.get_metadata(de.value.tag()) .and_then(|metadata| metadata.source.as_ref() .and_then(|s| s.file_path()) .map(|path| path.display().to_string())); let mut map = crate::value::Map::new(); if let Some(path) = metadata_path { map.insert(Self::FIELDS[0].into(), path.into()); } // If we have this struct with no metadata_path, still use the value. let value = de.value.find_ref(Self::FIELDS[1]).unwrap_or(&de.value); map.insert(Self::FIELDS[1].into(), value.clone()); visitor.visit_map(MapDe::new(&map, |v| ConfiguredValueDe::::from(config, v))) } } impl RelativePathBuf { /// Returns the path as it was declared, without modification. /// /// # Example /// /// ```rust /// use std::path::Path; /// /// use figment::{Figment, value::magic::RelativePathBuf, Jail}; /// use figment::providers::{Format, Toml}; /// /// #[derive(Debug, PartialEq, serde::Deserialize)] /// struct Config { /// path: RelativePathBuf, /// } /// /// Jail::expect_with(|jail| { /// jail.create_file("Config.toml", r#"path = "hello.html""#)?; /// let c: Config = Figment::from(Toml::file("Config.toml")).extract()?; /// assert_eq!(c.path.original(), Path::new("hello.html")); /// /// Ok(()) /// }); /// ``` pub fn original(&self) -> &Path { &self.path } /// Returns this path resolved relative to the file it was declared in, if any. /// /// If the configured path was relative and it was configured from a file, /// this function returns that path prefixed with that file's parent /// directory. Otherwise it returns the original path. Where /// `config_file_path` is the location of the configuration file, this /// corresponds to: /// /// ```rust /// # use figment::{Figment, value::magic::RelativePathBuf, Jail}; /// # use figment::providers::{Format, Toml}; /// # use serde::Deserialize; /// # /// # #[derive(Debug, PartialEq, Deserialize)] /// # struct Config { /// # path: RelativePathBuf, /// # } /// # Jail::expect_with(|jail| { /// # let config_file_path = jail.directory().join("Config.toml"); /// # let config_file = jail.create_file("Config.toml", r#"path = "hello.html""#)?; /// # let config: Config = Figment::from(Toml::file("Config.toml")).extract()?; /// # let relative_path_buf = config.path; /// let relative = config_file_path /// .parent() /// .unwrap() /// .join(relative_path_buf.original()); /// # assert_eq!(relative_path_buf.relative(), relative); /// # Ok(()) /// # }); /// ``` /// /// # Example /// /// ```rust /// use std::path::Path; /// /// use figment::{Figment, value::magic::RelativePathBuf, Jail}; /// use figment::providers::{Env, Format, Toml}; /// /// #[derive(Debug, PartialEq, serde::Deserialize)] /// struct Config { /// path: RelativePathBuf, /// } /// /// Jail::expect_with(|jail| { /// jail.create_file("Config.toml", r#"path = "hello.html""#)?; /// let c: Config = Figment::from(Toml::file("Config.toml")).extract()?; /// assert_eq!(c.path.relative(), jail.directory().join("hello.html")); /// /// jail.set_env("PATH", r#"hello.html"#); /// let c: Config = Figment::from(Env::raw().only(&["PATH"])).extract()?; /// assert_eq!(c.path.relative(), Path::new("hello.html")); /// /// Ok(()) /// }); /// ``` pub fn relative(&self) -> PathBuf { if self.original().has_root() { return self.original().into(); } self.metadata_path() .and_then(|root| match root.is_dir() { true => Some(root), false => root.parent(), }) .map(|root| root.join(self.original())) .unwrap_or_else(|| self.original().into()) } /// Returns the path to the file this path was declared in, if any. /// /// # Example /// /// ```rust /// use std::path::Path; /// /// use figment::{Figment, value::magic::RelativePathBuf, Jail}; /// use figment::providers::{Env, Format, Toml}; /// /// #[derive(Debug, PartialEq, serde::Deserialize)] /// struct Config { /// path: RelativePathBuf, /// } /// /// Jail::expect_with(|jail| { /// jail.create_file("Config.toml", r#"path = "hello.html""#)?; /// let c: Config = Figment::from(Toml::file("Config.toml")).extract()?; /// assert_eq!(c.path.metadata_path().unwrap(), jail.directory().join("Config.toml")); /// /// jail.set_env("PATH", r#"hello.html"#); /// let c: Config = Figment::from(Env::raw().only(&["PATH"])).extract()?; /// assert_eq!(c.path.metadata_path(), None); /// /// Ok(()) /// }); /// ``` pub fn metadata_path(&self) -> Option<&Path> { self.metadata_path.as_ref().map(|p| p.as_ref()) } /// Serialize `self` as the [`original`](Self::original()) path. /// /// See [serialization](Self#serialization) for more. /// /// # Example /// /// ```rust /// use std::path::PathBuf; /// use figment::value::magic::RelativePathBuf; /// use serde::Serialize; /// /// #[derive(Serialize)] /// struct Config { /// #[serde(serialize_with = "RelativePathBuf::serialize_original")] /// path: RelativePathBuf, /// } /// ``` pub fn serialize_original(&self, ser: S) -> Result where S: serde::Serializer { self.original().serialize(ser) } /// Serialize `self` as the [`relative`](Self::relative()) path. /// /// See [serialization](Self#serialization) for more. /// /// # Example /// /// ```rust /// use std::path::PathBuf; /// use figment::value::magic::RelativePathBuf; /// use serde::Serialize; /// /// #[derive(Serialize)] /// struct Config { /// #[serde(serialize_with = "RelativePathBuf::serialize_relative")] /// path: RelativePathBuf, /// } /// ``` // FIXME: Make this the default? We need a breaking change for this. pub fn serialize_relative(&self, ser: S) -> Result where S: serde::Serializer { self.relative().serialize(ser) } } // /// MAGIC // #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] // #[serde(rename = "___figment_selected_profile")] // pub struct SelectedProfile { // profile: crate::Profile, // } // // /// TODO: This doesn't work when it's in a map and the config doesn't contain a // /// value for the corresponding field; we never get to call `deserialize` on the // /// field's value. We can't fabricate this from no value. We either need to fake // /// the field name, somehow, or just not have this. // impl Magic for SelectedProfile { // const NAME: &'static str = "___figment_selected_profile"; // const FIELDS: &'static [&'static str] = &["profile"]; // // fn deserialize_from<'de: 'c, 'c, V: de::Visitor<'de>>( // de: ConfiguredValueDe<'c>, // visitor: V // ) -> Result{ // let mut map = crate::value::Map::new(); // map.insert(Self::FIELDS[0].into(), de.config.profile().to_string().into()); // visitor.visit_map(MapDe::new(&map, |v| ConfiguredValueDe::::from(de.config, v))) // } // } // // impl Deref for SelectedProfile { // type Target = crate::Profile; // // fn deref(&self) -> &Self::Target { // &self.profile // } // } /// (De)serializes as either a magic value `A` or any other deserializable value /// `B`. /// /// An `Either` deserializes as either an `A` or `B`, whichever succeeds /// first. /// /// The usual `Either` implementation or an "untagged" enum does not allow its /// internal values to provide hints to the deserializer. These hints are /// required for magic values to work. By contrast, this `Either` _does_ provide /// the appropriate hints. /// /// # Example /// /// ``` /// use serde::{Serialize, Deserialize}; /// use figment::{Figment, value::magic::{Either, RelativePathBuf, Tagged}}; /// /// #[derive(Debug, PartialEq, Deserialize, Serialize)] /// struct Config { /// int_or_str: Either, String>, /// path_or_bytes: Either>, /// } /// /// fn figment(a: A, b: B) -> Figment { /// Figment::from(("int_or_str", a)).merge(("path_or_bytes", b)) /// } /// /// let config: Config = figment(10, "/a/b").extract().unwrap(); /// assert_eq!(config.int_or_str, Either::Left(10.into())); /// assert_eq!(config.path_or_bytes, Either::Left("/a/b".into())); /// /// let config: Config = figment("hi", "c/d").extract().unwrap(); /// assert_eq!(config.int_or_str, Either::Right("hi".into())); /// assert_eq!(config.path_or_bytes, Either::Left("c/d".into())); /// /// let config: Config = figment(123, &[1, 2, 3]).extract().unwrap(); /// assert_eq!(config.int_or_str, Either::Left(123.into())); /// assert_eq!(config.path_or_bytes, Either::Right(vec![1, 2, 3].into())); /// /// let config: Config = figment("boo!", &[4, 5, 6]).extract().unwrap(); /// assert_eq!(config.int_or_str, Either::Right("boo!".into())); /// assert_eq!(config.path_or_bytes, Either::Right(vec![4, 5, 6].into())); /// /// let config: Config = Figment::from(figment::providers::Serialized::defaults(Config { /// int_or_str: Either::Left(10.into()), /// path_or_bytes: Either::Left("a/b/c".into()), /// })).extract().unwrap(); /// /// assert_eq!(config.int_or_str, Either::Left(10.into())); /// assert_eq!(config.path_or_bytes, Either::Left("a/b/c".into())); /// /// let config: Config = Figment::from(figment::providers::Serialized::defaults(Config { /// int_or_str: Either::Right("hi".into()), /// path_or_bytes: Either::Right(vec![3, 7, 13]), /// })).extract().unwrap(); /// /// assert_eq!(config.int_or_str, Either::Right("hi".into())); /// assert_eq!(config.path_or_bytes, Either::Right(vec![3, 7, 13])); /// ``` #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] // #[serde(untagged)] // #[derive(Serialize)] pub enum Either { /// The "left" variant. Left(A), /// The "right" variant. Right(B), } impl<'de: 'b, 'b, A, B> Deserialize<'de> for Either where A: Magic, B: Deserialize<'b> { fn deserialize(de: D) -> Result where D: de::Deserializer<'de> { use crate::value::ValueVisitor; // FIXME: propogate the error properly let value = de.deserialize_struct(A::NAME, A::FIELDS, ValueVisitor)?; match A::deserialize(&value) { Ok(value) => Ok(Either::Left(value)), Err(a_err) => { let value = value.as_dict() .and_then(|d| d.get(A::FIELDS[A::FIELDS.len() - 1])) .unwrap_or(&value); match B::deserialize(value) { Ok(value) => Ok(Either::Right(value)), Err(b_err) => Err(de::Error::custom(format!("{}; {}", a_err, b_err))) } } } // use crate::error::Kind::*; // result.map_err(|e| match e.kind { // InvalidType(Actual, String) => de::Error::invalid_type() // InvalidValue(Actual, String), // UnknownField(String, &'static [&'static str]), // MissingField(Cow<'static, str>), // DuplicateField(&'static str), // InvalidLength(usize, String), // UnknownVariant(String, &'static [&'static str]), // kind => de::Error::custom(kind.to_string()), // }) } } /// A wrapper around any value of type `T` and its [`Tag`]. /// /// ```rust /// use figment::{Figment, value::magic::Tagged, Jail}; /// use figment::providers::{Format, Toml}; /// /// #[derive(Debug, PartialEq, serde::Deserialize)] /// struct Config { /// number: Tagged, /// } /// /// Jail::expect_with(|jail| { /// jail.create_file("Config.toml", r#"number = 10"#)?; /// let figment = Figment::from(Toml::file("Config.toml")); /// let c: Config = figment.extract()?; /// assert_eq!(*c.number, 10); /// /// let tag = c.number.tag(); /// let metadata = figment.get_metadata(tag).expect("number has tag"); /// /// assert!(!tag.is_default()); /// assert_eq!(metadata.name, "TOML file"); /// Ok(()) /// }); /// ``` #[derive(Debug, Clone)] // #[derive(Deserialize, Serialize)] // #[serde(rename = "___figment_tagged_item")] pub struct Tagged { // #[serde(rename = "___figment_tagged_tag")] tag: Tag, // #[serde(rename = "___figment_tagged_value")] value: T, } impl PartialEq for Tagged { fn eq(&self, other: &Self) -> bool { self.value == other.value } } impl Deserialize<'de>> Magic for Tagged { const NAME: &'static str = "___figment_tagged_item"; const FIELDS: &'static [&'static str] = &[ "___figment_tagged_tag" , "___figment_tagged_value" ]; fn deserialize_from<'de: 'c, 'c, V: de::Visitor<'de>, I: Interpreter>( de: ConfiguredValueDe<'c, I>, visitor: V ) -> Result{ let config = de.config; let mut map = crate::value::Map::new(); // If we have this struct with a non-default tag, use it. if let Some(dict) = de.value.as_dict() { if let Some(tagv) = dict.get(Self::FIELDS[0]) { if let Ok(false) = tagv.deserialize::().map(|t| t.is_default()) { return visitor.visit_map(MapDe::new(dict, |v| { ConfiguredValueDe::::from(config, v) })); } } } // If we have this struct with default tag, use the value. let value = de.value.find_ref(Self::FIELDS[1]).unwrap_or(&de.value); map.insert(Self::FIELDS[0].into(), de.value.tag().into()); map.insert(Self::FIELDS[1].into(), value.clone()); visitor.visit_map(MapDe::new(&map, |v| ConfiguredValueDe::::from(config, v))) } } impl Tagged { /// Returns the tag of the inner value if it is known. As long `self` is a /// leaf and was extracted from a [`Figment`](crate::Figment), the returned /// value is expected to be `Some`. /// /// # Example /// /// ```rust /// use figment::{Figment, Profile, value::magic::Tagged}; /// /// let figment = Figment::from(("key", "value")); /// let tagged = figment.extract_inner::>("key").unwrap(); /// /// assert!(!tagged.tag().is_default()); /// assert_eq!(tagged.tag().profile(), Some(Profile::Global)); /// ``` pub fn tag(&self) -> Tag { self.tag } /// Consumes `self` and returns the inner value. /// /// # Example /// /// ```rust /// use figment::{Figment, value::magic::Tagged}; /// /// let tagged = Figment::from(("key", "value")) /// .extract_inner::>("key") /// .unwrap(); /// /// let value = tagged.into_inner(); /// assert_eq!(value, "value"); /// ``` pub fn into_inner(self) -> T { self.value } } impl Deref for Tagged { type Target = T; fn deref(&self) -> &Self::Target { &self.value } } impl From for Tagged { fn from(value: T) -> Self { Tagged { tag: Tag::Default, value, } } } /// These were generated by serde's derive. We don't want to depend on the /// 'derive' feature, so we simply expand it and copy the impls here. mod _serde { use super::*; #[allow(unused_imports)] pub mod export { // These are re-reexports used by serde's codegen. pub use std::clone::Clone; pub use std::convert::{From, Into}; pub use std::default::Default; pub use std::fmt::{self, Formatter}; pub use std::marker::PhantomData; pub use std::option::Option::{self, None, Some}; pub use std::result::Result::{self, Err, Ok}; pub fn missing_field<'de, V, E>(field: &'static str) -> Result where V: serde::de::Deserialize<'de>, E: serde::de::Error, { struct MissingFieldDeserializer(&'static str, PhantomData); impl<'de, E> serde::de::Deserializer<'de> for MissingFieldDeserializer where E: serde::de::Error { type Error = E; fn deserialize_any(self, _visitor: V) -> Result where V: serde::de::Visitor<'de>, { Err(serde::de::Error::missing_field(self.0)) } fn deserialize_option(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, { visitor.visit_none() } serde::forward_to_deserialize_any! { bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string bytes byte_buf unit unit_struct newtype_struct seq tuple tuple_struct map struct enum identifier ignored_any } } let deserializer = MissingFieldDeserializer(field, PhantomData); serde::de::Deserialize::deserialize(deserializer) } } #[doc(hidden)] #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)] const _: () = { #[allow(rust_2018_idioms, clippy::useless_attribute)] extern crate serde as _serde; #[automatically_derived] impl<'de> _serde::Deserialize<'de> for RelativePathBuf { fn deserialize<__D>(__deserializer: __D) -> export::Result where __D: _serde::Deserializer<'de>, { #[allow(non_camel_case_types)] enum __Field { __field0, __field1, __ignore, } struct __FieldVisitor; impl<'de> _serde::de::Visitor<'de> for __FieldVisitor { type Value = __Field; fn expecting( &self, __formatter: &mut export::Formatter, ) -> export::fmt::Result { export::Formatter::write_str(__formatter, "field identifier") } fn visit_u64<__E>( self, __value: u64, ) -> export::Result where __E: _serde::de::Error, { match __value { 0u64 => export::Ok(__Field::__field0), 1u64 => export::Ok(__Field::__field1), _ => export::Err(_serde::de::Error::invalid_value( _serde::de::Unexpected::Unsigned(__value), &"field index 0 <= i < 2", )), } } fn visit_str<__E>( self, __value: &str, ) -> export::Result where __E: _serde::de::Error, { match __value { "___figment_relative_metadata_path" => { export::Ok(__Field::__field0) } "___figment_relative_path" => export::Ok(__Field::__field1), _ => export::Ok(__Field::__ignore), } } fn visit_bytes<__E>( self, __value: &[u8], ) -> export::Result where __E: _serde::de::Error, { match __value { b"___figment_relative_metadata_path" => { export::Ok(__Field::__field0) } b"___figment_relative_path" => { export::Ok(__Field::__field1) } _ => export::Ok(__Field::__ignore), } } } impl<'de> _serde::Deserialize<'de> for __Field { #[inline] fn deserialize<__D>( __deserializer: __D, ) -> export::Result where __D: _serde::Deserializer<'de>, { _serde::Deserializer::deserialize_identifier( __deserializer, __FieldVisitor, ) } } struct __Visitor<'de> { marker: export::PhantomData, lifetime: export::PhantomData<&'de ()>, } impl<'de> _serde::de::Visitor<'de> for __Visitor<'de> { type Value = RelativePathBuf; fn expecting( &self, __formatter: &mut export::Formatter, ) -> export::fmt::Result { export::Formatter::write_str( __formatter, "struct RelativePathBuf", ) } #[inline] fn visit_seq<__A>( self, mut __seq: __A, ) -> export::Result where __A: _serde::de::SeqAccess<'de>, { let __field0 = match match _serde::de::SeqAccess::next_element::< Option, >(&mut __seq) { export::Ok(__val) => __val, export::Err(__err) => { return export::Err(__err); } } { export::Some(__value) => __value, export::None => { return export::Err(_serde::de::Error::invalid_length( 0usize, &"struct RelativePathBuf with 2 elements", )); } }; let __field1 = match match _serde::de::SeqAccess::next_element::( &mut __seq, ) { export::Ok(__val) => __val, export::Err(__err) => { return export::Err(__err); } } { export::Some(__value) => __value, export::None => { return export::Err(_serde::de::Error::invalid_length( 1usize, &"struct RelativePathBuf with 2 elements", )); } }; export::Ok(RelativePathBuf { metadata_path: __field0, path: __field1, }) } #[inline] fn visit_map<__A>( self, mut __map: __A, ) -> export::Result where __A: _serde::de::MapAccess<'de>, { let mut __field0: export::Option> = export::None; let mut __field1: export::Option = export::None; while let export::Some(__key) = match _serde::de::MapAccess::next_key::<__Field>(&mut __map) { export::Ok(__val) => __val, export::Err(__err) => { return export::Err(__err); } } { match __key { __Field::__field0 => { if export::Option::is_some(&__field0) { return export::Err( <__A::Error as _serde::de::Error>::duplicate_field( "___figment_relative_metadata_path", ), ); } __field0 = export::Some( match _serde::de::MapAccess::next_value::>( &mut __map, ) { export::Ok(__val) => __val, export::Err(__err) => { return export::Err(__err); } }, ); } __Field::__field1 => { if export::Option::is_some(&__field1) { return export::Err( <__A::Error as _serde::de::Error>::duplicate_field( "___figment_relative_path", ), ); } __field1 = export::Some( match _serde::de::MapAccess::next_value::( &mut __map, ) { export::Ok(__val) => __val, export::Err(__err) => { return export::Err(__err); } }, ); } _ => { let _ = match _serde::de::MapAccess::next_value::< _serde::de::IgnoredAny, >( &mut __map ) { export::Ok(__val) => __val, export::Err(__err) => { return export::Err(__err); } }; } } } let __field0 = match __field0 { export::Some(__field0) => __field0, export::None => match export::missing_field( "___figment_relative_metadata_path", ) { export::Ok(__val) => __val, export::Err(__err) => { return export::Err(__err); } }, }; let __field1 = match __field1 { export::Some(__field1) => __field1, export::None => match export::missing_field( "___figment_relative_path", ) { export::Ok(__val) => __val, export::Err(__err) => { return export::Err(__err); } }, }; export::Ok(RelativePathBuf { metadata_path: __field0, path: __field1, }) } } const FIELDS: &[&str] = &[ "___figment_relative_metadata_path", "___figment_relative_path", ]; _serde::Deserializer::deserialize_struct( __deserializer, "___figment_relative_path_buf", FIELDS, __Visitor { marker: export::PhantomData::, lifetime: export::PhantomData, }, ) } } }; #[doc(hidden)] #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)] const _: () = { #[allow(rust_2018_idioms, clippy::useless_attribute)] extern crate serde as _serde; #[automatically_derived] impl _serde::Serialize for RelativePathBuf { fn serialize<__S>( &self, __serializer: __S, ) -> export::Result<__S::Ok, __S::Error> where __S: _serde::Serializer, { let mut __serde_state = match _serde::Serializer::serialize_struct( __serializer, "___figment_relative_path_buf", false as usize + 1 + 1, ) { export::Ok(__val) => __val, export::Err(__err) => { return export::Err(__err); } }; match _serde::ser::SerializeStruct::serialize_field( &mut __serde_state, "___figment_relative_metadata_path", &self.metadata_path, ) { export::Ok(__val) => __val, export::Err(__err) => { return export::Err(__err); } }; match _serde::ser::SerializeStruct::serialize_field( &mut __serde_state, "___figment_relative_path", &self.path, ) { export::Ok(__val) => __val, export::Err(__err) => { return export::Err(__err); } }; _serde::ser::SerializeStruct::end(__serde_state) } } }; #[doc(hidden)] #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)] const _: () = { #[allow(rust_2018_idioms, clippy::useless_attribute)] extern crate serde as _serde; #[automatically_derived] impl _serde::Serialize for Either where A: _serde::Serialize, B: _serde::Serialize, { fn serialize<__S>( &self, __serializer: __S, ) -> export::Result<__S::Ok, __S::Error> where __S: _serde::Serializer, { match *self { Either::Left(ref __field0) => { _serde::Serialize::serialize(__field0, __serializer) } Either::Right(ref __field0) => { _serde::Serialize::serialize(__field0, __serializer) } } } } }; #[doc(hidden)] #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)] const _: () = { #[allow(rust_2018_idioms, clippy::useless_attribute)] extern crate serde as _serde; #[automatically_derived] impl<'de, T> _serde::Deserialize<'de> for Tagged where T: _serde::Deserialize<'de>, { fn deserialize<__D>(__deserializer: __D) -> export::Result where __D: _serde::Deserializer<'de>, { #[allow(non_camel_case_types)] enum __Field { __field0, __field1, __ignore, } struct __FieldVisitor; impl<'de> _serde::de::Visitor<'de> for __FieldVisitor { type Value = __Field; fn expecting( &self, __formatter: &mut export::Formatter, ) -> export::fmt::Result { export::Formatter::write_str(__formatter, "field identifier") } fn visit_u64<__E>( self, __value: u64, ) -> export::Result where __E: _serde::de::Error, { match __value { 0u64 => export::Ok(__Field::__field0), 1u64 => export::Ok(__Field::__field1), _ => export::Err(_serde::de::Error::invalid_value( _serde::de::Unexpected::Unsigned(__value), &"field index 0 <= i < 2", )), } } fn visit_str<__E>( self, __value: &str, ) -> export::Result where __E: _serde::de::Error, { match __value { "___figment_tagged_tag" => export::Ok(__Field::__field0), "___figment_tagged_value" => export::Ok(__Field::__field1), _ => export::Ok(__Field::__ignore), } } fn visit_bytes<__E>( self, __value: &[u8], ) -> export::Result where __E: _serde::de::Error, { match __value { b"___figment_tagged_tag" => export::Ok(__Field::__field0), b"___figment_tagged_value" => export::Ok(__Field::__field1), _ => export::Ok(__Field::__ignore), } } } impl<'de> _serde::Deserialize<'de> for __Field { #[inline] fn deserialize<__D>( __deserializer: __D, ) -> export::Result where __D: _serde::Deserializer<'de>, { _serde::Deserializer::deserialize_identifier( __deserializer, __FieldVisitor, ) } } struct __Visitor<'de, T> where T: _serde::Deserialize<'de>, { marker: export::PhantomData>, lifetime: export::PhantomData<&'de ()>, } impl<'de, T> _serde::de::Visitor<'de> for __Visitor<'de, T> where T: _serde::Deserialize<'de>, { type Value = Tagged; fn expecting( &self, __formatter: &mut export::Formatter, ) -> export::fmt::Result { export::Formatter::write_str(__formatter, "struct Tagged") } #[inline] fn visit_seq<__A>( self, mut __seq: __A, ) -> export::Result where __A: _serde::de::SeqAccess<'de>, { let __field0 = match match _serde::de::SeqAccess::next_element::( &mut __seq, ) { export::Ok(__val) => __val, export::Err(__err) => { return export::Err(__err); } } { export::Some(__value) => __value, export::None => { return export::Err(_serde::de::Error::invalid_length( 0usize, &"struct Tagged with 2 elements", )); } }; let __field1 = match match _serde::de::SeqAccess::next_element::(&mut __seq) { export::Ok(__val) => __val, export::Err(__err) => { return export::Err(__err); } } { export::Some(__value) => __value, export::None => { return export::Err( _serde::de::Error::invalid_length( 1usize, &"struct Tagged with 2 elements", ), ); } }; export::Ok(Tagged { tag: __field0, value: __field1, }) } #[inline] fn visit_map<__A>( self, mut __map: __A, ) -> export::Result where __A: _serde::de::MapAccess<'de>, { let mut __field0: export::Option = export::None; let mut __field1: export::Option = export::None; while let export::Some(__key) = match _serde::de::MapAccess::next_key::<__Field>(&mut __map) { export::Ok(__val) => __val, export::Err(__err) => { return export::Err(__err); } } { match __key { __Field::__field0 => { if export::Option::is_some(&__field0) { return export::Err( <__A::Error as _serde::de::Error>::duplicate_field( "___figment_tagged_tag", ), ); } __field0 = export::Some( match _serde::de::MapAccess::next_value::( &mut __map, ) { export::Ok(__val) => __val, export::Err(__err) => { return export::Err(__err); } }, ); } __Field::__field1 => { if export::Option::is_some(&__field1) { return export::Err( <__A::Error as _serde::de::Error>::duplicate_field( "___figment_tagged_value", ), ); } __field1 = export::Some( match _serde::de::MapAccess::next_value::(&mut __map) { export::Ok(__val) => __val, export::Err(__err) => { return export::Err(__err); } }, ); } _ => { let _ = match _serde::de::MapAccess::next_value::< _serde::de::IgnoredAny, >( &mut __map ) { export::Ok(__val) => __val, export::Err(__err) => { return export::Err(__err); } }; } } } let __field0 = match __field0 { export::Some(__field0) => __field0, export::None => match export::missing_field( "___figment_tagged_tag", ) { export::Ok(__val) => __val, export::Err(__err) => { return export::Err(__err); } }, }; let __field1 = match __field1 { export::Some(__field1) => __field1, export::None => match export::missing_field( "___figment_tagged_value", ) { export::Ok(__val) => __val, export::Err(__err) => { return export::Err(__err); } }, }; export::Ok(Tagged { tag: __field0, value: __field1, }) } } const FIELDS: &[&str] = &["___figment_tagged_tag", "___figment_tagged_value"]; _serde::Deserializer::deserialize_struct( __deserializer, "___figment_tagged_item", FIELDS, __Visitor { marker: export::PhantomData::>, lifetime: export::PhantomData, }, ) } } }; #[doc(hidden)] #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)] const _: () = { #[allow(rust_2018_idioms, clippy::useless_attribute)] extern crate serde as _serde; #[automatically_derived] impl _serde::Serialize for Tagged where T: _serde::Serialize, { fn serialize<__S>( &self, __serializer: __S, ) -> export::Result<__S::Ok, __S::Error> where __S: _serde::Serializer, { let mut __serde_state = match _serde::Serializer::serialize_struct( __serializer, "___figment_tagged_item", false as usize + 1 + 1, ) { export::Ok(__val) => __val, export::Err(__err) => { return export::Err(__err); } }; match _serde::ser::SerializeStruct::serialize_field( &mut __serde_state, "___figment_tagged_tag", &self.tag, ) { export::Ok(__val) => __val, export::Err(__err) => { return export::Err(__err); } }; match _serde::ser::SerializeStruct::serialize_field( &mut __serde_state, "___figment_tagged_value", &self.value, ) { export::Ok(__val) => __val, export::Err(__err) => { return export::Err(__err); } }; _serde::ser::SerializeStruct::end(__serde_state) } } }; } #[cfg(test)] mod tests { use crate::Figment; #[test] fn test_relative_path_buf() { use super::RelativePathBuf; use crate::providers::{Format, Toml}; use std::path::Path; crate::Jail::expect_with(|jail| { jail.set_env("foo", "bar"); jail.create_file("Config.toml", r###" [debug] file_path = "hello.js" another = "whoops/hi/there" absolute = "/tmp/foo" "###)?; let path: RelativePathBuf = Figment::new() .merge(Toml::file("Config.toml").nested()) .select("debug") .extract_inner("file_path")?; assert_eq!(path.original(), Path::new("hello.js")); assert_eq!(path.metadata_path().unwrap(), jail.directory().join("Config.toml")); assert_eq!(path.relative(), jail.directory().join("hello.js")); let path: RelativePathBuf = Figment::new() .merge(Toml::file("Config.toml").nested()) .select("debug") .extract_inner("another")?; assert_eq!(path.original(), Path::new("whoops/hi/there")); assert_eq!(path.metadata_path().unwrap(), jail.directory().join("Config.toml")); assert_eq!(path.relative(), jail.directory().join("whoops/hi/there")); let path: RelativePathBuf = Figment::new() .merge(Toml::file("Config.toml").nested()) .select("debug") .extract_inner("absolute")?; assert_eq!(path.original(), Path::new("/tmp/foo")); assert_eq!(path.metadata_path().unwrap(), jail.directory().join("Config.toml")); assert_eq!(path.relative(), Path::new("/tmp/foo")); jail.create_file("Config.toml", r###" [debug.inner.container] inside = "inside_path/a.html" "###)?; #[derive(serde::Deserialize)] struct Testing { inner: Container, } #[derive(serde::Deserialize)] struct Container { container: Inside, } #[derive(serde::Deserialize)] struct Inside { inside: RelativePathBuf, } let testing: Testing = Figment::new() .merge(Toml::file("Config.toml").nested()) .select("debug") .extract()?; let path = testing.inner.container.inside; assert_eq!(path.original(), Path::new("inside_path/a.html")); assert_eq!(path.metadata_path().unwrap(), jail.directory().join("Config.toml")); assert_eq!(path.relative(), jail.directory().join("inside_path/a.html")); Ok(()) }) } // #[test] // fn test_selected_profile() { // use super::SelectedProfile; // // let profile: SelectedProfile = Figment::new().extract().unwrap(); // assert_eq!(&*profile, crate::Profile::default()); // // let profile: SelectedProfile = Figment::new().select("foo").extract().unwrap(); // assert_eq!(&*profile, "foo"); // // let profile: SelectedProfile = Figment::new().select("bar").extract().unwrap(); // assert_eq!(&*profile, "bar"); // // #[derive(serde::Deserialize)] // struct Testing { // #[serde(alias = "other")] // profile: SelectedProfile, // value: usize // } // // let testing: Testing = Figment::from(("value", 123)) // .merge(("other", "hi")) // .select("with-value").extract().unwrap(); // // assert_eq!(&*testing.profile, "with-value"); // assert_eq!(testing.value, 123); // } // #[test] // fn test_selected_profile_kink() { // use super::SelectedProfile; // // #[derive(serde::Deserialize)] // struct Base { // profile: SelectedProfile, // } // // #[derive(serde::Deserialize)] // struct Testing { // base: Base, // value: usize // } // // let testing: Testing = Figment::from(("value", 123)).extract().unwrap(); // // assert_eq!(&*testing.base.profile, "with-value"); // assert_eq!(testing.value, 123); // } #[test] fn test_tagged() { use super::Tagged; let val = Figment::from(("foo", "hello")) .extract_inner::>("foo") .expect("extraction"); let first_tag = val.tag(); assert_eq!(val.value, "hello"); let val = Figment::from(("bar", "hi")) .extract_inner::>("bar") .expect("extraction"); let second_tag = val.tag(); assert_eq!(val.value, "hi"); assert!(second_tag != first_tag); #[derive(serde::Deserialize)] struct TwoVals { foo: Tagged, bar: Tagged, } let two = Figment::new() .merge(("foo", "hey")) .merge(("bar", 10)) .extract::() .expect("extraction"); let tag3 = two.foo.tag(); assert_eq!(two.foo.value, "hey"); assert!(tag3 != second_tag); let tag4 = two.bar.tag(); assert_eq!(two.bar.value, 10); assert!(tag4 != tag3); let val = Figment::new() .merge(("foo", "hey")) .merge(("bar", 10)) .extract::>() .expect("extraction"); assert!(val.tag().is_default()); let tag5 = val.value.foo.tag(); assert_eq!(val.value.foo.value, "hey"); assert!(tag4 != tag5); let tag6 = val.value.bar.tag(); assert_eq!(val.value.bar.value, 10); assert!(tag6 != tag5) } } figment-0.10.19/src/value/mod.rs000064400000000000000000000005631046102023000144610ustar 00000000000000//! [`Value`] and friends: types representing valid configuration values. //! mod value; mod ser; mod de; mod tag; #[cfg(feature = "parse-value")] mod parse; #[cfg(feature = "parse-value")] mod escape; pub mod magic; pub(crate) use {self::ser::*, self::de::*}; pub use tag::Tag; pub use value::{Value, Map, Num, Dict, Empty}; pub use uncased::{Uncased, UncasedStr}; figment-0.10.19/src/value/parse.rs000064400000000000000000000132431046102023000150130ustar 00000000000000use pear::{parse_error, parsers::*}; use pear::combinators::*; use pear::macros::{parse, parser, switch}; use pear::input::{Pear, Text}; use crate::value::{Value, Dict, escape::escape}; type Input<'a> = Pear>; type Result<'a, T> = pear::input::Result>; #[inline(always)] fn is_whitespace(&byte: &char) -> bool { byte.is_ascii_whitespace() } #[inline(always)] fn is_not_separator(&byte: &char) -> bool { !matches!(byte, ',' | '{' | '}' | '[' | ']') } // TODO: Be more permissive here? #[inline(always)] fn is_ident_char(&byte: &char) -> bool { byte.is_ascii_alphanumeric() || byte == '_' || byte == '-' } #[parser] fn string<'a>(input: &mut Input<'a>) -> Result<'a, String> { let mut is_escaped = false; let str_char = |&c: &char| -> bool { if is_escaped { is_escaped = false; return true; } if c == '\\' { is_escaped = true; return true; } c != '"' }; let inner = (eat('"')?, take_while(str_char)?, eat('"')?).1; match escape(inner) { Ok(string) => string.into_owned(), Err(e) => parse_error!("invalid string: {}", e)?, } } #[parser] fn key<'a>(input: &mut Input<'a>) -> Result<'a, String> { switch! { peek('"') => Ok(string()?), _ => Ok(take_some_while(is_ident_char)?.to_string()) } } #[parser] fn key_value<'a>(input: &mut Input<'a>) -> Result<'a, (String, Value)> { let key = (surrounded(key, is_whitespace)?, eat('=')?).0; (key, surrounded(value, is_whitespace)?) } #[parser] fn array<'a>(input: &mut Input<'a>) -> Result<'a, Vec> { Ok(delimited_collect('[', value, ',', ']')?) } #[parser] fn dict<'a>(input: &mut Input<'a>) -> Result<'a, Dict> { Ok(delimited_collect('{', key_value, ',', '}')?) } #[parser] fn value<'a>(input: &mut Input<'a>) -> Result<'a, Value> { skip_while(is_whitespace)?; let val = switch! { eat_slice("true") => Value::from(true), eat_slice("false") => Value::from(false), peek('{') => Value::from(dict()?), peek('[') => Value::from(array()?), peek('"') => Value::from(string()?), peek('\'') => Value::from((eat('\'')?, eat_any()?, eat('\'')?).1), _ => { let value = take_while(is_not_separator)?.trim(); if value.contains('.') { if let Ok(float) = value.parse::() { return Ok(Value::from(float)); } } if let Ok(int) = value.parse::() { Value::from(int) } else if let Ok(int) = value.parse::() { Value::from(int) } else { Value::from(value.to_string()) } } }; skip_while(is_whitespace)?; val } impl std::str::FromStr for Value { type Err = std::convert::Infallible; fn from_str(s: &str) -> std::result::Result { Ok(parse!(value: Text::from(s)) .unwrap_or_else(|_| Value::from(s.to_string()))) } } #[cfg(test)] mod tests { use super::*; use crate::util::map; macro_rules! assert_parse_eq { ($string:expr => $expected:expr) => ({ let expected = Value::from($expected); let actual: Value = $string.parse().unwrap(); assert_eq!(actual, expected, "got {:?}, expected {:?}", actual, expected); }); ($($string:expr => $expected:expr $(,)?)*) => ( $(assert_parse_eq!($string => $expected);)* ) } #[test] #[allow(clippy::approx_constant)] // false positive: using the PI constant would be wrong here fn check_simple_values_parse() { assert_parse_eq! { "true" => true, "false" => false, "\"false\"" => "false", "\"true\"" => "true", " false" => false, " false " => false, "true " => true, "1" => 1u8, " 0" => 0u8, " -0" => 0i8, " -2" => -2, " 123 " => 123u8, "\"a\"" => "a", "a " => "a", " a " => "a", "\" a\"" => " a", "\"a \"" => "a ", "\" a \"" => " a ", "1.2" => 1.2, " 1.2" => 1.2, "3.14159" => 3.14159, "\"\\t\"" => "\t", "\"abc\\td\"" => "abc\td", "\"abc\\td\\n\"" => "abc\td\n", "\"abc\\td\\n\\n\"" => "abc\td\n\n", "\"abc\\td\"" => "abc\td", "\"\\\"\"" => "\"", "\"\\n\\f\\b\\\\\\r\\\"\"" => "\n\u{c}\u{8}\\\r\"", "\"\\\"hi\\\"\"" => "\"hi\"", "\"hi\\u1234there\"" => "hi\u{1234}there", "\"\\\\\"" => "\\", // unterminated strings pass through as themselves "\"\\\"" => "\"\\\"", } } #[test] fn check_compund_values_parse() { fn v>(v: T) -> Value { v.into() } assert_parse_eq! { "[1,2,3]" => vec![1u8, 2u8, 3u8], "{a=b}" => map!["a" => "b"], "{\"a\"=b}" => map!["a" => "b"], "{\"a.b.c\"=b}" => map!["a.b.c" => "b"], "{a=1,b=3}" => map!["a" => 1u8, "b" => 3u8], "{a=1,b=hi}" => map!["a" => v(1u8), "b" => v("hi")], "[1,[2],3]" => vec![v(1u8), v(vec![2u8]), v(3u8)], "{a=[[-2]]}" => map!["a" => vec![vec![-2]]], "{a=[[-2]],b=\" hi\"}" => map!["a" => v(vec![vec![-2]]), "b" => v(" hi")], "[1,true,hi,\"a \"]" => vec![v(1u8), v(true), v("hi"), v("a ")], "[1,{a=b},hi]" => vec![v(1u8), v(map!["a" => "b"]), v("hi")], "[[ -1], {a=[ b ]}, hi ]" => vec![v(vec![-1]), v(map!["a" => vec!["b"]]), v("hi")], } } } figment-0.10.19/src/value/ser.rs000064400000000000000000000241101046102023000144650ustar 00000000000000use serde::{ser, Serialize, Serializer}; use crate::error::{Error, Kind}; use crate::value::{Value, Dict, Num, Empty}; type Result = std::result::Result; impl Serialize for Value { fn serialize(&self, ser: S) -> std::result::Result { use ser::{SerializeSeq, SerializeMap}; match self { Value::String(_, v) => ser.serialize_str(v), Value::Char(_, v) => ser.serialize_char(*v), Value::Bool(_, v) => ser.serialize_bool(*v), Value::Num(_, v) => v.serialize(ser), Value::Empty(_, v) => v.serialize(ser), Value::Dict(_, v) => { let mut map = ser.serialize_map(Some(v.len()))?; for (key, val) in v { map.serialize_entry(key, val)?; } map.end() } Value::Array(_, v) => { let mut seq = ser.serialize_seq(Some(v.len()))?; for elem in v { seq.serialize_element(elem)?; } seq.end() } } } } impl Serialize for Num { fn serialize(&self, ser: S) -> std::result::Result { match *self { Num::U8(v) => ser.serialize_u8(v), Num::U16(v) => ser.serialize_u16(v), Num::U32(v) => ser.serialize_u32(v), Num::U64(v) => ser.serialize_u64(v), Num::U128(v) => ser.serialize_u128(v), Num::USize(v) => ser.serialize_u64(v as u64), Num::I8(v) => ser.serialize_i8(v), Num::I16(v) => ser.serialize_i16(v), Num::I32(v) => ser.serialize_i32(v), Num::I64(v) => ser.serialize_i64(v), Num::I128(v) => ser.serialize_i128(v), Num::ISize(v) => ser.serialize_i64(v as i64), Num::F32(v) => ser.serialize_f32(v), Num::F64(v) => ser.serialize_f64(v), } } } impl Serialize for Empty { fn serialize(&self, ser: S) -> std::result::Result { match self { Empty::None => ser.serialize_none(), Empty::Unit => ser.serialize_unit(), } } } pub struct ValueSerializer; macro_rules! serialize_fn { ($name:ident: $T:ty => $V:path) => ( fn $name(self, v: $T) -> Result { Ok(v.into()) } ) } pub struct SeqSerializer { tag: Option<&'static str>, sequence: Vec } pub struct MapSerializer { tag: Option<&'static str>, keys: Vec, values: Vec } impl Serializer for ValueSerializer { type Ok = Value; type Error = Error; type SerializeSeq = SeqSerializer; type SerializeTuple = SeqSerializer; type SerializeTupleStruct = SeqSerializer; type SerializeTupleVariant = SeqSerializer; type SerializeMap = MapSerializer; type SerializeStruct = MapSerializer; type SerializeStructVariant = MapSerializer; serialize_fn!(serialize_bool: bool => Value::Bool); serialize_fn!(serialize_char: char => Value::Char); serialize_fn!(serialize_str: &str => Value::String); serialize_fn!(serialize_i8: i8 => Num::I8); serialize_fn!(serialize_i16: i16 => Num::I16); serialize_fn!(serialize_i32: i32 => Num::I32); serialize_fn!(serialize_i64: i64 => Num::I64); serialize_fn!(serialize_i128: i128 => Num::I128); serialize_fn!(serialize_u8: u8 => Num::U8); serialize_fn!(serialize_u16: u16 => Num::U16); serialize_fn!(serialize_u32: u32 => Num::U32); serialize_fn!(serialize_u64: u64 => Num::U64); serialize_fn!(serialize_u128: u128 => Num::U128); serialize_fn!(serialize_f32: f32 => Num::F32); serialize_fn!(serialize_f64: f64 => Num::F64); fn serialize_bytes(self, v: &[u8]) -> Result { use serde::ser::SerializeSeq; let mut seq = self.serialize_seq(Some(v.len()))?; for byte in v { seq.serialize_element(byte)?; } seq.end() } fn serialize_seq(self, len: Option) -> Result { Ok(SeqSerializer::new(None, len)) } fn serialize_map(self, len: Option) -> Result { Ok(MapSerializer::new(None, len)) } fn serialize_struct( self, _name: &'static str, len: usize, ) -> Result { Ok(MapSerializer::new(None, Some(len))) } fn serialize_struct_variant( self, _name: &'static str, _variant_index: u32, variant: &'static str, len: usize, ) -> Result { Ok(MapSerializer::new(Some(variant), Some(len))) } fn serialize_some(self, value: &T) -> Result where T: Serialize { value.serialize(self) } fn serialize_unit_variant( self, _name: &'static str, _variant_index: u32, variant: &'static str, ) -> Result { self.serialize_str(variant) } fn serialize_newtype_struct( self, _name: &'static str, value: &T, ) -> Result where T: Serialize { value.serialize(self) } fn serialize_newtype_variant( self, _name: &'static str, _variant_index: u32, variant: &'static str, value: &T, ) -> Result { Ok(crate::util::map![variant => value.serialize(self)?].into()) } fn serialize_tuple(self, len: usize) -> Result { self.serialize_seq(Some(len)) } fn serialize_tuple_struct( self, _name: &'static str, len: usize, ) -> Result { self.serialize_seq(Some(len)) } fn serialize_tuple_variant( self, _name: &'static str, _variant_index: u32, variant: &'static str, len: usize, ) -> Result { Ok(SeqSerializer::new(Some(variant), Some(len))) } fn serialize_none(self) -> Result { Ok(Empty::None.into()) } fn serialize_unit(self) -> Result { Ok(Empty::Unit.into()) } fn serialize_unit_struct(self, _name: &'static str) -> Result { self.serialize_unit() } } impl SeqSerializer { pub fn new(tag: Option<&'static str>, len: Option) -> Self { Self { tag, sequence: len.map(Vec::with_capacity).unwrap_or_default(), } } } impl<'a> ser::SerializeSeq for SeqSerializer { type Ok = Value; type Error = Error; fn serialize_element(&mut self, value: &T) -> Result<()> where T: Serialize { Ok(self.sequence.push(value.serialize(ValueSerializer)?)) } fn end(self) -> Result { let value: Value = self.sequence.into(); match self.tag { Some(tag) => Ok(crate::util::map![tag => value].into()), None => Ok(value) } } } impl<'a> ser::SerializeTuple for SeqSerializer { type Ok = Value; type Error = Error; fn serialize_element(&mut self, value: &T) -> Result<()> where T: Serialize { ser::SerializeSeq::serialize_element(self, value) } fn end(self) -> Result { ser::SerializeSeq::end(self) } } // Same thing but for tuple structs. impl<'a> ser::SerializeTupleStruct for SeqSerializer { type Ok = Value; type Error = Error; fn serialize_field(&mut self, value: &T) -> Result<()> where T: Serialize { ser::SerializeSeq::serialize_element(self, value) } fn end(self) -> Result { ser::SerializeSeq::end(self) } } impl<'a> ser::SerializeTupleVariant for SeqSerializer { type Ok = Value; type Error = Error; fn serialize_field(&mut self, value: &T) -> Result<()> where T: Serialize { ser::SerializeSeq::serialize_element(self, value) } fn end(self) -> Result { ser::SerializeSeq::end(self) } } impl MapSerializer { pub fn new(tag: Option<&'static str>, len: Option) -> Self { Self { tag, keys: len.map(Vec::with_capacity).unwrap_or_default(), values: len.map(Vec::with_capacity).unwrap_or_default(), } } } impl<'a> ser::SerializeMap for MapSerializer { type Ok = Value; type Error = Error; fn serialize_key(&mut self, key: &T) -> Result<()> where T: Serialize { match key.serialize(ValueSerializer)? { Value::String(_, s) => self.keys.push(s), v => return Err(Kind::UnsupportedKey(v.to_actual(), "string".into()).into()), }; Ok(()) } fn serialize_value(&mut self, value: &T) -> Result<()> where T: Serialize { self.values.push(value.serialize(ValueSerializer)?); Ok(()) } fn end(self) -> Result { let iter = self.keys.into_iter().zip(self.values.into_iter()); let value: Value = iter.collect::().into(); match self.tag { Some(tag) => Ok(crate::util::map![tag => value].into()), None => Ok(value) } } } impl<'a> ser::SerializeStruct for MapSerializer { type Ok = Value; type Error = Error; fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> where T: Serialize { ser::SerializeMap::serialize_key(self, key)?; ser::SerializeMap::serialize_value(self, value) } fn end(self) -> Result { ser::SerializeMap::end(self) } } impl<'a> ser::SerializeStructVariant for MapSerializer { type Ok = Value; type Error = Error; fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> where T: Serialize { ser::SerializeMap::serialize_key(self, key)?; ser::SerializeMap::serialize_value(self, value) } fn end(self) -> Result { ser::SerializeMap::end(self) } } figment-0.10.19/src/value/tag.rs000064400000000000000000000116131046102023000144530ustar 00000000000000use std::fmt; use std::sync::atomic::Ordering; use serde::{de, ser}; use crate::profile::{Profile, ProfileTag}; /// An opaque, unique tag identifying a value's [`Metadata`](crate::Metadata) /// and profile. /// /// A `Tag` is retrieved either via [`Tagged`] or [`Value::tag()`]. The /// corresponding metadata can be retrieved via [`Figment::get_metadata()`] and /// the profile vile [`Tag::profile()`]. /// /// [`Tagged`]: crate::value::magic::Tagged /// [`Value::tag()`]: crate::value::Value::tag() /// [`Figment::get_metadata()`]: crate::Figment::get_metadata() #[derive(Copy, Clone)] pub struct Tag(u64); #[cfg(any(target_pointer_width = "8", target_pointer_width = "16", target_pointer_width = "32"))] static COUNTER: atomic::Atomic = atomic::Atomic::new(1); #[cfg(not(any(target_pointer_width = "8", target_pointer_width = "16", target_pointer_width = "32")))] static COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1); impl Tag { /// The default `Tag`. Such a tag will never have associated metadata and /// is associated with a profile of `Default`. // NOTE: `0` is special! We should never create a default tag via `next()`. #[allow(non_upper_case_globals)] pub const Default: Tag = Tag(0); const PROFILE_TAG_SHIFT: u64 = 62; const PROFILE_TAG_MASK: u64 = 0b11 << Self::PROFILE_TAG_SHIFT; const METADATA_ID_SHIFT: u64 = 0; const METADATA_ID_MASK: u64 = (!Self::PROFILE_TAG_MASK) << Self::METADATA_ID_SHIFT; const fn new(metadata_id: u64, profile_tag: ProfileTag) -> Tag { let bits = ((metadata_id << Self::METADATA_ID_SHIFT) & Self::METADATA_ID_MASK) | ((profile_tag as u64) << Self::PROFILE_TAG_SHIFT) & Self::PROFILE_TAG_MASK; Tag(bits) } // Returns a tag with a unique metadata id. pub(crate) fn next() -> Tag { let id = COUNTER.fetch_add(1, Ordering::AcqRel); if id > Self::METADATA_ID_MASK { panic!("figment: out of unique tag IDs"); } Tag::new(id, ProfileTag::Default) } pub(crate) fn metadata_id(self) -> u64 { (self.0 & Self::METADATA_ID_MASK) >> Self::METADATA_ID_SHIFT } pub(crate) fn profile_tag(self) -> ProfileTag { let bits = (self.0 & Self::PROFILE_TAG_MASK) >> Self::PROFILE_TAG_SHIFT; (bits as u8).into() } pub(crate) fn for_profile(self, profile: &crate::Profile) -> Self { Tag::new(self.metadata_id(), profile.into()) } /// Returns `true` if `self` is `Tag::Default`. /// /// # Example /// /// ```rust /// use figment::value::Tag; /// /// assert!(Tag::Default.is_default()); /// ``` pub const fn is_default(self) -> bool { self.0 == Tag::Default.0 } /// Returns the profile `self` refers to if it is either `Profile::Default` /// or `Profile::Custom`; otherwise returns `None`. /// /// # Example /// /// ```rust /// use figment::Profile; /// use figment::value::Tag; /// /// assert_eq!(Tag::Default.profile(), Some(Profile::Default)); /// ``` pub fn profile(self) -> Option { self.profile_tag().into() } } impl Default for Tag { fn default() -> Self { Tag::Default } } impl PartialEq for Tag { fn eq(&self, other: &Self) -> bool { self.metadata_id() == other.metadata_id() } } impl Eq for Tag { } impl PartialOrd for Tag { fn partial_cmp(&self, other: &Self) -> Option { self.metadata_id().partial_cmp(&other.metadata_id()) } } impl Ord for Tag { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.metadata_id().cmp(&other.metadata_id()) } } impl std::hash::Hash for Tag { fn hash(&self, state: &mut H) { state.write_u64(self.metadata_id()) } } impl From for crate::value::Value { fn from(tag: Tag) -> Self { crate::value::Value::from(tag.0) } } impl<'de> de::Deserialize<'de> for Tag { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de> { struct Visitor; impl<'de> de::Visitor<'de> for Visitor { type Value = Tag; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("a 64-bit metadata id integer") } fn visit_u64(self, v: u64) -> Result { Ok(Tag(v)) } } deserializer.deserialize_any(Visitor) } } impl ser::Serialize for Tag { fn serialize(&self, ser: S) -> Result { ser.serialize_u64(self.0) } } impl fmt::Debug for Tag { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { t if t.is_default() => write!(f, "Tag::Default"), _ => write!(f, "Tag({:?}, {})", self.profile_tag(), self.metadata_id()) } } } figment-0.10.19/src/value/value.rs000064400000000000000000000602771046102023000150260ustar 00000000000000use std::collections::BTreeMap; use std::num::{ParseFloatError, ParseIntError}; use std::str::{FromStr, Split}; use serde::Serialize; use crate::value::{Tag, ValueSerializer, magic::Either}; use crate::error::{Error, Actual}; /// An alias to the type of map used in [`Value::Dict`]. pub type Map = BTreeMap; /// An alias to a [`Map`] from `String` to [`Value`]s. pub type Dict = Map; /// An enum representing all possible figment value variants. /// /// Note that `Value` implements `From` for all reasonable `T`: /// /// ``` /// use figment::value::Value; /// /// let v = Value::from("hello"); /// assert_eq!(v.as_str(), Some("hello")); /// ``` #[derive(Clone)] pub enum Value { /// A string. String(Tag, String), /// A character. Char(Tag, char), /// A boolean. Bool(Tag, bool), /// A numeric value. Num(Tag, Num), /// A value with no value. Empty(Tag, Empty), /// A dictionary: a map from `String` to `Value`. Dict(Tag, Dict), /// A sequence/array/vector. Array(Tag, Vec), } macro_rules! conversion_fn { ($RT:ty, $([$star:tt])? $Variant:ident => $T:ty, $fn_name:ident) => { conversion_fn!( concat!( "Converts `self` into a `", stringify!($T), "` if `self` is a \ `Value::", stringify!($Variant), "`.\n\n", "# Example\n\n", "```\n", "use figment::value::Value;\n\n", "let value: Value = 123.into();\n", "let converted = value.", stringify!($fn_name), "();\n", "```" ), $RT, $([$star])? $Variant => $T, $fn_name ); }; ($doc:expr, $RT:ty, $([$star:tt])? $Variant:ident => $T:ty, $fn_name:ident) => { #[doc = $doc] pub fn $fn_name(self: $RT) -> Option<$T> { match $($star)? self { Value::$Variant(_, v) => Some(v), _ => None } } }; } impl Value { /// Serialize a `Value` from any `T: Serialize`. /// /// ``` /// use figment::value::{Value, Empty}; /// /// let value = Value::serialize(10i8).unwrap(); /// assert_eq!(value.to_i128(), Some(10)); /// /// let value = Value::serialize(()).unwrap(); /// assert_eq!(value, Empty::Unit.into()); /// /// let value = Value::serialize(vec![4, 5, 6]).unwrap(); /// assert_eq!(value, vec![4, 5, 6].into()); /// ``` pub fn serialize(value: T) -> Result { value.serialize(ValueSerializer) } /// Deserialize `self` into any deserializable `T`. /// /// ``` /// use figment::value::Value; /// /// let value = Value::from("hello"); /// let string: String = value.deserialize().unwrap(); /// assert_eq!(string, "hello"); /// ``` pub fn deserialize<'de, T: serde::Deserialize<'de>>(&self) -> Result { T::deserialize(self) } /// Looks up and returns the value at path `path`, where `path` is of the /// form `a.b.c` where `a`, `b`, and `c` are keys to dictionaries. If the /// key is empty, simply returns `self`. If the key is not empty and `self` /// or any of the values for non-leaf keys in the path are not dictionaries, /// returns `None`. /// /// This method consumes `self`. See [`Value::find_ref()`] for a /// non-consuming variant. /// /// # Example /// /// ```rust /// use figment::{value::Value, util::map}; /// /// let value = Value::from(map! { /// "apple" => map! { /// "bat" => map! { /// "pie" => 4usize, /// }, /// "cake" => map! { /// "pumpkin" => 10usize, /// } /// } /// }); /// /// assert!(value.clone().find("apple").is_some()); /// assert!(value.clone().find("apple.bat").is_some()); /// assert!(value.clone().find("apple.cake").is_some()); /// /// assert_eq!(value.clone().find("apple.bat.pie").unwrap().to_u128(), Some(4)); /// assert_eq!(value.clone().find("apple.cake.pumpkin").unwrap().to_u128(), Some(10)); /// /// assert!(value.clone().find("apple.pie").is_none()); /// assert!(value.clone().find("pineapple").is_none()); /// ``` pub fn find(self, path: &str) -> Option { fn find(mut keys: Split, value: Value) -> Option { match keys.next() { Some(k) if !k.is_empty() => find(keys, value.into_dict()?.remove(k)?), Some(_) | None => Some(value) } } find(path.split('.'), self) } /// Exactly like [`Value::find()`] but does not consume `self`, /// returning a reference to the found value, if any, instead. /// /// # Example /// /// ```rust /// use figment::{value::Value, util::map}; /// /// let value = Value::from(map! { /// "apple" => map! { /// "bat" => map! { /// "pie" => 4usize, /// }, /// "cake" => map! { /// "pumpkin" => 10usize, /// } /// } /// }); /// /// assert!(value.find_ref("apple").is_some()); /// assert!(value.find_ref("apple.bat").is_some()); /// assert!(value.find_ref("apple.cake").is_some()); /// /// assert_eq!(value.find_ref("apple.bat.pie").unwrap().to_u128(), Some(4)); /// assert_eq!(value.find_ref("apple.cake.pumpkin").unwrap().to_u128(), Some(10)); /// /// assert!(value.find_ref("apple.pie").is_none()); /// assert!(value.find_ref("pineapple").is_none()); /// ``` pub fn find_ref<'a>(&'a self, path: &str) -> Option<&'a Value> { fn find<'a, 'v>(mut keys: Split<'a, char>, value: &'v Value) -> Option<&'v Value> { match keys.next() { Some(k) if !k.is_empty() => find(keys, value.as_dict()?.get(k)?), Some(_) | None => Some(value) } } find(path.split('.'), self) } /// Returns the [`Tag`] applied to this value. /// /// ``` /// use figment::{Figment, Profile, value::Value, util::map}; /// /// let map: Value = Figment::from(("key", "value")).extract().unwrap(); /// let value = map.find_ref("key").expect("value"); /// assert_eq!(value.as_str(), Some("value")); /// assert!(!value.tag().is_default()); /// assert_eq!(value.tag().profile(), Some(Profile::Global)); /// /// let map: Value = Figment::from(("key", map!["key2" => 123])).extract().unwrap(); /// let value = map.find_ref("key.key2").expect("value"); /// assert_eq!(value.to_i128(), Some(123)); /// assert!(!value.tag().is_default()); /// assert_eq!(value.tag().profile(), Some(Profile::Global)); /// ``` pub fn tag(&self) -> Tag { match *self { Value::String(tag, ..) => tag, Value::Char(tag, ..) => tag, Value::Bool(tag, ..) => tag, Value::Num(tag, ..) => tag, Value::Dict(tag, ..) => tag, Value::Array(tag, ..) => tag, Value::Empty(tag, ..) => tag, } } conversion_fn!(&Value, String => &str, as_str); conversion_fn!(Value, String => String, into_string); conversion_fn!(&Value, [*]Char => char, to_char); conversion_fn!(&Value, [*]Bool => bool, to_bool); conversion_fn!(&Value, [*]Num => Num, to_num); conversion_fn!(&Value, [*]Empty => Empty, to_empty); conversion_fn!(&Value, Dict => &Dict, as_dict); conversion_fn!(Value, Dict => Dict, into_dict); conversion_fn!(&Value, Array => &[Value], as_array); conversion_fn!(Value, Array => Vec, into_array); /// Converts `self` into a `u128` if `self` is an unsigned `Value::Num` /// variant. /// /// # Example /// /// ``` /// use figment::value::Value; /// /// let value: Value = 123u8.into(); /// let converted = value.to_u128(); /// assert_eq!(converted, Some(123)); /// ``` pub fn to_u128(&self) -> Option { self.to_num()?.to_u128() } /// Converts `self` into an `i128` if `self` is an signed `Value::Num` /// variant. /// /// # Example /// /// ``` /// use figment::value::Value; /// /// let value: Value = 123i8.into(); /// let converted = value.to_i128(); /// assert_eq!(converted, Some(123)); /// /// let value: Value = Value::from(5000i64); /// assert_eq!(value.to_i128(), Some(5000i128)); /// ``` pub fn to_i128(&self) -> Option { self.to_num()?.to_i128() } /// Converts `self` into an `f64` if `self` is either a [`Num::F32`] or /// [`Num::F64`]. /// /// # Example /// /// ``` /// use figment::value::Value; /// /// let value: Value = 7.0f32.into(); /// let converted = value.to_f64(); /// assert_eq!(converted, Some(7.0f64)); /// /// let value: Value = Value::from(7.0f64); /// assert_eq!(value.to_f64(), Some(7.0f64)); /// ``` pub fn to_f64(&self) -> Option { self.to_num()?.to_f64() } /// Converts `self` to a `bool` if it is a [`Value::Bool`], or if it is a /// [`Value::String`] or a [`Value::Num`] with a boolean interpretation. /// /// The case-insensitive strings "true", "yes", "1", and "on", and the /// signed or unsigned integers `1` are interpreted as `true`. /// /// The case-insensitive strings "false", "no", "0", and "off", and the /// signed or unsigned integers `0` are interpreted as false. /// /// # Example /// /// ``` /// use figment::value::Value; /// /// let value = Value::from(true); /// assert_eq!(value.to_bool_lossy(), Some(true)); /// /// let value = Value::from(1); /// assert_eq!(value.to_bool_lossy(), Some(true)); /// /// let value = Value::from("YES"); /// assert_eq!(value.to_bool_lossy(), Some(true)); /// /// let value = Value::from(false); /// assert_eq!(value.to_bool_lossy(), Some(false)); /// /// let value = Value::from(0); /// assert_eq!(value.to_bool_lossy(), Some(false)); /// /// let value = Value::from("no"); /// assert_eq!(value.to_bool_lossy(), Some(false)); /// /// let value = Value::from("hello"); /// assert_eq!(value.to_bool_lossy(), None); /// ``` pub fn to_bool_lossy(&self) -> Option { match self { Value::Bool(_, b) => Some(*b), Value::Num(_, num) => match num.to_u128_lossy() { Some(0) => Some(false), Some(1) => Some(true), _ => None } Value::String(_, s) => { const TRUE: &[&str] = &["true", "yes", "1", "on"]; const FALSE: &[&str] = &["false", "no", "0", "off"]; if TRUE.iter().any(|v| uncased::eq(v, s)) { Some(true) } else if FALSE.iter().any(|v| uncased::eq(v, s)) { Some(false) } else { None } }, _ => None, } } /// Converts `self` to a [`Num`] if it is a [`Value::Num`] or if it is a /// [`Value::String`] that parses as a `usize` ([`Num::USize`]), `isize` /// ([`Num::ISize`]), or `f64` ([`Num::F64`]), in that order of precendence. /// /// # Examples /// /// ``` /// use figment::value::{Value, Num}; /// /// let value = Value::from(7_i32); /// assert_eq!(value.to_num_lossy(), Some(Num::I32(7))); /// /// let value = Value::from("7"); /// assert_eq!(value.to_num_lossy(), Some(Num::U8(7))); /// /// let value = Value::from("-7000"); /// assert_eq!(value.to_num_lossy(), Some(Num::I16(-7000))); /// /// let value = Value::from("7000.5"); /// assert_eq!(value.to_num_lossy(), Some(Num::F64(7000.5))); /// ``` pub fn to_num_lossy(&self) -> Option { match self { Value::Num(_, num) => Some(*num), Value::String(_, s) => s.parse().ok(), _ => None, } } /// Converts `self` into the corresponding [`Actual`]. /// /// See also [`Num::to_actual()`] and [`Empty::to_actual()`], which are /// called internally by this method. /// /// # Example /// /// ```rust /// use figment::{value::Value, error::Actual}; /// /// assert_eq!(Value::from('a').to_actual(), Actual::Char('a')); /// assert_eq!(Value::from(&[1, 2, 3]).to_actual(), Actual::Seq); /// ``` pub fn to_actual(&self) -> Actual { match self { Value::String(_, s) => Actual::Str(s.into()), Value::Char(_, c) => Actual::Char(*c), Value::Bool(_, b) => Actual::Bool(*b), Value::Num(_, n) => n.to_actual(), Value::Empty(_, e) => e.to_actual(), Value::Dict(_, _) => Actual::Map, Value::Array(_, _) => Actual::Seq, } } pub(crate) fn tag_mut(&mut self) -> &mut Tag { match self { Value::String(tag, ..) => tag, Value::Char(tag, ..) => tag, Value::Bool(tag, ..) => tag, Value::Num(tag, ..) => tag, Value::Dict(tag, ..) => tag, Value::Array(tag, ..) => tag, Value::Empty(tag, ..) => tag, } } pub(crate) fn map_tag(&mut self, mut f: F) where F: FnMut(&mut Tag) + Copy { if *self.tag_mut() == Tag::Default { f(self.tag_mut()); } match self { Value::Dict(_, v) => v.iter_mut().for_each(|(_, v)| v.map_tag(f)), Value::Array(_, v) => v.iter_mut().for_each(|v| v.map_tag(f)), _ => { /* already handled */ } } } } impl std::fmt::Debug for Value { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::String(_, v) => f.debug_tuple("String").field(v).finish(), Self::Char(_, v) => f.debug_tuple("Char").field(v).finish(), Self::Bool(_, v) => f.debug_tuple("Bool").field(v).finish(), Self::Num(_, v) => f.debug_tuple("Num").field(v).finish(), Self::Empty(_, v) => f.debug_tuple("Empty").field(v).finish(), Self::Dict(_, v) => f.debug_tuple("Dict").field(v).finish(), Self::Array(_, v) => f.debug_tuple("Array").field(v).finish(), } } } impl PartialEq for Value { fn eq(&self, other: &Self) -> bool { match (self, other) { (Value::String(_, v1), Value::String(_, v2)) => v1 == v2, (Value::Char(_, v1), Value::Char(_, v2)) => v1 == v2, (Value::Bool(_, v1), Value::Bool(_, v2)) => v1 == v2, (Value::Num(_, v1), Value::Num(_, v2)) => v1 == v2, (Value::Empty(_, v1), Value::Empty(_, v2)) => v1 == v2, (Value::Dict(_, v1), Value::Dict(_, v2)) => v1 == v2, (Value::Array(_, v1), Value::Array(_, v2)) => v1 == v2, _ => false, } } } macro_rules! impl_from_array { ($($N:literal),*) => ($(impl_from_array!(@$N);)*); (@$N:literal) => ( impl<'a, T: Into + Clone> From<&'a [T; $N]> for Value { #[inline(always)] fn from(value: &'a [T; $N]) -> Value { Value::from(&value[..]) } } ) } impl_from_array!(1, 2, 3, 4, 5, 6, 7, 8); impl From<&str> for Value { fn from(value: &str) -> Value { Value::String(Tag::Default, value.to_string()) } } impl<'a, T: Into + Clone> From<&'a [T]> for Value { fn from(value: &'a [T]) -> Value { Value::Array(Tag::Default, value.iter().map(|v| v.clone().into()).collect()) } } impl<'a, T: Into> From> for Value { fn from(vec: Vec) -> Value { let vector = vec.into_iter().map(|v| v.into()).collect(); Value::Array(Tag::Default, vector) } } impl, V: Into> From> for Value { fn from(map: Map) -> Value { let dict: Dict = map.into_iter() .map(|(k, v)| (k.as_ref().to_string(), v.into())) .collect(); Value::Dict(Tag::Default, dict) } } macro_rules! impl_from_for_value { ($($T:ty: $V:ident),*) => ($( impl From<$T> for Value { fn from(value: $T) -> Value { Value::$V(Tag::Default, value.into()) } } )*) } macro_rules! try_convert { ($n:expr => $($T:ty),*) => {$( if let Ok(n) = <$T as std::convert::TryFrom<_>>::try_from($n) { return Ok(n.into()); } )*} } impl FromStr for Num { type Err = Either; fn from_str(string: &str) -> Result { let string = string.trim(); if string.contains('.') { if string.len() <= (f32::DIGITS as usize + 1) { Ok(string.parse::().map_err(Either::Right)?.into()) } else { Ok(string.parse::().map_err(Either::Right)?.into()) } } else if string.starts_with('-') { let int = string.parse::().map_err(Either::Left)?; try_convert![int => i8, i16, i32, i64]; Ok(int.into()) } else { let uint = string.parse::().map_err(Either::Left)?; try_convert![uint => u8, u16, u32, u64]; Ok(uint.into()) } } } impl_from_for_value! { String: String, char: Char, bool: Bool, u8: Num, u16: Num, u32: Num, u64: Num, u128: Num, usize: Num, i8: Num, i16: Num, i32: Num, i64: Num, i128: Num, isize: Num, f32: Num, f64: Num, Num: Num, Empty: Empty } /// A signed or unsigned numeric value. #[derive(Debug, Clone, Copy)] pub enum Num { /// An 8-bit unsigned integer. U8(u8), /// A 16-bit unsigned integer. U16(u16), /// A 32-bit unsigned integer. U32(u32), /// A 64-bit unsigned integer. U64(u64), /// A 128-bit unsigned integer. U128(u128), /// An unsigned integer of platform width. USize(usize), /// An 8-bit signed integer. I8(i8), /// A 16-bit signed integer. I16(i16), /// A 32-bit signed integer. I32(i32), /// A 64-bit signed integer. I64(i64), /// A 128-bit signed integer. I128(i128), /// A signed integer of platform width. ISize(isize), /// A 32-bit wide float. F32(f32), /// A 64-bit wide float. F64(f64), } impl Num { /// Converts `self` into a `u32` if `self` is an unsigned variant with `<= /// 32` bits. /// /// # Example /// /// ``` /// use figment::value::Num; /// /// let num: Num = 123u8.into(); /// assert_eq!(num.to_u32(), Some(123)); /// /// let num: Num = (u32::max_value() as u64 + 1).into(); /// assert_eq!(num.to_u32(), None); /// ``` pub fn to_u32(self) -> Option { Some(match self { Num::U8(v) => v as u32, Num::U16(v) => v as u32, Num::U32(v) => v as u32, _ => return None, }) } /// Converts `self` into a `u128` if `self` is an unsigned variant. /// /// # Example /// /// ``` /// use figment::value::Num; /// /// let num: Num = 123u8.into(); /// assert_eq!(num.to_u128(), Some(123)); /// ``` pub fn to_u128(self) -> Option { Some(match self { Num::U8(v) => v as u128, Num::U16(v) => v as u128, Num::U32(v) => v as u128, Num::U64(v) => v as u128, Num::U128(v) => v as u128, Num::USize(v) => v as u128, _ => return None, }) } /// Converts `self` into a `u128` if it is non-negative, even if `self` is /// of a signed variant. /// /// # Example /// /// ``` /// use figment::value::Num; /// /// let num: Num = 123u8.into(); /// assert_eq!(num.to_u128_lossy(), Some(123)); /// /// let num: Num = 123i8.into(); /// assert_eq!(num.to_u128_lossy(), Some(123)); /// ``` pub fn to_u128_lossy(self) -> Option { Some(match self { Num::U8(v) => v as u128, Num::U16(v) => v as u128, Num::U32(v) => v as u128, Num::U64(v) => v as u128, Num::U128(v) => v as u128, Num::USize(v) => v as u128, Num::I8(v) if v >= 0 => v as u128, Num::I16(v) if v >= 0 => v as u128, Num::I32(v) if v >= 0 => v as u128, Num::I64(v) if v >= 0 => v as u128, Num::I128(v) if v >= 0 => v as u128, Num::ISize(v) if v >= 0 => v as u128, _ => return None, }) } /// Converts `self` into an `i128` if `self` is a signed `Value::Num` /// variant. /// /// # Example /// /// ``` /// use figment::value::Num; /// /// let num: Num = 123i8.into(); /// assert_eq!(num.to_i128(), Some(123)); /// ``` pub fn to_i128(self) -> Option { Some(match self { Num::I8(v) => v as i128, Num::I16(v) => v as i128, Num::I32(v) => v as i128, Num::I64(v) => v as i128, Num::I128(v) => v as i128, Num::ISize(v) => v as i128, _ => return None, }) } /// Converts `self` into an `f64` if `self` is either a [`Num::F32`] or /// [`Num::F64`]. /// /// # Example /// /// ``` /// use figment::value::Num; /// /// let num: Num = 3.0f32.into(); /// assert_eq!(num.to_f64(), Some(3.0f64)); /// ``` pub fn to_f64(&self) -> Option { Some(match *self { Num::F32(v) => v as f64, Num::F64(v) => v as f64, _ => return None, }) } /// Converts `self` into an [`Actual`]. All unsigned variants return /// [`Actual::Unsigned`], signed variants [`Actual::Signed`], and float /// variants [`Actual::Float`]. Values exceeding the bit-width of the target /// [`Actual`] are truncated. /// /// # Example /// /// ```rust /// use figment::{value::Num, error::Actual}; /// /// assert_eq!(Num::U8(10).to_actual(), Actual::Unsigned(10)); /// assert_eq!(Num::U64(2380).to_actual(), Actual::Unsigned(2380)); /// /// assert_eq!(Num::I8(127).to_actual(), Actual::Signed(127)); /// assert_eq!(Num::ISize(23923).to_actual(), Actual::Signed(23923)); /// /// assert_eq!(Num::F32(2.5).to_actual(), Actual::Float(2.5)); /// assert_eq!(Num::F64(2.103).to_actual(), Actual::Float(2.103)); /// ``` pub fn to_actual(&self) -> Actual { match *self { Num::U8(v) => Actual::Unsigned(v as u128), Num::U16(v) => Actual::Unsigned(v as u128), Num::U32(v) => Actual::Unsigned(v as u128), Num::U64(v) => Actual::Unsigned(v as u128), Num::U128(v) => Actual::Unsigned(v as u128), Num::USize(v) => Actual::Unsigned(v as u128), Num::I8(v) => Actual::Signed(v as i128), Num::I16(v) => Actual::Signed(v as i128), Num::I32(v) => Actual::Signed(v as i128), Num::I64(v) => Actual::Signed(v as i128), Num::I128(v) => Actual::Signed(v as i128), Num::ISize(v) => Actual::Signed(v as i128), Num::F32(v) => Actual::Float(v as f64), Num::F64(v) => Actual::Float(v as f64), } } } impl PartialEq for Num { fn eq(&self, other: &Self) -> bool { self.to_actual() == other.to_actual() } } macro_rules! impl_from_for_num_value { ($($T:ty: $V:ident),*) => ($( impl From<$T> for Num { fn from(value: $T) -> Num { Num::$V(value) } } )*) } impl_from_for_num_value! { u8: U8, u16: U16, u32: U32, u64: U64, u128: U128, usize: USize, i8: I8, i16: I16, i32: I32, i64: I64, i128: I128, isize: ISize, f32: F32, f64: F64 } /// A value with no value: `None` or `Unit`. #[derive(Debug, Clone, Copy, PartialEq)] pub enum Empty { /// Like `Option::None`. None, /// Like `()`. Unit } impl Empty { /// Converts `self` into an [`Actual`]. /// /// # Example /// /// ```rust /// use figment::{value::Empty, error::Actual}; /// /// assert_eq!(Empty::None.to_actual(), Actual::Option); /// assert_eq!(Empty::Unit.to_actual(), Actual::Unit); /// ``` pub fn to_actual(&self) -> Actual { match self { Empty::None => Actual::Option, Empty::Unit => Actual::Unit, } } } figment-0.10.19/tests/camel-case.rs000064400000000000000000000007601046102023000151320ustar 00000000000000use figment::{Figment, providers::Env}; #[test] fn camel_case() { #[derive(serde::Deserialize, PartialEq, Debug)] #[serde(rename_all = "camelCase")] struct Config { top_key_1: i32 } figment::Jail::expect_with(|jail| { jail.set_env("topKey1", "100"); let config: Config = Figment::new() .merge(Env::raw().lowercase(false)) .extract() .unwrap(); assert_eq!(config.top_key_1, 100); Ok(()) }); } figment-0.10.19/tests/cargo.rs000064400000000000000000000025411046102023000142320ustar 00000000000000use serde::Deserialize; use figment::{Figment, providers::{Format, Toml, Json, Env}}; #[test] fn mini_cargo() { #[derive(Debug, PartialEq, Deserialize)] struct Package { name: String, description: Option, authors: Vec, publish: Option, } #[derive(Debug, PartialEq, Deserialize)] struct Config { package: Package, rustc: Option, rustdoc: Option, } // Replicate part of Cargo's config but also support `Cargo.json` with lower // precedence than `Cargo.toml`. figment::Jail::expect_with(|jail| { jail.create_file("Cargo.toml", r#" [package] name = "test" authors = ["bob"] publish = false "#)?; let config: Config = Figment::new() .merge(Toml::file("Cargo.toml")) .merge(Env::prefixed("CARGO_")) .merge(Env::raw().only(&["RUSTC", "RUSTDOC"])) .join(Json::file("Cargo.json")) .extract()?; assert_eq!(config, Config { package: Package { name: "test".into(), description: None, authors: vec!["bob".into()], publish: Some(false) }, rustc: None, rustdoc: None }); Ok(()) }); } figment-0.10.19/tests/empty-env-vars.rs000064400000000000000000000027141046102023000160360ustar 00000000000000use figment::{Figment, providers::Env}; #[derive(serde::Deserialize)] struct Config { foo: String } #[test] fn empty_env_vars() { figment::Jail::expect_with(|jail| { jail.set_env("FOO", "bar"); jail.set_env("BAZ", "put"); let config = Figment::new() .merge(Env::raw().map(|_| "".into())) .extract::(); assert!(config.is_err()); let config = Figment::new() .merge(Env::raw().map(|_| " ".into())) .extract::(); assert!(config.is_err()); let config = Figment::new() .merge(Env::raw().map(|k| { if k == "foo" { k.into() } else { "".into() } })) .extract::()?; assert_eq!(config.foo, "bar"); let config = Figment::new() .merge(Env::raw().map(|k| { if k == "foo" { " foo ".into() } else { "".into() } })) .extract::()?; assert_eq!(config.foo, "bar"); jail.set_env("___foo", "is here"); let config = Figment::new() .merge(Env::raw().split("_")) .extract::()?; assert_eq!(config.foo, "bar"); jail.set_env("foo__", "is here"); let config = Figment::new() .merge(Env::raw().split("_")) .extract::()?; assert_eq!(config.foo, "bar"); Ok(()) }); } figment-0.10.19/tests/enum.rs000064400000000000000000000033151046102023000141030ustar 00000000000000use serde::{Deserialize, Serialize}; use figment::{Figment, providers::{Format, Toml, Serialized, Env}}; #[derive(Debug, Default, Deserialize, Serialize)] pub struct Config { foo: Option, bar: Option, baz: Option, } #[derive(PartialEq, Debug, Deserialize, Serialize)] pub enum Foo { Mega, Supa } #[derive(PartialEq, Debug, Deserialize, Serialize)] pub enum Bar { None, Some(usize, String) } #[derive(PartialEq, Debug, Deserialize, Serialize)] #[serde(untagged)] pub enum Baz { A(String), B(usize), } #[test] fn test_enum_de() { let figment = || Figment::new() .merge(Serialized::defaults(Config { foo: None, bar: Some(Bar::Some(9999, "not-the-string".into())), baz: None })) .merge(Toml::file("Test.toml")) .merge(Env::prefixed("TEST_")); figment::Jail::expect_with(|jail| { let test: Config = figment().extract()?; assert_eq!(test.foo, None); assert_eq!(test.bar, Some(Bar::Some(9999, "not-the-string".into()))); assert_eq!(test.baz, None); jail.create_file("Test.toml", r#" foo = "Mega" baz = "goobar" [bar] Some = [10, "hi"] "#)?; let test: Config = figment().extract()?; assert_eq!(test.foo, Some(Foo::Mega)); assert_eq!(test.bar, Some(Bar::Some(10, "hi".into()))); assert_eq!(test.baz, Some(Baz::A("goobar".into()))); jail.set_env("TEST_foo", "Supa"); jail.set_env("TEST_bar", "None"); let test: Config = figment().extract()?; assert_eq!(test.foo, Some(Foo::Supa)); assert_eq!(test.bar, Some(Bar::None)); Ok(()) }) } figment-0.10.19/tests/lossy_values.rs000064400000000000000000000015231046102023000156660ustar 00000000000000use serde::Deserialize; use figment::{Figment, providers::{Toml, Format}}; #[derive(Debug, Deserialize, PartialEq)] struct Config { bs: Vec, u8s: Vec, i32s: Vec, f64s: Vec, } static TOML: &str = r##" u8s = [1, 2, 3, "4", 5, "6"] i32s = [-1, -2, 3, "-4", 5, "6"] f64s = [1, "2", -3, -4.5, "5.0", "-6.0"] bs = [true, false, "true", "false", "YES", "no", "on", "OFF", "1", "0", 1, 0] "##; #[test] fn lossy_values() { let config: Config = Figment::from(Toml::string(TOML)).extract_lossy().unwrap(); assert_eq!(&config.u8s, &[ 1, 2, 3, 4, 5, 6 ]); assert_eq!(&config.i32s, &[-1, -2, 3, -4, 5, 6]); assert_eq!(&config.f64s, &[1.0, 2.0, -3.0, -4.5, 5.0, -6.0]); assert_eq!(&config.bs, &[ true, false, true, false, true, false, true, false, true, false, true, false ]); } figment-0.10.19/tests/tagged.rs000064400000000000000000000035051046102023000143730ustar 00000000000000use figment::{Figment, Jail, Profile}; use figment::{value::{Value, magic::Tagged}, providers::Serialized}; #[test] fn check_values_are_tagged_with_profile() { Jail::expect_with(|_| { let figment = Figment::new() .merge(Serialized::default("default", "default")) .merge(Serialized::global("global", "global")) .merge(Serialized::default("custom", "custom").profile("custom")); let tagged: Tagged = figment.extract_inner("default")?; let value: Value = figment.find_value("default")?; assert_eq!(tagged.tag().profile(), Some(Profile::Default)); assert_eq!(value.tag().profile(), Some(Profile::Default)); let tagged: Tagged = figment.extract_inner("global")?; let value: Value = figment.find_value("global")?; assert_eq!(tagged.tag().profile(), Some(Profile::Global)); assert_eq!(value.tag().profile(), Some(Profile::Global)); let figment = figment.select("custom"); let tagged: Tagged = figment.extract_inner("custom")?; let value: Value = figment.find_value("custom")?; assert_eq!(tagged.tag().profile(), None); assert_eq!(value.tag().profile(), None); Ok(()) }); } #[test] fn check_errors_are_tagged_with_path() { Jail::expect_with(|_| { let figment = Figment::new() .merge(("foo", 123)) .merge(("foo.bar", 789)) .merge(("baz", 789)); let err = figment.extract_inner::("foo").unwrap_err(); assert_eq!(err.path, vec!["foo"]); let err = figment.extract_inner::("foo.bar").unwrap_err(); assert_eq!(err.path, vec!["foo", "bar"]); let err = figment.extract_inner::("foo.bar.baz").unwrap_err(); assert!(err.path.is_empty()); Ok(()) }); } figment-0.10.19/tests/tuple-struct.rs000064400000000000000000000006021046102023000156060ustar 00000000000000use figment::{Figment, providers::{Toml, Format}}; use serde::Deserialize; #[derive(Debug, Deserialize, PartialEq)] struct Foo(pub isize); #[derive(Debug, Deserialize, PartialEq)] struct Config { foo: Foo } #[test] fn one_value() { let config: Config = Figment::from(Toml::string("foo = 42")).extract().unwrap(); assert_eq!(config, Config { foo: Foo(42) }) } figment-0.10.19/tests/yaml-enum.rs000064400000000000000000000027011046102023000150410ustar 00000000000000use serde::Deserialize; use figment::{Figment, providers::{Format, Yaml}}; #[derive(Deserialize, PartialEq, Debug, Clone)] pub enum Trigger { Start, Inter { sec: u32 }, Triple(usize, usize, usize), End(String), } #[derive(Deserialize, Debug, Clone)] pub struct Script { pub triggers: Vec, } const YAML: &str = " triggers: - !Start - !Inter sec: 5 - !Inter { sec: 7, } - !Triple [1, 2, 3] - !End Now "; const YAML2: &str = " triggers: - Start: - Inter: sec: 5 - !Inter { sec: 7, } - Triple: - 1 - 2 - 3 - End: Now "; #[test] fn figment_yaml_deserize() { let figment = Figment::new().merge(Yaml::string(YAML)); let script = figment.extract::