use crate::{
ast,
ast::MetaNameValue,
error::ExtError as _,
};
use std::collections::HashMap;
use syn::spanned::Spanned;
#[derive(Debug, Default, PartialEq, Eq)]
pub struct Config {
env: Option<Environment>,
whitelisted_attributes: WhitelistedAttributes,
}
#[derive(Debug, PartialEq, Eq)]
pub struct WhitelistedAttributes(pub HashMap<String, ()>);
impl Default for WhitelistedAttributes {
fn default() -> Self {
Self(HashMap::from([
("cfg".to_string(), ()),
("cfg_attr".to_string(), ()),
("allow".to_string(), ()),
("warn".to_string(), ()),
("deny".to_string(), ()),
("forbid".to_string(), ()),
("deprecated".to_string(), ()),
("must_use".to_string(), ()),
("doc".to_string(), ()),
("rustfmt".to_string(), ()),
]))
}
}
impl WhitelistedAttributes {
pub fn parse_arg_value(&mut self, arg: &MetaNameValue) -> Result<(), syn::Error> {
return if let ast::PathOrLit::Lit(syn::Lit::Str(attributes)) = &arg.value {
attributes.value().split(',').for_each(|attribute| {
self.0.insert(attribute.trim().to_string(), ());
});
Ok(())
} else {
Err(format_err_spanned!(
arg,
"expected a string with attributes separated by `,`",
))
}
}
pub fn filter_attr(&self, attrs: Vec<syn::Attribute>) -> Vec<syn::Attribute> {
attrs
.into_iter()
.filter(|attr| {
if let Some(ident) = attr.path.get_ident() {
self.0.contains_key(&ident.to_string())
} else {
false
}
})
.collect()
}
}
fn duplicate_config_err<F, S>(fst: F, snd: S, name: &str) -> syn::Error
where
F: Spanned,
S: Spanned,
{
format_err!(
snd.span(),
"encountered duplicate ink! `{}` configuration argument",
name,
)
.into_combine(format_err!(
fst.span(),
"first `{}` configuration argument here",
name
))
}
impl TryFrom<ast::AttributeArgs> for Config {
type Error = syn::Error;
fn try_from(args: ast::AttributeArgs) -> Result<Self, Self::Error> {
let mut env: Option<(Environment, ast::MetaNameValue)> = None;
let mut whitelisted_attributes = WhitelistedAttributes::default();
for arg in args.into_iter() {
if arg.name.is_ident("env") {
if let Some((_, ast)) = env {
return Err(duplicate_config_err(ast, arg, "env"))
}
if let ast::PathOrLit::Path(path) = &arg.value {
env = Some((Environment { path: path.clone() }, arg))
} else {
return Err(format_err_spanned!(
arg,
"expected a path for `env` ink! configuration argument",
))
}
} else if arg.name.is_ident("keep_attr") {
if let Err(err) = whitelisted_attributes.parse_arg_value(&arg) {
return Err(err)
}
} else {
return Err(format_err_spanned!(
arg,
"encountered unknown or unsupported ink! configuration argument",
))
}
}
Ok(Config {
env: env.map(|(value, _)| value),
whitelisted_attributes,
})
}
}
impl Config {
pub fn env(&self) -> syn::Path {
self.env
.as_ref()
.map(|env| &env.path)
.cloned()
.unwrap_or(Environment::default().path)
}
pub fn whitelisted_attributes(&self) -> &WhitelistedAttributes {
&self.whitelisted_attributes
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Environment {
pub path: syn::Path,
}
impl Default for Environment {
fn default() -> Self {
Self {
path: syn::parse_quote! { ::ink_env::DefaultEnvironment },
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_try_from(
input: ast::AttributeArgs,
expected: Result<Config, &'static str>,
) {
assert_eq!(
<Config as TryFrom<ast::AttributeArgs>>::try_from(input)
.map_err(|err| err.to_string()),
expected.map_err(ToString::to_string),
);
}
#[test]
fn empty_config_works() {
assert_try_from(syn::parse_quote! {}, Ok(Config::default()))
}
#[test]
fn env_works() {
assert_try_from(
syn::parse_quote! {
env = ::my::env::Types
},
Ok(Config {
env: Some(Environment {
path: syn::parse_quote! { ::my::env::Types },
}),
whitelisted_attributes: Default::default(),
}),
)
}
#[test]
fn env_invalid_value_fails() {
assert_try_from(
syn::parse_quote! { env = "invalid" },
Err("expected a path for `env` ink! configuration argument"),
);
}
#[test]
fn unknown_arg_fails() {
assert_try_from(
syn::parse_quote! { unknown = argument },
Err("encountered unknown or unsupported ink! configuration argument"),
);
}
#[test]
fn duplicate_args_fails() {
assert_try_from(
syn::parse_quote! {
env = ::my::env::Types,
env = ::my::other::env::Types,
},
Err("encountered duplicate ink! `env` configuration argument"),
);
}
#[test]
fn keep_attr_works() {
let mut attrs = WhitelistedAttributes::default();
attrs.0.insert("foo".to_string(), ());
attrs.0.insert("bar".to_string(), ());
assert_try_from(
syn::parse_quote! {
keep_attr = "foo, bar"
},
Ok(Config {
env: None,
whitelisted_attributes: attrs,
}),
)
}
#[test]
fn keep_attr_invalid_value_fails() {
assert_try_from(
syn::parse_quote! { keep_attr = 1u16 },
Err("expected a string with attributes separated by `,`"),
);
}
}