use lazy_static::lazy_static;
use log::trace;
use regex::{Captures, Regex};
use serde::de::{self, Deserialize, Deserializer};
use serde::ser::{Serialize, Serializer};
use std::collections::BTreeMap;
use std::env;
use std::error;
use std::fmt::{self, Display};
use std::path::{Path, PathBuf};
use std::result;
use std::str::FromStr;
use std::string;
use void::Void;
use super::merge_override::MergeOverride;
use crate::errors::*;
pub trait Environment {
fn var(&self, key: &str) -> result::Result<String, env::VarError>;
}
#[derive(Debug, Default)]
#[allow(missing_copy_implementations)]
pub struct OsEnvironment {
_hidden: (),
}
impl OsEnvironment {
pub fn new() -> OsEnvironment {
Default::default()
}
}
impl Environment for OsEnvironment {
fn var(&self, key: &str) -> result::Result<String, env::VarError> {
let result = env::var(key);
trace!("Read env var {}: {:?}", key, &result);
result
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Mode {
Interpolate,
Unescape,
Validate,
}
fn interpolate_helper(
input: &str,
mode: Mode,
env: &dyn Environment,
) -> Result<String> {
#[rustfmt::skip]
lazy_static! {
static ref VAR: Regex = Regex::new(r#"(?x)
# We found a '$',
\$
# ...but what follows it?
(?:
# A variable like $FOO
(?P<var1>[A-Za-z_][A-Za-z0-9_]+)
|
# A variable like ${FOO}, ${FOO-default} or ${FOO:-default}.
\{(?P<var2>[A-Za-z_][A-Za-z0-9_]+) (?:(?P<colon>:)?-(?P<default>.*))? \}
|
# An escaped dollar sign?
(?P<dollar>\$)
|
# Something else? In this case, we want to fail.
(?P<unknown>.|$)
)
"#).unwrap();
}
let mut err = None;
let result = VAR.replace_all(input, |caps: &Captures<'_>| {
if caps.name("unknown").is_some() {
err = Some(Error::InterpolateInvalidSyntax(input.to_owned()));
"".to_owned()
} else if caps.name("dollar").is_some() {
"$".to_owned()
} else if mode == Mode::Unescape {
err = Some(Error::InterpolationDisabled(input.to_owned()));
"".to_owned()
} else {
let var = caps
.name("var1")
.or_else(|| caps.name("var2"))
.unwrap()
.as_str();
match (env.var(var), caps.name("colon"), caps.name("default")) {
_ if mode == Mode::Validate => "".to_owned(),
(Ok(ref val), Some(_), Some(default)) if val == "" => {
default.as_str().to_owned()
}
(Ok(val), _, _) => val,
(Err(_), _, Some(default)) => default.as_str().to_owned(),
(Err(_), _, _) => {
err = Some(Error::InterpolateUndefinedVariable(var.to_owned()));
"".to_owned()
}
}
}
});
if let Some(e) = err {
return Err(e);
}
Ok(result.into_owned())
}
fn interpolate_env(input: &str, env: &dyn Environment) -> Result<String> {
interpolate_helper(input, Mode::Interpolate, env)
}
#[test]
fn interpolate_env_interpolates_env_vars() {
env::set_var("FOO", "foo");
env::remove_var("BAR");
env::set_var("EMPTY", "");
let env = OsEnvironment::new();
assert_eq!("foo", interpolate_env("$FOO", &env).unwrap());
assert_eq!("foo", interpolate_env("${FOO}", &env).unwrap());
assert_eq!("foo foo", interpolate_env("$FOO $FOO", &env).unwrap());
assert_eq!("plain", interpolate_env("plain", &env).unwrap());
assert_eq!("$escaped", interpolate_env("$$escaped", &env).unwrap());
assert_eq!("${escaped}", interpolate_env("$${escaped}", &env).unwrap());
assert_eq!("bar", interpolate_env("${BAR-bar}", &env).unwrap());
assert_eq!("bar", interpolate_env("${BAR:-bar}", &env).unwrap());
assert_eq!("", interpolate_env("${EMPTY-bar}", &env).unwrap());
assert_eq!("bar", interpolate_env("${EMPTY:-bar}", &env).unwrap());
}
#[test]
fn interpolate_env_returns_an_error_if_input_is_invalid() {
let env = OsEnvironment::new();
assert!(interpolate_env("$", &env).is_err());
assert!(interpolate_env("${", &env).is_err());
assert!(interpolate_env("$}", &env).is_err());
assert!(interpolate_env("${}", &env).is_err());
assert!(interpolate_env("${ }", &env).is_err());
assert!(interpolate_env("${ foo}", &env).is_err());
assert!(interpolate_env("${foo }", &env).is_err());
assert!(interpolate_env("${foo!}", &env).is_err());
}
#[test]
fn interpolate_env_returns_an_error_if_variable_is_undefined() {
let env = OsEnvironment::new();
env::remove_var("NOSUCH");
assert!(interpolate_env("$NOSUCH", &env).is_err());
}
fn escape_str(input: &str) -> String {
input.replace("$", "$$")
}
#[test]
fn escape_str_escapes_dollar_signs() {
assert_eq!("$$VAR1 $${VAR2} $$", escape_str("$VAR1 ${VAR2} $"));
}
fn unescape_str(input: &str) -> Result<String> {
let env = OsEnvironment::new();
interpolate_helper(input, Mode::Unescape, &env)
}
#[test]
fn unescape_str_unescapes_without_interpolating() {
env::set_var("FOO", "foo");
assert!(unescape_str("$FOO").is_err());
assert_eq!("plain", unescape_str("plain").unwrap());
assert_eq!("$escaped", unescape_str("$$escaped").unwrap());
assert_eq!("${escaped}", unescape_str("$${escaped}").unwrap());
}
fn validate(input: &str) -> Result<()> {
let env = OsEnvironment::new();
interpolate_helper(input, Mode::Validate, &env).map(|_| ())
}
#[test]
fn validate_tests_interpolation_strings() {
assert!(validate("plain").is_ok());
assert!(validate("$$escaped").is_ok());
assert!(validate("$${escaped}").is_ok());
assert!(validate("$FOO").is_ok());
assert!(validate("${FOO}").is_ok());
assert!(validate("${").is_err());
assert!(validate("$}").is_err());
assert!(validate("${}").is_err());
assert!(validate("${ }").is_err());
assert!(validate("${ foo}").is_err());
assert!(validate("${foo }").is_err());
assert!(validate("${foo!}").is_err());
}
pub trait IntoInvalidValueError: error::Error + Sized {
fn into_invalid_value_error(self, wanted: &str, input: &str) -> Error {
Error::invalid_value(wanted, input)
}
}
impl IntoInvalidValueError for Error {
fn into_invalid_value_error(self, _: &str, _: &str) -> Error {
self
}
}
impl IntoInvalidValueError for string::ParseError {
}
impl IntoInvalidValueError for Void {
fn into_invalid_value_error(self, _: &str, _: &str) -> Error {
unreachable!()
}
}
pub trait InterpolatableValue: Clone + Eq {
fn iv_from_str(s: &str) -> Result<Self>;
fn fmt_iv(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result;
}
macro_rules! impl_interpolatable_value {
($ty:ty) => {
impl $crate::v2::interpolation::InterpolatableValue for $ty {
fn iv_from_str(s: &str) -> Result<Self> {
use $crate::v2::interpolation::IntoInvalidValueError;
fn convert_err<E>(err: E, input: &str) -> Error
where
E: IntoInvalidValueError,
{
err.into_invalid_value_error(stringify!($ty), input)
}
FromStr::from_str(s).map_err(|err| convert_err(err, s))
}
fn fmt_iv(&self, f: &mut fmt::Formatter<'_>) -> ::std::fmt::Result {
use std::fmt::Display;
self.fmt(f)
}
}
};
}
impl_interpolatable_value!(String);
impl InterpolatableValue for PathBuf {
fn iv_from_str(s: &str) -> Result<Self> {
Ok(Path::new(s).to_owned())
}
fn fmt_iv(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display().fmt(f)
}
}
struct DisplayInterpolatableValue<'a, V>(&'a V)
where
V: InterpolatableValue;
impl<'a, T> Display for DisplayInterpolatableValue<'a, T>
where
T: InterpolatableValue,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
DisplayInterpolatableValue(val) => val.fmt_iv(f),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum RawOrValue<T>
where
T: InterpolatableValue,
{
Raw(String),
Value(T),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RawOr<T>(RawOrValue<T>)
where
T: InterpolatableValue;
impl<T: InterpolatableValue> MergeOverride for RawOr<T> {}
pub fn raw<T, S>(s: S) -> Result<RawOr<T>>
where
T: InterpolatableValue,
S: Into<String>,
{
let raw: String = s.into();
validate(&raw)?;
match unescape_str(&raw) {
Ok(unescaped) => {
let parsed: T = InterpolatableValue::iv_from_str(&unescaped)?;
Ok(RawOr(RawOrValue::Value(parsed)))
}
Err(_) => Ok(RawOr(RawOrValue::Raw(raw))),
}
}
pub fn escape<T, S>(s: S) -> Result<RawOr<T>>
where
T: InterpolatableValue,
S: AsRef<str>,
{
let value: T = InterpolatableValue::iv_from_str(s.as_ref())?;
Ok(RawOr(RawOrValue::Value(value)))
}
pub fn value<T>(v: T) -> RawOr<T>
where
T: InterpolatableValue,
{
RawOr(RawOrValue::Value(v))
}
impl<T> RawOr<T>
where
T: InterpolatableValue,
{
pub fn value(&self) -> Result<&T> {
match *self {
RawOr(RawOrValue::Value(ref val)) => Ok(val),
RawOr(RawOrValue::Raw(ref raw)) => Err(unescape_str(raw).unwrap_err()),
}
}
pub fn value_mut(&mut self) -> Result<&mut T> {
match *self {
RawOr(RawOrValue::Value(ref mut val)) => Ok(val),
RawOr(RawOrValue::Raw(ref raw)) => Err(unescape_str(raw).unwrap_err()),
}
}
pub fn interpolate_env(&mut self, env: &dyn Environment) -> Result<&mut T> {
let RawOr(ref mut inner) = *self;
if let RawOrValue::Value(ref mut val) = *inner {
Ok(val)
} else {
let new_val = if let RawOrValue::Raw(ref raw) = *inner {
let interpolated = interpolate_env(raw, env)?;
InterpolatableValue::iv_from_str(&interpolated)?
} else {
unreachable!()
};
*inner = RawOrValue::Value(new_val);
if let RawOrValue::Value(ref mut val) = *inner {
Ok(val)
} else {
unreachable!()
}
}
}
pub fn interpolate(&mut self) -> Result<&mut T> {
let env = OsEnvironment::new();
self.interpolate_env(&env)
}
}
impl<T> Display for RawOr<T>
where
T: InterpolatableValue,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
RawOr(RawOrValue::Raw(ref raw)) => write!(f, "{}", raw),
RawOr(RawOrValue::Value(ref value)) => {
let s = format!("{}", DisplayInterpolatableValue(value));
write!(f, "{}", escape_str(&s))
}
}
}
}
impl<T> Serialize for RawOr<T>
where
T: InterpolatableValue,
{
fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<T> FromStr for RawOr<T>
where
T: InterpolatableValue,
{
type Err = Error;
fn from_str(s: &str) -> result::Result<Self, Self::Err> {
raw(s)
}
}
impl<'de, T> Deserialize<'de> for RawOr<T>
where
T: InterpolatableValue,
{
fn deserialize<D>(deserializer: D) -> result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let string = String::deserialize(deserializer)?;
Self::from_str(&string).map_err(|err| de::Error::custom(format!("{}", err)))
}
}
pub trait InterpolateAll {
fn interpolate_all(&mut self) -> Result<()> {
Ok(())
}
}
impl InterpolateAll for i16 {}
impl InterpolateAll for u16 {}
impl InterpolateAll for u32 {}
impl InterpolateAll for bool {}
impl InterpolateAll for String {}
impl InterpolateAll for () {}
impl<T: InterpolateAll> InterpolateAll for Option<T> {
fn interpolate_all(&mut self) -> Result<()> {
if let Some(ref mut v) = *self {
v.interpolate_all()?;
}
Ok(())
}
}
impl<T: InterpolateAll> InterpolateAll for Vec<T> {
fn interpolate_all(&mut self) -> Result<()> {
for v in self.iter_mut() {
v.interpolate_all()?;
}
Ok(())
}
}
impl<K: Ord + Clone, T: InterpolateAll> InterpolateAll for BTreeMap<K, T> {
fn interpolate_all(&mut self) -> Result<()> {
for (_k, v) in self.iter_mut() {
v.interpolate_all()?;
}
Ok(())
}
}
impl<T: InterpolatableValue> InterpolateAll for RawOr<T> {
fn interpolate_all(&mut self) -> Result<()> {
self.interpolate()?;
Ok(())
}
}
macro_rules! derive_interpolate_all_for {
($ty:ident, { $( $field:ident ),+ }) => {
impl $crate::v2::interpolation::InterpolateAll for $ty {
fn interpolate_all(&mut self) -> Result<()>
{
$( self.$field.interpolate_all()?; )+
Ok(())
}
}
}
}