use std::str;
use std::str::FromStr;
use std::i32;
use std::ops::RangeBounds;
use std::fmt::Debug;
use syn::{ Attribute, Meta, MetaList, NestedMeta, MetaNameValue, Lit };
use syn::synom::Synom;
use crate::{
attr::{ ExtMeta, NestedExtMeta, PathExt },
error::{ Error, Result },
};
pub trait RangeBoundsExt<T>: RangeBounds<T> {
fn contains_value<U>(&self, value: &U) -> bool
where T: PartialOrd<U>,
U: PartialOrd<T> + ?Sized,
{
use std::ops::Bound::*;
(match self.start_bound() {
Included(ref start) => *start <= value,
Excluded(ref start) => *start < value,
Unbounded => true,
})
&&
(match self.end_bound() {
Included(ref end) => value <= *end,
Excluded(ref end) => value < *end,
Unbounded => true,
})
}
}
impl<T, R: RangeBounds<T>> RangeBoundsExt<T> for R {}
fn meta(attrs: &[Attribute], name: &str, key: &str) -> Option<Meta> {
attrs.iter().find_map(|attr| {
let meta_list = match attr.interpret_meta()? {
Meta::List(list) => {
if list.ident == name {
list
} else {
return None;
}
},
_ => return None,
};
meta_list.nested.into_iter().find_map(|nested_meta| {
let meta = match nested_meta {
NestedMeta::Meta(meta) => meta,
_ => return None,
};
let ident = match meta.clone() {
Meta::Word(ident) => ident,
Meta::List(list) => list.ident,
Meta::NameValue(name_value) => name_value.ident,
};
if ident == key {
Some(meta)
} else {
None
}
})
})
}
fn name_value(attrs: &[Attribute], name: &str, key: &str) -> Result<Option<MetaNameValue>> {
match meta(attrs, name, key) {
Some(Meta::NameValue(name_value)) => Ok(Some(name_value)),
Some(_) => {
err_fmt!("attribute must have form `#[{}({} = \"...\")]`", name, key)
}
None => Ok(None),
}
}
fn has_meta_word(attrs: &[Attribute], name: &str, key: &str) -> Result<bool> {
match meta(attrs, name, key) {
Some(Meta::Word(_)) => Ok(true),
Some(_) => {
err_fmt!("attribute must have form `#[{}({})]`", name, key)
}
None => Ok(false),
}
}
pub fn serde_name_value(attrs: &[Attribute], key: &str) -> Result<Option<MetaNameValue>> {
name_value(attrs, "serde", key)
}
pub fn has_serde_word(attrs: &[Attribute], key: &str) -> Result<bool> {
has_meta_word(attrs, "serde", key)
}
pub fn value_as_bool(key: &str, lit: &Lit) -> Result<bool> {
match *lit {
Lit::Bool(ref lit) => Ok(lit.value),
_ => err_fmt!("value for key `{}` must be a bool", key)
}
}
pub fn value_as_str(nv: &MetaNameValue) -> Result<String> {
match nv.lit {
Lit::Str(ref string) => Ok(string.value()),
Lit::ByteStr(ref string) => {
String::from_utf8(string.value()).map_err(Into::into)
}
_ => err_fmt!("value for key `{}` must be a valid UTF-8 string",
nv.ident.to_string())
}
}
pub fn lit_value_as_str(key: &str, lit: &Lit) -> Result<String> {
match *lit {
Lit::Str(ref string) => Ok(string.value()),
Lit::ByteStr(ref string) => {
String::from_utf8(string.value()).map_err(Into::into)
}
_ => err_fmt!("value for key `{}` must be a valid UTF-8 string", key)
}
}
#[allow(clippy::cast_possible_truncation)]
pub fn value_as_i32<R>(key: &str, lit: &Lit, range: R) -> Result<i32>
where R: Debug + RangeBoundsExt<i32>
{
let value = match *lit {
Lit::Int(ref lit) => {
let v = lit.value();
if v <= i32::MAX as u64 {
v as i32
} else {
err_fmt!("integer value `{}` for key `{}` overflows i32", v, key)?
}
}
Lit::Str(ref lit) => lit.value().parse()?,
Lit::ByteStr(ref lit) => str::from_utf8(&lit.value())?.parse()?,
_ => return err_fmt!("value for key `{}` must be an i32", key)
};
if range.contains_value(&value) {
Ok(value)
} else {
err_fmt!("value `{}` for key `{}` exceeds range {:?}",
value, key, range)
}
}
#[allow(clippy::cast_precision_loss)]
pub fn value_as_f64<R>(key: &str, lit: &Lit, range: R) -> Result<f64>
where R: Debug + RangeBoundsExt<f64>
{
let value = match *lit {
Lit::Float(ref lit) => lit.value(),
Lit::Int(ref lit) => lit.value() as f64,
Lit::Str(ref lit) => lit.value().parse()?,
Lit::ByteStr(ref lit) => str::from_utf8(&lit.value())?.parse()?,
_ => return err_fmt!("value for key `{}` must be an f64", key)
};
if range.contains_value(&value) {
Ok(value)
} else {
err_fmt!("value `{}` for key `{}` exceeds range {:?}",
value, key, range)
}
}
pub fn list_into_names_and_values<T, I>(outer_name: &str, list: I) -> Result<Vec<(String, T)>>
where T: FromStr,
T::Err: Into<Error>,
I: IntoIterator<Item = NestedExtMeta>,
{
list.into_iter()
.map(|nested| match nested {
NestedExtMeta::Meta(ExtMeta::KeyValue(path, _, literal)) => {
let val_str = match literal {
Lit::Str(ref s) => s.value(),
Lit::ByteStr(ref s) => String::from_utf8(s.value())?,
_ => return err_fmt!(
"value for key `{}` must be a valid UTF-8 string",
path.colon_sep_str()
)
};
val_str
.parse()
.map_err(Into::into)
.map(|value| (path.dot_sep_str(), value))
}
_ => err_fmt!(
"attribute `{}` must contain key-value pairs only, not {:#?}",
outer_name,
nested
)
})
.collect()
}
pub fn literal_value_for_name<T: Synom>(attrs: &[Attribute], name: &str) -> Result<Option<T>> {
attrs
.iter()
.find_map(|attr| match attr.interpret_meta()? {
Meta::NameValue(nv) => {
if nv.ident == name {
value_as_str(&nv)
.and_then(|s| syn::parse_str(&s).map_err(Into::into))
.into()
} else {
None
}
}
Meta::Word(ident) | Meta::List(MetaList { ident, .. }) => {
if ident == name {
Some(
err_fmt!("attribute must have form `#[{} = ...]`", name)
)
} else {
None
}
}
})
.map_or(Ok(None), |result| result.map(Some))
}