use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::{Token, TypePath}; use crate::macro_flag::MacroFlag; use crate::util::iterator_ext::IteratorExt; pub const DECLARE_INTERFACE_FLAGS: &[&str] = &["threadsafe_sharable"]; pub struct DeclareInterfaceArgs { pub implementation: TypePath, pub interface: TypePath, pub flags: Punctuated, } impl Parse for DeclareInterfaceArgs { fn parse(input: ParseStream) -> syn::Result { let implementation: TypePath = input.parse()?; input.parse::]>()?; let interface: TypePath = input.parse()?; let flags = if input.peek(Token![,]) { input.parse::()?; let flags = Punctuated::::parse_terminated(input)?; for flag in &flags { let flag_name = flag.name().to_string(); if !DECLARE_INTERFACE_FLAGS.contains(&flag_name.as_str()) { return Err(input.error(format!( "Unknown flag '{flag_name}'. Expected one of [ {} ]", DECLARE_INTERFACE_FLAGS.join(",") ))); } } if let Some((dupe_flag, _)) = flags.iter().find_duplicate() { return Err(input.error(format!("Duplicate flag '{}'", dupe_flag.name()))); } flags } else { Punctuated::new() }; Ok(Self { implementation, interface, flags, }) } } #[cfg(test)] mod tests { use proc_macro2::Span; use quote::{format_ident, quote}; use syn::{parse2, Lit, LitBool}; use super::*; use crate::macro_flag::MacroFlagValue; use crate::test_utils; #[test] fn can_parse_with_no_flags() { let input_args = quote! { Foo -> IFoo }; let decl_interface_args = parse2::(input_args).unwrap(); assert_eq!( decl_interface_args.implementation, TypePath { qself: None, path: test_utils::create_path(&[test_utils::create_path_segment( format_ident!("Foo"), &[] )]) } ); assert_eq!( decl_interface_args.interface, TypePath { qself: None, path: test_utils::create_path(&[test_utils::create_path_segment( format_ident!("IFoo"), &[] )]) } ); assert!(decl_interface_args.flags.is_empty()); } #[test] fn can_parse_with_flags() { let input_args = quote! { Foobar -> IFoobar, threadsafe_sharable = true }; let decl_interface_args = parse2::(input_args).unwrap(); assert_eq!( decl_interface_args.implementation, TypePath { qself: None, path: test_utils::create_path(&[test_utils::create_path_segment( format_ident!("Foobar"), &[] )]) } ); assert_eq!( decl_interface_args.interface, TypePath { qself: None, path: test_utils::create_path(&[test_utils::create_path_segment( format_ident!("IFoobar"), &[] )]) } ); assert_eq!( decl_interface_args.flags, Punctuated::from_iter(vec![MacroFlag { name: format_ident!("threadsafe_sharable"), value: MacroFlagValue::Literal(Lit::Bool(LitBool::new( true, Span::call_site() ))) }]) ); } #[test] fn cannot_parse_with_invalid_flag() { let input_args = quote! { Foobar -> IFoobar, xyz = false, threadsafe_sharable = true }; assert!(parse2::(input_args).is_err()); } #[test] fn cannot_parse_with_duplicate_flag() { assert!( // Formatting is weird without this comment parse2::(quote! { Foobar -> IFoobar, threadsafe_sharable = true, threadsafe_sharable = true }) .is_err() ); assert!( // Formatting is weird without this comment parse2::(quote! { Foobar -> IFoobar, threadsafe_sharable = true, threadsafe_sharable = false }) .is_err() ); } }