use std::collections::HashMap;
use std::convert::TryInto;
use quote::ToTokens;
use syn::{LitChar, LitFloat, LitInt, Path};
use crate::{ArgValue, Error, ValueClass};
#[derive(Clone)]
pub struct AttrReq {
pub arg_req: HashMap<String, ArgValueReq>,
pub path_req: ListReq<Path>,
pub char_req: ListReq<LitChar>,
pub integer_req: ListReq<LitInt>,
pub float_req: ListReq<LitFloat>,
pub string_req: ValueReq,
pub bytes_req: ValueReq,
pub bool_req: ValueReq,
}
impl AttrReq {
pub fn with(args: HashMap<&str, ArgValueReq>) -> AttrReq {
let args = args
.into_iter()
.map(|(name, req)| (name.to_owned(), req))
.collect();
AttrReq {
arg_req: args,
path_req: ListReq::Deny,
char_req: ListReq::Deny,
integer_req: ListReq::Deny,
float_req: ListReq::Deny,
string_req: ValueReq::Prohibited,
bytes_req: ValueReq::Prohibited,
bool_req: ValueReq::Prohibited,
}
}
}
#[derive(Clone)]
pub enum ArgValueReq {
Required {
default: Option<ArgValue>,
class: ValueClass,
},
Optional(ValueClass),
Prohibited,
}
impl ArgValueReq {
pub fn with_default(default: impl Into<ArgValue>) -> ArgValueReq {
let value = default.into();
ArgValueReq::Required {
class: value
.value_class()
.expect("Default argument value must not be `ArgValue::None`"),
default: Some(value),
}
}
pub fn required(class: impl Into<ValueClass>) -> ArgValueReq {
ArgValueReq::Required {
default: None,
class: class.into(),
}
}
pub fn optional(class: impl Into<ValueClass>) -> ArgValueReq {
ArgValueReq::Optional(class.into())
}
pub fn value_class(&self) -> Option<ValueClass> {
match self {
ArgValueReq::Required { class, .. } | ArgValueReq::Optional(class) => Some(*class),
ArgValueReq::Prohibited => None,
}
}
pub fn default_value(&self) -> ArgValue {
match self {
ArgValueReq::Required {
default: Some(d), ..
} => d.clone(),
_ => ArgValue::None,
}
}
pub fn is_required(&self) -> bool {
#[allow(clippy::match_like_matches_macro)]
match self {
ArgValueReq::Required { default: None, .. } => true,
_ => false,
}
}
pub fn check(
&self,
value: &mut ArgValue,
attr: impl ToString,
arg: impl ToString,
) -> Result<(), Error> {
let value = match (value, self) {
(ref val, ArgValueReq::Required { default: None, .. }) if val.is_none() => {
return Err(Error::ArgValueRequired {
attr: attr.to_string(),
arg: arg.to_string(),
});
}
(ref val, ArgValueReq::Prohibited) if val.is_some() => {
return Err(Error::ArgMustNotHaveValue {
attr: attr.to_string(),
arg: arg.to_string(),
});
}
(
ref val,
ArgValueReq::Required {
default: Some(d), ..
},
) if val.value_class() != d.value_class() && val.value_class().is_some() => {
panic!(
"Default value class {:?} does not match argument value class {:?} for \
attribute {}, argument {}",
d.value_class(),
val.value_class(),
attr.to_string(),
arg.to_string()
);
}
(val, req) => {
if val.is_none() {
if let ArgValueReq::Required {
default: Some(d), ..
} = req
{
*val = d.clone();
}
}
val
}
};
if let Some(value_class) = self.value_class() {
value_class.check(value, attr, arg)?;
}
Ok(())
}
}
#[derive(Clone)]
pub enum ValueReq {
Required,
Default(ArgValue),
Optional,
Prohibited,
}
impl ValueReq {
#[inline]
pub fn is_required(&self) -> bool {
#[allow(clippy::match_like_matches_macro)]
match self {
ValueReq::Required => true,
_ => false,
}
}
pub fn check<T>(
&self,
value: &mut T,
attr: impl ToString,
arg: impl ToString,
) -> Result<(), Error>
where
T: Clone,
ArgValue: From<T> + TryInto<T>,
Error: From<<ArgValue as TryInto<T>>::Error>,
{
let attr = attr.to_string();
let arg_value = ArgValue::from(value.clone());
match (self, value) {
(ValueReq::Required, _) if arg_value.is_none() => Err(Error::ArgValueRequired {
attr,
arg: arg.to_string(),
}),
(ValueReq::Prohibited, _) if arg_value.is_some() => Err(Error::ArgMustNotHaveValue {
attr,
arg: arg.to_string(),
}),
(ValueReq::Default(ref val), ref mut v) if arg_value.is_none() => {
**v = val.clone().try_into()?;
Ok(())
}
_ => Ok(()),
}
}
}
#[derive(Clone)]
pub enum ListReq<T> {
Single {
whitelist: Option<Vec<T>>,
default: Option<T>,
},
Many {
whitelist: Option<Vec<T>>,
required: bool,
max_no: Option<u8>,
},
Predefined {
whitelist: Option<Vec<T>>,
default: Vec<T>,
},
Deny,
}
impl<T> ListReq<T> {
pub fn maybe_one(name: T) -> Self {
ListReq::Many {
whitelist: Some(vec![name]),
required: false,
max_no: Some(1),
}
}
pub fn one_of(names: Vec<T>) -> Self {
ListReq::Single {
whitelist: Some(names),
default: None,
}
}
pub fn any_of(names: Vec<T>, required: bool) -> Self {
ListReq::Many {
whitelist: Some(names),
required,
max_no: None,
}
}
}
impl<T> ListReq<T>
where T: Clone + ToTokens
{
pub fn check(
&self,
value: &mut Vec<T>,
attr: impl ToString,
arg: impl ToString,
) -> Result<(), Error> {
match (self, value.len()) {
(ListReq::Deny, x) if x > 0 => {
return Err(Error::ArgTypeProhibited {
attr: attr.to_string(),
arg: arg.to_string(),
});
}
(ListReq::Many { required: true, .. }, 0) |
(ListReq::Single { default: None, .. }, 0) => {
return Err(Error::ArgRequired {
attr: attr.to_string(),
arg: arg.to_string(),
});
}
(
ListReq::Many {
max_no: Some(max_no),
..
},
no,
) if no > *max_no as usize => {
return Err(Error::ArgNumberExceedsMax {
attr: attr.to_string(),
type_name: arg.to_string(),
no,
max_no: *max_no,
});
}
(
ListReq::Many {
whitelist: Some(whitelist),
..
},
len,
) |
(
ListReq::Predefined {
whitelist: Some(whitelist),
..
},
len,
) |
(
ListReq::Single {
whitelist: Some(whitelist),
..
},
len,
) if len > 0 => {
for item in value {
if !whitelist.iter().any(|i| {
i.to_token_stream().to_string() == item.to_token_stream().to_string()
}) {
return Err(Error::AttributeUnknownArgument {
attr: attr.to_string(),
arg: arg.to_string(),
});
}
}
}
(
ListReq::Single {
default: Some(d), ..
},
0,
) => value.push(d.clone()),
(ListReq::Predefined { default, .. }, 0) => *value = default.clone(),
_ => {}
}
Ok(())
}
}