use regex::{Captures, Regex};
use serde::de::{self, Deserialize, Deserializer};
use serde::ser::{Serialize, Serializer};
use std::env;
use std::error::{self, Error};
use std::fmt::{self, Display};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::string;
use void::Void;
use super::helpers::InvalidValueError;
#[derive(Debug)]
pub enum InterpolationError {
InvalidSyntax(String),
UnparsableValue(InvalidValueError),
UndefinedVariable(String),
InterpolationDisabled(String),
}
impl fmt::Display for InterpolationError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self {
&InterpolationError::InvalidSyntax(ref input) =>
write!(f, "{}: <{}>", self.description(), input),
&InterpolationError::UnparsableValue(ref err) =>
write!(f, "{}: {}", self.description(), err),
&InterpolationError::UndefinedVariable(ref var) =>
write!(f, "{}: {}", self.description(), var),
&InterpolationError::InterpolationDisabled(ref input) =>
write!(f, "{}: <{}>", self.description(), input),
}
}
}
impl error::Error for InterpolationError {
fn description(&self) -> &str {
match self {
&InterpolationError::InvalidSyntax(_) =>
"invalid interpolation syntax",
&InterpolationError::UnparsableValue(_) =>
"cannot escape invalid value",
&InterpolationError::UndefinedVariable(_) =>
"undefined environment variable in interpolation",
&InterpolationError::InterpolationDisabled(_) =>
"cannot parse without interpolating environment variables",
}
}
fn cause(&self) -> Option<&Error> {
match self {
&InterpolationError::UnparsableValue(ref err) => Some(err),
_ => None,
}
}
}
impl From<InvalidValueError> for InterpolationError {
fn from(err: InvalidValueError) -> InterpolationError {
InterpolationError::UnparsableValue(err)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Mode {
Interpolate,
Unescape,
Validate,
}
fn interpolate_helper(input: &str, mode: Mode) ->
Result<String, InterpolationError>
{
lazy_static! {
static ref VAR: Regex =
Regex::new(r#"\$(?:([A-Za-z_][A-Za-z0-9_]+)|\{([A-Za-z_][A-Za-z0-9_]+)\}|(\$)|(.))"#).unwrap();
}
let mut err = None;
let result = VAR.replace_all(input, |caps: &Captures| {
if caps.at(4).is_some() {
err = Some(InterpolationError::InvalidSyntax(input.to_owned()));
"".to_owned()
} else if caps.at(3).is_some() {
"$".to_owned()
} else if mode == Mode::Unescape {
err = Some(InterpolationError::InterpolationDisabled(input.to_owned()));
return "".to_owned();
} else {
let var = caps.at(1).or_else(|| caps.at(2)).unwrap();
match env::var(var) {
_ if mode == Mode::Validate => "".to_owned(),
Ok(val) => val,
Err(_) => {
err = Some(InterpolationError::UndefinedVariable(var.to_owned()));
"".to_owned()
}
}
}
});
if let Some(e) = err {
return Err(e);
}
Ok(result)
}
fn interpolate_env(input: &str) -> Result<String, InterpolationError> {
interpolate_helper(input, Mode::Interpolate)
}
#[test]
fn interpolate_env_interpolates_env_vars() {
env::set_var("FOO", "foo");
assert_eq!("foo", interpolate_env("$FOO").unwrap());
assert_eq!("foo", interpolate_env("${FOO}").unwrap());
assert_eq!("foo foo", interpolate_env("$FOO $FOO").unwrap());
assert_eq!("plain", interpolate_env("plain").unwrap());
assert_eq!("$escaped", interpolate_env("$$escaped").unwrap());
assert_eq!("${escaped}", interpolate_env("$${escaped}").unwrap());
}
#[test]
fn interpolate_env_returns_an_error_if_input_is_invalid() {
assert!(interpolate_env("${").is_err());
assert!(interpolate_env("$}").is_err());
assert!(interpolate_env("${}").is_err());
assert!(interpolate_env("${ }").is_err());
assert!(interpolate_env("${ foo}").is_err());
assert!(interpolate_env("${foo }").is_err());
assert!(interpolate_env("${foo!}").is_err());
}
#[test]
fn interpolate_env_returns_an_error_if_variable_is_undefined() {
env::remove_var("NOSUCH");
assert!(interpolate_env("$NOSUCH").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, InterpolationError> {
interpolate_helper(input, Mode::Unescape)
}
#[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<(), InterpolationError> {
interpolate_helper(input, Mode::Validate).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 + Sized {
fn into_invalid_value_error(self, wanted: &str, input: &str) ->
InvalidValueError
{
InvalidValueError::new(wanted, input)
}
}
impl IntoInvalidValueError for InvalidValueError {
fn into_invalid_value_error(self, _: &str, _: &str) -> InvalidValueError {
self
}
}
impl IntoInvalidValueError for string::ParseError {
}
impl IntoInvalidValueError for Void {
fn into_invalid_value_error(self, _: &str, _: &str) -> InvalidValueError {
unreachable!()
}
}
pub trait InterpolatableValue: Clone + Eq {
fn iv_from_str(s: &str) -> Result<Self, InvalidValueError>;
fn fmt_iv(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>;
}
macro_rules! impl_interpolatable_value {
($ty:ty) => {
impl $crate::v2::interpolation::InterpolatableValue for $ty {
fn iv_from_str(s: &str) ->
Result<Self, $crate::v2::helpers::InvalidValueError>
{
use $crate::v2::interpolation::IntoInvalidValueError;
fn convert_err<E>(err: E, input: &str) -> InvalidValueError
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) -> Result<(), fmt::Error> {
use std::fmt::Display;
self.fmt(f)
}
}
}
}
impl_interpolatable_value!(String);
impl InterpolatableValue for PathBuf {
fn iv_from_str(s: &str) -> Result<Self, InvalidValueError> {
Ok(Path::new(s).to_owned())
}
fn fmt_iv(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
self.display().fmt(f)
}
}
struct DisplayInterpolatableValue<'a, V>(&'a V)
where V: 'a + InterpolatableValue;
impl<'a, T> fmt::Display for DisplayInterpolatableValue<'a, T>
where T: InterpolatableValue
{
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
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;
pub fn raw<T, S>(s: S) -> Result<RawOr<T>, InterpolationError>
where T: InterpolatableValue,
S: Into<String>
{
let raw: String = s.into();
try!(validate(&raw));
match unescape_str(&raw) {
Ok(unescaped) => {
let parsed: T = try!(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>, InterpolationError>
where T: InterpolatableValue,
S: AsRef<str>
{
let value: T = try!(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, InterpolationError> {
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, InterpolationError> {
match self {
&mut RawOr(RawOrValue::Value(ref mut val)) => Ok(val),
&mut RawOr(RawOrValue::Raw(ref raw)) =>
Err(unescape_str(&raw).unwrap_err()),
}
}
pub fn interpolate(&mut self) -> Result<&mut T, InterpolationError> {
let &mut RawOr(ref mut inner) = self;
if let &mut RawOrValue::Value(ref mut val) = inner {
Ok(val)
} else {
let new_val =
if let &mut RawOrValue::Raw(ref raw) = inner {
try!(InterpolatableValue::iv_from_str(&try!(interpolate_env(raw))))
} else {
unreachable!()
};
*inner = RawOrValue::Value(new_val);
if let &mut RawOrValue::Value(ref mut val) = inner {
Ok(val)
} else {
unreachable!()
}
}
}
}
impl<T> fmt::Display for RawOr<T>
where T: InterpolatableValue
{
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
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: &mut S) -> Result<(), S::Error>
where S: Serializer
{
serializer.serialize_str(&self.to_string())
}
}
impl<T> FromStr for RawOr<T>
where T: InterpolatableValue
{
type Err = InvalidValueError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
raw(s).map_err(|err| {
match err {
InterpolationError::UnparsableValue(err) => err,
_ => InvalidValueError::new("interpolation", s),
}
})
}
}
impl<T> Deserialize for RawOr<T>
where T: InterpolatableValue
{
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: Deserializer
{
let string = try!(String::deserialize(deserializer));
Self::from_str(&string).map_err(|err| {
de::Error::custom(format!("{}", err))
})
}
}