use proc_macro2::TokenStream;
use quote::quote;
use syn::spanned::Spanned;
use crate::diagnostic::{MacroError, MacroErrorCode, MacroResult};
pub fn page_macro(input: TokenStream) -> MacroResult {
let expr: syn::Expr =
syn::parse2(input).map_err(|e| MacroError::from_syn(MacroErrorCode::ArcM001, e))?;
let syn::Expr::Struct(expr_struct) = &expr else {
return Err(MacroError::new(
MacroErrorCode::ArcM002,
expr.span(),
"page! requires a struct constructor expression, \
e.g. `page!(ShowUserPage { ... })`",
));
};
let type_path = &expr_struct.path;
Ok(quote! {{
const fn _assert_client_data<T: ::arcature::inertia::ClientData>() {}
_assert_client_data::<#type_path>();
::arcature::Page(#expr)
}})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wraps_the_constructor_in_a_page() {
let s = page_macro(quote! { ShowUserPage { user: user_resource } })
.unwrap()
.to_string();
assert!(s.contains(":: arcature :: Page"), "got: {s}");
assert!(s.contains("ShowUserPage"), "got: {s}");
assert!(s.contains("user_resource"), "got: {s}");
}
#[test]
fn asserts_the_type_implements_client_data() {
let s = page_macro(quote! { HomePage { title: t } })
.unwrap()
.to_string();
assert!(
s.contains("_assert_client_data :: < HomePage >"),
"got: {s}"
);
assert!(
s.contains(":: arcature :: inertia :: ClientData"),
"got: {s}"
);
}
#[test]
fn accepts_a_qualified_type_path() {
let s = page_macro(quote! { pages::HomePage { title: t } })
.unwrap()
.to_string();
assert!(
s.contains("_assert_client_data :: < pages :: HomePage >"),
"got: {s}"
);
}
#[test]
fn accepts_an_empty_struct_literal() {
let s = page_macro(quote! { BlankPage {} }).unwrap().to_string();
assert!(
s.contains("_assert_client_data :: < BlankPage >"),
"got: {s}"
);
}
#[test]
fn rejects_a_non_struct_expression() {
let err = page_macro(quote! { make_page() }).unwrap_err();
assert_eq!(err.code(), MacroErrorCode::ArcM002);
}
#[test]
fn rejects_input_that_is_not_an_expression() {
let err = page_macro(quote! { pub struct Foo {} }).unwrap_err();
assert_eq!(err.code(), MacroErrorCode::ArcM001);
}
}