use std::fmt;
use std::str::FromStr;
use crate::errors::ParamParseError;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Type {
pub namespace: Vec<String>,
pub name: String,
pub bare: bool,
pub generic_ref: bool,
pub generic_arg: Option<Box<Type>>,
}
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for ns in &self.namespace {
write!(f, "{ns}.")?;
}
if self.generic_ref {
write!(f, "!")?;
}
write!(f, "{}", self.name)?;
if let Some(arg) = &self.generic_arg {
write!(f, "<{arg}>")?;
}
Ok(())
}
}
impl Type {
pub(crate) fn collect_generic_refs<'a>(&'a self, output: &mut Vec<&'a str>) {
if self.generic_ref {
output.push(&self.name);
}
if let Some(arg) = &self.generic_arg {
arg.collect_generic_refs(output);
}
}
}
impl FromStr for Type {
type Err = ParamParseError;
fn from_str(raw: &str) -> Result<Self, Self::Err> {
let (raw, generic_ref) = match raw.strip_prefix('!') {
Some(r) => (r, true),
None => (raw, false),
};
let (name_part, generic_arg) = match raw.split_once('<') {
Some((name, rest)) => match rest.strip_suffix('>') {
Some(arg) => (name, Some(Box::new(Type::from_str(arg)?))),
None => return Err(ParamParseError::InvalidGeneric),
},
None => (raw, None),
};
let (namespace, name) = match name_part.rsplit_once('.') {
Some((ns_part, n)) => (ns_part.split('.').map(String::from).collect::<Vec<_>>(), n),
None => (Vec::new(), name_part),
};
if namespace.iter().any(|p| p.is_empty()) {
return Err(ParamParseError::Empty);
}
let first = name.chars().next().ok_or(ParamParseError::Empty)?;
let bare = first.is_ascii_lowercase();
Ok(Self {
namespace,
name: name.to_owned(),
bare,
generic_ref,
generic_arg,
})
}
}