use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{
Token,
parse::{Parse, ParseStream},
parse_macro_input,
punctuated::Punctuated,
spanned::Spanned,
};
mod command;
mod item;
mod message_events;
mod partial_matches;
mod query;
mod relationship;
mod report;
mod saga;
mod setter;
mod view;
pub(crate) fn is_myko_crate() -> bool {
std::env::var("CARGO_PKG_NAME")
.map(|name| name == "myko")
.unwrap_or(false)
}
pub(crate) fn myko_path() -> syn::Path {
if is_myko_crate() {
syn::Path::from(syn::Ident::new("crate", Span::call_site()))
} else {
syn::Path::from(syn::Ident::new("myko", Span::call_site()))
}
}
pub(crate) struct DeriveCtx {
pub krate: syn::Path,
pub serde_path: proc_macro2::TokenStream,
pub serde_crate_attr: Option<String>,
pub partially_path: proc_macro2::TokenStream,
pub partially_crate_attr: Option<String>,
}
impl DeriveCtx {
pub fn new() -> Self {
let krate = myko_path();
if is_myko_crate() {
Self {
krate,
serde_path: quote!(serde),
serde_crate_attr: None,
partially_path: quote!(partially),
partially_crate_attr: None,
}
} else {
let serde_crate_str = "myko::serde".to_string();
let partially_crate_str = "myko::partially".to_string();
Self {
krate,
serde_path: quote!(myko::serde),
serde_crate_attr: Some(serde_crate_str),
partially_path: quote!(myko::partially),
partially_crate_attr: Some(partially_crate_str),
}
}
}
pub fn serde_attr(&self, rest: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
match &self.serde_crate_attr {
Some(crate_str) => {
if rest.is_empty() {
quote!(#[serde(crate = #crate_str)])
} else {
quote!(#[serde(crate = #crate_str, #rest)])
}
}
None => {
if rest.is_empty() {
quote!()
} else {
quote!(#[serde(#rest)])
}
}
}
}
}
pub(crate) fn take_manual_cache_key_attr(input_struct: &mut syn::ItemStruct) -> bool {
let mut found = take_marker_attr(input_struct, "myko_manual_cache_key");
input_struct.attrs.retain(|attr| {
let is_doc_marker = attr.path().is_ident("doc")
&& attr
.meta
.require_name_value()
.ok()
.and_then(|nv| match &nv.value {
syn::Expr::Lit(expr_lit) => match &expr_lit.lit {
syn::Lit::Str(s) => Some(s.value() == "__myko_manual_cache_key"),
_ => None,
},
_ => None,
})
.unwrap_or(false);
found |= is_doc_marker;
!is_doc_marker
});
found
}
pub(crate) fn take_non_hash_cache_key_attr(input_struct: &mut syn::ItemStruct) -> bool {
let mut found = take_marker_attr(input_struct, "myko_non_hash_cache_key");
input_struct.attrs.retain(|attr| {
let is_doc_marker = attr.path().is_ident("doc")
&& attr
.meta
.require_name_value()
.ok()
.and_then(|nv| match &nv.value {
syn::Expr::Lit(expr_lit) => match &expr_lit.lit {
syn::Lit::Str(s) => Some(s.value() == "__myko_non_hash_cache_key"),
_ => None,
},
_ => None,
})
.unwrap_or(false);
found |= is_doc_marker;
!is_doc_marker
});
found
}
fn take_marker_attr(input_struct: &mut syn::ItemStruct, attr_name: &str) -> bool {
let mut found = false;
input_struct.attrs.retain(|attr| {
let matches = attr.path().is_ident(attr_name);
found |= matches;
!matches
});
found
}
#[proc_macro_attribute]
pub fn myko_manual_cache_key(_attr: TokenStream, input: TokenStream) -> TokenStream {
let item = parse_macro_input!(input as syn::ItemStruct);
quote! {
#[doc = "__myko_manual_cache_key"]
#item
}
.into()
}
#[proc_macro_attribute]
pub fn myko_non_hash_cache_key(_attr: TokenStream, input: TokenStream) -> TokenStream {
let item = parse_macro_input!(input as syn::ItemStruct);
quote! {
#[doc = "__myko_non_hash_cache_key"]
#item
}
.into()
}
#[proc_macro_derive(PartialMatches)]
pub fn derive_partial_matches(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as syn::DeriveInput);
partial_matches::derive_partial_matches_impl(input).into()
}
#[proc_macro_attribute]
pub fn myko_item(attr: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(attr as item::ItemArgs);
let input = parse_macro_input!(input as syn::ItemStruct);
item::myko_item_impl(args, input).into()
}
#[proc_macro_attribute]
pub fn myko_query(attr: TokenStream, input: TokenStream) -> TokenStream {
let query_item_type = parse_macro_input!(attr as syn::Path);
let input = parse_macro_input!(input as syn::ItemStruct);
query::myko_query_impl(query_item_type, input).into()
}
#[proc_macro_attribute]
pub fn myko_view(attr: TokenStream, input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as syn::ItemStruct);
if attr.is_empty() {
return syn::Error::new(
input.ident.span(),
"#[myko_view] requires an item type: #[myko_view(ViewItemType)]",
)
.to_compile_error()
.into();
}
let args = parse_macro_input!(attr as view::ViewArgs);
view::myko_view_impl(args, input).into()
}
#[proc_macro_attribute]
pub fn myko_view_item(_attr: TokenStream, input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as syn::ItemStruct);
view::myko_view_item_impl(input).into()
}
#[proc_macro_attribute]
pub fn myko_report(attr: TokenStream, input: TokenStream) -> TokenStream {
let report_output_type = parse_macro_input!(attr as syn::Path);
let input = parse_macro_input!(input as syn::ItemStruct);
report::myko_report_impl(report_output_type, input).into()
}
#[proc_macro_attribute]
pub fn myko_command(attr: TokenStream, input: TokenStream) -> TokenStream {
let options = if attr.is_empty() {
command::CommandOptions {
result_type: None,
custom_serialize: false,
}
} else {
parse_macro_input!(attr as CommandArgs).into()
};
let input = parse_macro_input!(input as syn::ItemStruct);
command::myko_command_impl(options, input).into()
}
struct CommandArgs {
result_type: Option<syn::Path>,
custom_serialize: bool,
}
impl From<CommandArgs> for command::CommandOptions {
fn from(value: CommandArgs) -> Self {
Self {
result_type: value.result_type,
custom_serialize: value.custom_serialize,
}
}
}
impl Parse for CommandArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let args = Punctuated::<syn::Path, Token![,]>::parse_terminated(input)?;
let mut result_type = None;
let mut custom_serialize = false;
for path in args {
if path.is_ident("custom_serialize") {
if custom_serialize {
return Err(syn::Error::new(
path.span(),
"duplicate custom_serialize flag",
));
}
custom_serialize = true;
continue;
}
if result_type.is_some() {
return Err(syn::Error::new(
path.span(),
"expected at most one result type",
));
}
result_type = Some(path);
}
Ok(Self {
result_type,
custom_serialize,
})
}
}
#[proc_macro_derive(MessageEvents)]
pub fn derive_message_events(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as syn::DeriveInput);
message_events::derive_message_events_impl(input).into()
}
#[proc_macro_attribute]
pub fn myko_saga(attr: TokenStream, input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as syn::ItemStruct);
saga::myko_saga_impl(attr.into(), input).into()
}
#[proc_macro_attribute]
pub fn myko_report_output(_attr: TokenStream, input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as syn::ItemStruct);
let name = &input.ident;
let ctx = DeriveCtx::new();
let krate = &ctx.krate;
let serde_path = &ctx.serde_path;
let serde_rename_attr = ctx.serde_attr(quote!(rename_all = "camelCase"));
let expanded = quote! {
#[derive(Debug, Clone, PartialEq, #serde_path::Serialize, #serde_path::Deserialize, #krate::TS)]
#serde_rename_attr
#input
#krate::register_ts_export!(#name);
};
expanded.into()
}