use std::{
collections::{BTreeMap, BTreeSet, HashMap, HashSet, LinkedList, VecDeque},
fmt::Display,
hash::Hash,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
str::FromStr,
};
pub use derpscfg_derive::Derpscfg;
#[cfg(feature = "preserve_order")]
use crate::indexmap::IndexMap;
#[cfg(feature = "preserve_order")]
use crate::indexmap::IndexSet;
use crate::{Directive, Error, Scfg};
pub fn parse<T>(input: &str) -> Result<T, Error>
where
T: TryFrom<Scfg, Error = Error>,
{
let p = input.parse::<Scfg>()?;
T::try_from(p)
}
pub trait Decode<T>: Sized {
fn decode(field: &'static str, d: Directive) -> Result<Vec<T>, Error>;
}
pub trait DecodeScalar: Sized {
fn decode_scalar(field: &'static str, d: Directive) -> Result<Vec<Self>, Error>;
}
pub trait DecodeCardinality<S, T>: Sized {
fn cardinality(field: &'static str, d: Vec<Directive>) -> Result<T, Error>;
}
pub trait DecodeParamsCardinality<S, T>: Sized {
fn params_cardinality(field: &'static str, d: &mut Directive) -> Result<T, Error>;
}
macro_rules! decode_from_str {
($i:ident) => {
impl DecodeScalar for $i {
fn decode_scalar(f: &'static str, d: Directive) -> Result<Vec<$i>, Error> {
if d.child().is_some() {
return Err(Error::FromStr(format!(
"'{f}' should be scalar value, but has child node"
)));
}
d.params()
.into_iter()
.map(|p| {
$i::from_str(p).map_err(|e| {
Error::FromStr(format!("invalid value '{p}' for '{f}': {e}"))
})
})
.collect()
}
}
};
}
decode_from_str!(bool);
decode_from_str!(char);
decode_from_str!(u8);
decode_from_str!(u16);
decode_from_str!(u32);
decode_from_str!(u64);
decode_from_str!(u128);
decode_from_str!(i8);
decode_from_str!(i16);
decode_from_str!(i32);
decode_from_str!(i64);
decode_from_str!(i128);
decode_from_str!(f32);
decode_from_str!(f64);
decode_from_str!(usize);
decode_from_str!(isize);
decode_from_str!(String);
decode_from_str!(IpAddr);
decode_from_str!(Ipv4Addr);
decode_from_str!(Ipv6Addr);
impl<T> Decode<T> for T
where
T: DecodeScalar,
{
fn decode(f: &'static str, d: Directive) -> Result<Vec<T>, Error> {
T::decode_scalar(f, d)
}
}
impl Decode<Directive> for Directive {
fn decode(_f: &'static str, d: Directive) -> Result<Vec<Directive>, Error> {
Ok(vec![d])
}
}
pub struct Decoder;
impl<T> DecodeCardinality<T, T> for Decoder
where
T: Decode<T>,
{
fn cardinality(f: &'static str, d: Vec<Directive>) -> Result<T, Error> {
let values: Vec<_> = d
.into_iter()
.map(|d| T::decode(f, d))
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.flatten()
.collect();
match values.len() {
0 => Err(Error::MissingValue(format!("missing value for '{f}'"))),
1 => Ok(values.into_iter().next().unwrap()),
_ => Err(Error::Cardinality(format!(
"found {} values for '{f}', but must be exactly one",
values.len()
))),
}
}
}
impl<T> DecodeCardinality<T, Option<T>> for Decoder
where
T: Decode<T>,
{
fn cardinality(f: &'static str, d: Vec<Directive>) -> Result<Option<T>, Error> {
let values: Vec<_> = d
.into_iter()
.map(|d| T::decode(f, d))
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.flatten()
.collect();
match values.len() {
0 | 1 => Ok(values.into_iter().next()),
_ => Err(Error::Cardinality(format!(
"found {} values for '{f}', but must be one or none",
values.len()
))),
}
}
}
macro_rules! decode_cardinality {
($i:ident) => {
impl<T> DecodeCardinality<T, $i<T>> for Decoder
where
T: Decode<T>,
{
fn cardinality(f: &'static str, d: Vec<Directive>) -> Result<$i<T>, Error> {
let r: Vec<_> = d.into_iter().map(|d| T::decode(f, d)).collect::<Vec<_>>();
let t: Result<Vec<Vec<T>>, _> = r.into_iter().collect();
Ok(t?.into_iter().flat_map(|i| i).collect())
}
}
};
}
decode_cardinality!(Vec);
decode_cardinality!(VecDeque);
decode_cardinality!(LinkedList);
impl<T> DecodeCardinality<T, BTreeSet<T>> for Decoder
where
T: Decode<T> + Ord,
{
fn cardinality(f: &'static str, d: Vec<Directive>) -> Result<BTreeSet<T>, Error> {
let r: Vec<_> = d.into_iter().map(|d| T::decode(f, d)).collect::<Vec<_>>();
let t: Result<Vec<Vec<T>>, _> = r.into_iter().collect();
Ok(t?.into_iter().flatten().collect())
}
}
impl<T> DecodeCardinality<T, HashSet<T>> for Decoder
where
T: Decode<T> + Hash + Eq,
{
fn cardinality(f: &'static str, d: Vec<Directive>) -> Result<HashSet<T>, Error> {
let r: Vec<_> = d.into_iter().map(|d| T::decode(f, d)).collect::<Vec<_>>();
let t: Result<Vec<Vec<T>>, _> = r.into_iter().collect();
Ok(t?.into_iter().flatten().collect())
}
}
#[cfg(feature = "preserve_order")]
impl<T> DecodeCardinality<T, IndexSet<T>> for Decoder
where
T: Decode<T> + Hash + Eq,
{
fn cardinality(f: &'static str, d: Vec<Directive>) -> Result<IndexSet<T>, Error> {
let r: Vec<_> = d.into_iter().map(|d| T::decode(f, d)).collect::<Vec<_>>();
let t: Result<Vec<Vec<T>>, _> = r.into_iter().collect();
Ok(t?.into_iter().flatten().collect())
}
}
macro_rules! decode_cardinality_map {
($i:ident) => {
impl<T> DecodeCardinality<T, $i<String, T>> for Decoder
where
T: Decode<T>,
{
fn cardinality(f: &'static str, d: Vec<Directive>) -> Result<$i<String, T>, Error> {
let mut result = $i::new();
d.into_iter()
.map(|mut d| {
let key = d
.pop_param()
.ok_or(Error::MissingValue("missing param".to_string()))?;
let values = T::decode(f, d)?;
let value = match values.len() {
0 => Err(Error::MissingValue(format!("missing value for '{f}'"))),
1 => Ok(values.into_iter().next().unwrap()),
_ => Err(Error::Cardinality(format!(
"found {} values for '{f}', but must be exactly one",
values.len()
))),
}?;
match result.insert(key.clone(), value) {
None => Ok(()),
Some(_) => Err(Error::DuplicateKey(format!("{f} '{key}'"))),
}
})
.collect::<Result<Vec<_>, Error>>()?;
Ok(result)
}
}
};
}
decode_cardinality_map!(BTreeMap);
decode_cardinality_map!(HashMap);
#[cfg(feature = "preserve_order")]
decode_cardinality_map!(IndexMap);
impl<T, E> DecodeParamsCardinality<T, T> for Decoder
where
T: FromStr<Err = E>,
E: Display + Sized,
{
fn params_cardinality(f: &'static str, d: &mut Directive) -> Result<T, Error> {
let s = d.pop_param().ok_or(Error::MissingValue(format!(
"'{f}' has no more parameters, but must have at least one"
)))?;
T::from_str(s.as_str())
.map_err(|e| Error::FromStr(format!("invalid parameter value '{s}' for '{f}': {e}")))
}
}
impl<T, E> DecodeParamsCardinality<T, Option<T>> for Decoder
where
T: FromStr<Err = E>,
E: Display + Sized,
{
fn params_cardinality(f: &'static str, d: &mut Directive) -> Result<Option<T>, Error> {
d.pop_param()
.map(|s| {
T::from_str(s.as_str()).map_err(|e| {
Error::FromStr(format!("invalid parameter value '{s}' for '{f}': {e}"))
})
})
.transpose()
}
}
macro_rules! decode_params_cardinality {
($i:ident) => {
impl<T, E> DecodeParamsCardinality<T, $i<T>> for Decoder
where
T: FromStr<Err = E>,
E: Display + Sized,
{
fn params_cardinality(f: &'static str, d: &mut Directive) -> Result<$i<T>, Error> {
d.take_params()
.into_iter()
.map(|s| {
T::from_str(&s).map_err(|e| {
Error::FromStr(format!("invalid parameter value '{s}' for '{f}': {e}"))
})
})
.collect()
}
}
};
}
decode_params_cardinality!(Vec);
decode_params_cardinality!(VecDeque);
decode_params_cardinality!(LinkedList);
#[cfg(test)]
mod test {
use super::*;
use crate as derpscfg; #[cfg(feature = "preserve_order")]
use crate::indexmap::IndexSet;
#[derive(Debug, Clone, Derpscfg, PartialEq, Eq, PartialOrd, Ord)]
pub struct Item {
pub name: String,
}
#[test]
fn test_parse_simple() {
static SCFG_DOC: &str = r#"
name derp
"#;
let have = parse::<Item>(SCFG_DOC).unwrap();
let want = Item {
name: "derp".to_string(),
};
assert_eq!(want, have);
}
#[test]
fn test_parse_wrap() {
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct Wrapper {
pub item: Item,
}
static SCFG_DOC: &str = r#"
item {
name derp
}
"#;
let have = parse::<Wrapper>(SCFG_DOC).unwrap();
let want = Wrapper {
item: Item {
name: "derp".to_string(),
},
};
assert_eq!(want, have);
}
#[test]
fn test_parse_wrap_param() {
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct ParamItem {
#[scfg(param)]
pub id: u64,
pub name: String,
}
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct Wrapper {
pub item: ParamItem,
}
static SCFG_DOC: &str = r#"
item 1 {
name derp
}
"#;
let have = parse::<Wrapper>(SCFG_DOC).unwrap();
let want = Wrapper {
item: ParamItem {
id: 1,
name: "derp".to_string(),
},
};
assert_eq!(want, have);
}
#[test]
fn test_parse_wrap_opt_none() {
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct Wrapper {
pub item: Option<Item>,
}
static SCFG_DOC: &str = r#""#;
let have = parse::<Wrapper>(SCFG_DOC).unwrap();
let want = Wrapper { item: None };
assert_eq!(want, have);
}
#[test]
fn test_parse_wrap_opt_some() {
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct Wrapper {
pub item: Option<Item>,
}
static SCFG_DOC: &str = r#"
item {
name derp
}
"#;
let have = parse::<Wrapper>(SCFG_DOC).unwrap();
let want = Wrapper {
item: Some(Item {
name: "derp".to_string(),
}),
};
assert_eq!(want, have);
}
#[test]
fn test_parse_wrap_vec_empty() {
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct Wrapper {
pub item: Vec<Item>,
}
static SCFG_DOC: &str = r#""#;
let have = parse::<Wrapper>(SCFG_DOC).unwrap();
let want = Wrapper { item: vec![] };
assert_eq!(want, have);
}
#[test]
fn test_parse_wrap_vec_many() {
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct Wrapper {
pub item: Vec<Item>,
}
static SCFG_DOC: &str = r#"
item {
name derp
}
item {
name bleh
}
"#;
let have = parse::<Wrapper>(SCFG_DOC).unwrap();
let want = Wrapper {
item: vec![
Item {
name: "derp".to_string(),
},
Item {
name: "bleh".to_string(),
},
],
};
assert_eq!(want, have);
}
#[test]
fn test_parse_wrap_set_simple() {
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct Wrapper {
pub item: BTreeSet<String>,
}
static SCFG_DOC: &str = r#"
item foo bar
item bar2
"#;
let have = parse::<Wrapper>(SCFG_DOC).unwrap();
let want_item = BTreeSet::from(["foo".to_string(), "bar".to_string(), "bar2".to_string()]);
assert_eq!(have.item, want_item);
}
#[cfg(feature = "preserve_order")]
#[test]
fn test_parse_wrap_set_index() {
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct Wrapper {
pub item: IndexSet<String>,
}
static SCFG_DOC: &str = r#"
item foo bar
item bar2
"#;
let have = parse::<Wrapper>(SCFG_DOC).unwrap();
let want_item = IndexSet::from(["foo".to_string(), "bar".to_string(), "bar2".to_string()]);
assert_eq!(have.item, want_item);
}
#[test]
fn test_parse_wrap_set_item() {
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct Wrapper {
pub item: BTreeSet<Item>,
}
static SCFG_DOC: &str = r#"
item {
name fooname
}
item {
name barname
}
"#;
let have = parse::<Wrapper>(SCFG_DOC).unwrap();
let want_item = BTreeSet::from([
Item {
name: "fooname".to_string(),
},
Item {
name: "barname".to_string(),
},
]);
assert_eq!(have.item, want_item);
}
#[test]
fn test_parse_wrap_map_simple() {
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct Wrapper {
pub item: BTreeMap<String, String>,
}
static SCFG_DOC: &str = r#"
item foo foovalue
item bar barvalue
"#;
let have = parse::<Wrapper>(SCFG_DOC).unwrap();
let want_item = BTreeMap::from([
("foo".to_string(), "foovalue".to_string()),
("bar".to_string(), "barvalue".to_string()),
]);
assert_eq!(have.item, want_item);
}
#[test]
fn test_parse_wrap_map_item() {
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct Wrapper {
pub item: BTreeMap<String, Item>,
}
static SCFG_DOC: &str = r#"
item foo {
name fooname
}
item bar {
name barname
}
"#;
let have = parse::<Wrapper>(SCFG_DOC).unwrap();
let want_item = BTreeMap::from([
(
"foo".to_string(),
Item {
name: "fooname".to_string(),
},
),
(
"bar".to_string(),
Item {
name: "barname".to_string(),
},
),
]);
assert_eq!(have.item, want_item);
}
#[test]
fn test_parse_wrap_map_item_dup() {
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct Wrapper {
pub item: BTreeMap<String, Item>,
}
static SCFG_DOC: &str = r#"
item foo {
name fooname
}
item foo {
name barname
}
"#;
let have = parse::<Wrapper>(SCFG_DOC);
assert!(matches!(have, Err(Error::DuplicateKey(_))));
}
#[test]
fn test_parse_wrap_altname() {
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct Wrapper {
pub das_item: Item,
}
static SCFG_DOC: &str = r#"
das-item {
name derp
}
"#;
let have = parse::<Wrapper>(SCFG_DOC).unwrap();
let want = Wrapper {
das_item: Item {
name: "derp".to_string(),
},
};
assert_eq!(want, have);
}
#[test]
fn test_parse_wrap_rename() {
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct Wrapper {
#[scfg(name = "das-item")]
pub item: Item,
}
static SCFG_DOC: &str = r#"
das-item {
name derp
}
"#;
let have = parse::<Wrapper>(SCFG_DOC).unwrap();
let want = Wrapper {
item: Item {
name: "derp".to_string(),
},
};
assert_eq!(want, have);
}
#[test]
fn test_parse_wrap_default() {
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct DefItem {
#[scfg(default)]
pub name: String,
}
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct Wrapper {
pub item: DefItem,
}
static SCFG_DOC: &str = r#"
item
"#;
let have = parse::<Wrapper>(SCFG_DOC).unwrap();
let want = Wrapper {
item: DefItem {
name: String::default(),
},
};
assert_eq!(want, have);
}
#[test]
fn test_parse_wrap_default_custom() {
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct DefItem {
#[scfg(default=String::from("<no name>"))]
pub name: String,
}
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct Wrapper {
pub item: DefItem,
}
static SCFG_DOC: &str = r#"
item
"#;
let have = parse::<Wrapper>(SCFG_DOC).unwrap();
let want = Wrapper {
item: DefItem {
name: "<no name>".to_string(),
},
};
assert_eq!(want, have);
}
#[test]
fn test_parse_wrap_skip() {
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct SkipItem {
pub name: String,
#[scfg(skip)]
pub skip: String,
}
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct Wrapper {
pub item: SkipItem,
}
static SCFG_DOC: &str = r#"
item {
name derp
}
"#;
let have = parse::<Wrapper>(SCFG_DOC).unwrap();
let want = Wrapper {
item: SkipItem {
name: "derp".to_string(),
skip: String::default(),
},
};
assert_eq!(want, have);
}
#[test]
fn test_parse_wrap_skip_err() {
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct SkipItem {
pub name: String,
#[scfg(skip)]
pub skip: String,
}
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct Wrapper {
pub item: SkipItem,
}
static SCFG_DOC: &str = r#"
item {
name derp
skip "must be skipped"
}
"#;
let have = parse::<Wrapper>(SCFG_DOC);
assert!(matches!(have, Err(Error::UnknownDirective(_))));
}
#[test]
fn test_parse_wrap_skip_toplevel() {
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct Wrapper {
#[scfg(skip)]
pub item: String,
}
static SCFG_DOC: &str = r#""#;
let have = parse::<Wrapper>(SCFG_DOC).unwrap();
let want = Wrapper {
item: String::default(),
};
assert_eq!(want, have);
}
#[test]
fn test_parse_wrap_skip_toplevel_err() {
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct Wrapper {
#[scfg(skip)]
pub item: String,
}
static SCFG_DOC: &str = r#"
item foo
"#;
let have = parse::<Wrapper>(SCFG_DOC);
assert!(matches!(have, Err(Error::UnknownDirective(_))));
}
#[test]
fn test_parse_wrap_skip_toplevel_custom_default() {
#[derive(Debug, Derpscfg, PartialEq, Eq)]
pub struct Wrapper {
#[scfg(skip,default=String::from("derp"))]
pub item: String,
}
static SCFG_DOC: &str = r#""#;
let have = parse::<Wrapper>(SCFG_DOC).unwrap();
let want = Wrapper {
item: "derp".to_string(),
};
assert_eq!(want, have);
}
}