use serde::de::DeserializeOwned;
use crate::VariableMap;
pub fn from_slice<'a, T: DeserializeOwned, M>(data: &[u8], variables: &'a M) -> Result<T, Error>
where
M: VariableMap<'a> + ?Sized,
M::Value: AsRef<str>,
{
from_str(std::str::from_utf8(data)?, variables)
}
pub fn from_str<'a, T: DeserializeOwned, M>(data: &str, variables: &'a M) -> Result<T, Error>
where
M: VariableMap<'a> + ?Sized,
M::Value: AsRef<str>,
{
let mut value: toml::Value = toml::from_str(data)?;
substitute_string_values(&mut value, variables)?;
Ok(T::deserialize(value)?)
}
pub fn substitute_string_values<'a, M>(value: &mut toml::Value, variables: &'a M) -> Result<(), crate::Error>
where
M: VariableMap<'a> + ?Sized,
M::Value: AsRef<str>,
{
visit_string_values(value, |value| {
*value = crate::substitute(value.as_str(), variables)?;
Ok(())
})
}
#[derive(Debug)]
pub enum Error {
InvalidUtf8(std::str::Utf8Error),
Toml(toml::de::Error),
Subst(crate::Error),
}
impl From<std::str::Utf8Error> for Error {
#[inline]
fn from(other: std::str::Utf8Error) -> Self {
Self::InvalidUtf8(other)
}
}
impl From<toml::de::Error> for Error {
#[inline]
fn from(other: toml::de::Error) -> Self {
Self::Toml(other)
}
}
impl From<crate::Error> for Error {
#[inline]
fn from(other: crate::Error) -> Self {
Self::Subst(other)
}
}
impl std::error::Error for Error {}
impl std::fmt::Display for Error {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidUtf8(e) => std::fmt::Display::fmt(e, f),
Self::Toml(e) => std::fmt::Display::fmt(e, f),
Self::Subst(e) => std::fmt::Display::fmt(e, f),
}
}
}
fn visit_string_values<F, E>(value: &mut toml::Value, fun: F) -> Result<(), E>
where
F: Copy + Fn(&mut String) -> Result<(), E>,
{
match value {
toml::Value::Boolean(_) => Ok(()),
toml::Value::Integer(_) => Ok(()),
toml::Value::Float(_) => Ok(()),
toml::Value::Datetime(_) => Ok(()),
toml::Value::String(val) => fun(val),
toml::Value::Array(seq) => {
for value in seq {
visit_string_values(value, fun)?;
}
Ok(())
},
toml::Value::Table(map) => {
for (_key, value) in map.iter_mut() {
visit_string_values(value, fun)?;
}
Ok(())
},
}
}
#[cfg(test)]
#[rustfmt::skip]
mod test {
use std::collections::HashMap;
use super::*;
use assert2::{assert, let_assert};
#[test]
fn test_from_str() {
#[derive(Debug, serde::Deserialize)]
struct Struct {
bar: String,
baz: String,
}
let mut variables = HashMap::new();
variables.insert("bar", "aap");
variables.insert("baz", "noot");
#[rustfmt::skip]
let_assert!(Ok(parsed) = from_str(
concat!(
"bar = \"$bar\"\n",
"baz = \"$baz/with/stuff\"\n",
),
&variables,
));
let parsed: Struct = parsed;
assert!(parsed.bar == "aap");
assert!(parsed.baz == "noot/with/stuff");
}
#[test]
fn test_from_str_no_substitution() {
#[derive(Debug, serde::Deserialize)]
struct Struct {
bar: String,
baz: String,
}
let mut variables = HashMap::new();
variables.insert("bar", "aap");
variables.insert("baz", "noot");
#[rustfmt::skip]
let_assert!(Ok(parsed) = from_str(
concat!(
"bar = \"aap\"\n",
"baz = \"noot/with/stuff\"\n",
),
&crate::NoSubstitution,
));
let parsed: Struct = parsed;
assert!(parsed.bar == "aap");
assert!(parsed.baz == "noot/with/stuff");
}
#[test]
fn test_toml_in_var_is_not_parsed() {
#[derive(Debug, serde::Deserialize)]
struct Struct {
bar: String,
baz: String,
}
let mut variables = HashMap::new();
variables.insert("bar", "aap\nbaz = \"mies\"");
variables.insert("baz", "noot");
#[rustfmt::skip]
let_assert!(Ok(parsed) = from_str(
concat!(
"bar = \"$bar\"\n",
"baz = \"$baz\"\n",
),
&variables,
));
let parsed: Struct = parsed;
assert!(parsed.bar == "aap\nbaz = \"mies\"");
assert!(parsed.baz == "noot");
}
#[test]
fn test_dyn_variable_map() {
#[derive(Debug, serde::Deserialize)]
struct Struct {
bar: String,
baz: String,
}
let mut variables = HashMap::new();
variables.insert("bar", "aap");
variables.insert("baz", "noot");
let variables: &dyn VariableMap<Value = &&str> = &variables;
#[rustfmt::skip]
let_assert!(Ok(parsed) = from_str(
concat!(
"bar = \"$bar\"\n",
"baz = \"$baz/with/stuff\"\n",
),
variables,
));
let parsed: Struct = parsed;
assert!(parsed.bar == "aap");
assert!(parsed.baz == "noot/with/stuff");
}
}