use anyhow::anyhow;
use std::str::FromStr;
use crate::Container;
#[derive(Debug, Clone, PartialEq)]
enum Target {
All,
Type(String),
Structs,
Enums {
unit_only: bool,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct Derive {
target: Target,
pub derived_trait: String,
}
impl Derive {
pub fn all(derived_trait: &str) -> Self {
Derive {
target: Target::All,
derived_trait: derived_trait.to_owned(),
}
}
pub fn is_applicable_to(&self, s: &Container) -> bool {
match &self.target {
Target::All => true,
Target::Type(name) => &s.name == name,
Target::Structs => !s.is_enum,
Target::Enums { unit_only } => {
if !s.is_enum {
return false;
}
if *unit_only && s.members.iter().any(|member| !member.type_.is_empty()) {
return false;
}
true
}
}
}
}
impl FromStr for Derive {
type Err = anyhow::Error;
fn from_str(value: &str) -> std::prelude::v1::Result<Self, Self::Err> {
if let Some((target, derived_trait)) = value.split_once('=') {
if target.is_empty() {
return Err(anyhow!("derive target cannot be empty in '{value}'"));
};
if derived_trait.is_empty() {
return Err(anyhow!("derived trait cannot be empty in '{value}'"));
}
let target = if let Some(target) = target.strip_prefix('@') {
match target {
"struct" | "structs" => Target::Structs,
"enum" | "enums" => Target::Enums { unit_only: false },
"enum:simple" | "enums:simple" => Target::Enums { unit_only: true },
other => {
return Err(anyhow!(
"unknown derive target @{other}, must be one of @struct, @enum, or @enum:simple"
))
}
}
} else {
Target::Type(target.to_owned())
};
Ok(Derive {
target,
derived_trait: derived_trait.to_owned(),
})
} else {
Ok(Derive {
target: Target::All,
derived_trait: value.to_owned(),
})
}
}
}
#[cfg(test)]
#[test]
fn derive_applicability() {
use crate::Member;
let structure = Container {
is_enum: false,
..Default::default()
};
let simple_enum = Container {
is_enum: true,
members: vec![Member {
type_: String::new(),
..Default::default()
}],
..Default::default()
};
let complex_enum = Container {
is_enum: true,
members: vec![Member {
type_: "SomeNonEmptyType".to_string(),
..Default::default()
}],
..Default::default()
};
let named_structure = Container {
name: "MyStruct".to_string(),
is_enum: false,
..Default::default()
};
let named_enum = Container {
name: "OtherEnum".to_string(),
is_enum: true,
..Default::default()
};
let all_trait = Derive::all("PartialEq");
assert!(all_trait.is_applicable_to(&structure));
assert!(all_trait.is_applicable_to(&simple_enum));
assert!(all_trait.is_applicable_to(&complex_enum));
assert!(all_trait.is_applicable_to(&named_structure));
assert!(all_trait.is_applicable_to(&named_enum));
let simple_enum_trait = Derive {
target: Target::Enums { unit_only: true },
derived_trait: "PartialEq".to_string(),
};
assert!(simple_enum_trait.is_applicable_to(&simple_enum));
assert!(!simple_enum_trait.is_applicable_to(&complex_enum));
assert!(!simple_enum_trait.is_applicable_to(&structure));
assert!(!simple_enum_trait.is_applicable_to(&named_structure));
assert!(simple_enum_trait.is_applicable_to(&named_enum));
let complex_enum_trait = Derive {
target: Target::Enums { unit_only: false },
derived_trait: "PartialEq".to_string(),
};
assert!(complex_enum_trait.is_applicable_to(&simple_enum));
assert!(complex_enum_trait.is_applicable_to(&complex_enum));
assert!(!complex_enum_trait.is_applicable_to(&structure));
assert!(!complex_enum_trait.is_applicable_to(&named_structure));
assert!(complex_enum_trait.is_applicable_to(&named_enum));
let struct_trait = Derive {
target: Target::Structs,
derived_trait: "PartialEq".to_string(),
};
assert!(!struct_trait.is_applicable_to(&simple_enum));
assert!(!struct_trait.is_applicable_to(&complex_enum));
assert!(struct_trait.is_applicable_to(&structure));
assert!(struct_trait.is_applicable_to(&named_structure));
assert!(!struct_trait.is_applicable_to(&named_enum));
let named_struct_trait = Derive {
target: Target::Type("MyStruct".to_string()),
derived_trait: "PartialEq".to_string(),
};
assert!(!named_struct_trait.is_applicable_to(&simple_enum));
assert!(!named_struct_trait.is_applicable_to(&complex_enum));
assert!(!named_struct_trait.is_applicable_to(&structure));
assert!(named_struct_trait.is_applicable_to(&named_structure));
assert!(!named_struct_trait.is_applicable_to(&named_enum));
}
#[cfg(test)]
#[test]
fn test_derive_parsing() {
assert_eq!("PartialEq".parse::<Derive>().unwrap(), Derive::all("PartialEq"));
assert_eq!("@struct=PartialEq".parse::<Derive>().unwrap(), Derive {
target: Target::Structs,
derived_trait: "PartialEq".to_string()
});
assert_eq!("@enum=PartialEq".parse::<Derive>().unwrap(), Derive {
target: Target::Enums { unit_only: false },
derived_trait: "PartialEq".to_string()
});
assert_eq!("@enum:simple=PartialEq".parse::<Derive>().unwrap(), Derive {
target: Target::Enums { unit_only: true },
derived_trait: "PartialEq".to_string()
});
assert_eq!("MyStruct=PartialEq".parse::<Derive>().unwrap(), Derive {
target: Target::Type("MyStruct".to_string()),
derived_trait: "PartialEq".to_string()
});
assert_eq!(
"=".parse::<Derive>().unwrap_err().to_string(),
"derive target cannot be empty in '='"
);
assert_eq!(
"=PartialEq".parse::<Derive>().unwrap_err().to_string(),
"derive target cannot be empty in '=PartialEq'"
);
assert_eq!(
"@struct=".parse::<Derive>().unwrap_err().to_string(),
"derived trait cannot be empty in '@struct='"
);
}