From b7b8379e10e567485177e4269dd4e249d8bef600 Mon Sep 17 00:00:00 2001 From: Paul Woolcock Date: Tue, 5 Mar 2019 15:15:17 -0500 Subject: [PATCH] Add built-in support for deserializing Data from the env --- Cargo.toml | 4 ++- src/errors.rs | 7 ++++ src/helpers/env.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++++ src/helpers/mod.rs | 12 +++++++ src/lib.rs | 3 ++ 5 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 src/helpers/env.rs diff --git a/Cargo.toml b/Cargo.toml index f557438..4fb88da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ tap-reader = "1" try_from = "0.3.2" toml = { version = "0.4.6", optional = true } hyper-old-types = "0.11.0" +envy = { version = "0.4.0", optional = true } [dependencies.chrono] version = "0.4" @@ -30,7 +31,8 @@ features = ["serde"] [features] default = [] json = [] -all = ["toml", "json"] +env = ["envy"] +all = ["toml", "json", "env"] [build-dependencies] skeptic = "0.13.3" diff --git a/src/errors.rs b/src/errors.rs index f9fd8e6..d33e90c 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,5 +1,7 @@ use std::{error, fmt, io::Error as IoError}; +#[cfg(feature = "env")] +use envy::Error as EnvyError; use hyper_old_types::Error as HeaderParseError; use reqwest::{header::ToStrError as HeaderStrError, Error as HttpError, StatusCode}; use serde_json::Error as SerdeError; @@ -52,6 +54,9 @@ pub enum Error { HeaderStrError(HeaderStrError), /// Error parsing the http Link header HeaderParseError(HeaderParseError), + #[cfg(feature = "env")] + /// Error deserializing from the environment + Envy(EnvyError), /// Other errors Other(String), } @@ -89,6 +94,7 @@ impl error::Error for Error { Error::TomlDe(ref e) => e.description(), Error::HeaderStrError(ref e) => e.description(), Error::HeaderParseError(ref e) => e.description(), + Error::Envy(ref e) => e.description(), Error::Other(ref e) => e, } } @@ -128,6 +134,7 @@ from! { #[cfg(feature = "toml")] TomlDeError, TomlDe, HeaderStrError, HeaderStrError, HeaderParseError, HeaderParseError, + EnvyError, Envy, String, Other, } diff --git a/src/helpers/env.rs b/src/helpers/env.rs new file mode 100644 index 0000000..1f13acb --- /dev/null +++ b/src/helpers/env.rs @@ -0,0 +1,81 @@ +use envy; + +use data::Data; +use Result; + +/// Attempts to deserialize a Data struct from the environment +pub fn from_env() -> Result { + Ok(envy::from_env()?) +} + +/// Attempts to deserialize a Data struct from the environment. All keys are +/// prefixed with the given prefix +pub fn from_env_prefixed(prefix: &str) -> Result { + Ok(envy::prefixed(prefix).from_env()?) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{ + env, + ops::FnOnce, + panic::{catch_unwind, UnwindSafe}, + }; + + fn withenv R + UnwindSafe, R>(prefix: Option<&'static str>, test: F) -> R { + env::set_var(makekey(prefix, "BASE"), "https://example.com"); + env::set_var(makekey(prefix, "CLIENT_ID"), "adbc01234"); + env::set_var(makekey(prefix, "CLIENT_SECRET"), "0987dcba"); + env::set_var(makekey(prefix, "REDIRECT"), "urn:ietf:wg:oauth:2.0:oob"); + env::set_var(makekey(prefix, "TOKEN"), "fedc5678"); + + let result = catch_unwind(test); + + env::remove_var(makekey(prefix, "BASE")); + env::remove_var(makekey(prefix, "CLIENT_ID")); + env::remove_var(makekey(prefix, "CLIENT_SECRET")); + env::remove_var(makekey(prefix, "REDIRECT")); + env::remove_var(makekey(prefix, "TOKEN")); + + fn makekey(prefix: Option<&'static str>, key: &str) -> String { + if let Some(prefix) = prefix { + format!("{}{}", prefix, key) + } else { + key.to_string() + } + } + + result.expect("failed") + } + + #[test] + fn test_from_env_no_prefix() { + let desered = withenv(None, || from_env()).expect("Couldn't deser"); + assert_eq!( + desered, + Data { + base: "https://example.com".into(), + client_id: "adbc01234".into(), + client_secret: "0987dcba".into(), + redirect: "urn:ietf:wg:oauth:2.0:oob".into(), + token: "fedc5678".into(), + } + ); + } + + #[test] + fn test_from_env_prefixed() { + let desered = withenv(Some("APP_"), || from_env_prefixed("APP_")).expect("Couldn't deser"); + assert_eq!( + desered, + Data { + base: "https://example.com".into(), + client_id: "adbc01234".into(), + client_secret: "0987dcba".into(), + redirect: "urn:ietf:wg:oauth:2.0:oob".into(), + token: "fedc5678".into(), + } + ); + } +} diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index ed2b107..d1c420e 100644 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -22,5 +22,17 @@ pub mod toml; /// ``` pub mod json; +#[cfg(feature = "env")] +/// Helpers for deserializing a `Data` struct from the environment +/// +/// In order to use this module, set the "env" feature in your Cargo.toml: +/// +/// ```toml,ignore +/// [dependencies.elefren] +/// version = "0.18" +/// features = ["env"] +/// ``` +pub mod env; + /// Helpers for working with the command line pub mod cli; diff --git a/src/lib.rs b/src/lib.rs index 0489873..0b1956a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,6 +58,9 @@ extern crate tap_reader; extern crate try_from; extern crate url; +#[cfg(feature = "env")] +extern crate envy; + #[cfg(feature = "toml")] extern crate toml as tomlcrate;