use crate::ast::Protocol;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use std::collections::HashMap;
fn is_valid_field_ident(candidate: &str) -> bool {
if candidate.is_empty() {
return false;
}
let mut chars = candidate.chars();
let Some(first) = chars.next() else {
return false;
};
if !(first == '_' || first.is_ascii_alphabetic()) {
return false;
}
chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
}
pub fn generate_annotation_docs(annotations: &HashMap<String, String>) -> TokenStream {
if annotations.is_empty() {
return quote! {};
}
let mut entries: Vec<_> = annotations.iter().collect();
entries.sort_by(|(key_a, _), (key_b, _)| key_a.cmp(key_b));
let doc_lines: Vec<TokenStream> = entries
.into_iter()
.map(|(key, value)| {
if value == "true" {
quote! { #[doc = concat!("@", #key)] }
} else {
quote! { #[doc = concat!("@", #key, ": ", #value)] }
}
})
.collect();
quote! { #(#doc_lines)* }
}
pub fn generate_annotation_metadata(
name: &str,
annotations: &HashMap<String, String>,
) -> TokenStream {
if annotations.is_empty() {
return quote! {};
}
let mut supported: Vec<(&str, &String)> = annotations
.iter()
.filter_map(|(key, value)| {
let normalized = key.to_lowercase();
if is_valid_field_ident(&normalized) {
Some((key.as_str(), value))
} else {
None
}
})
.collect();
supported.sort_by(|(key_a, _), (key_b, _)| key_a.cmp(key_b));
if supported.is_empty() {
return quote! {};
}
let metadata_name = format_ident!("{}Annotations", name);
let annotation_fields: Vec<TokenStream> = supported
.iter()
.map(|(key, _)| {
let field_name = format_ident!("{}", key.to_lowercase());
quote! {
pub #field_name: &'static str,
}
})
.collect();
let annotation_values: Vec<TokenStream> = supported
.iter()
.map(|(key, value)| {
let field_name = format_ident!("{}", key.to_lowercase());
quote! {
#field_name: #value,
}
})
.collect();
quote! {
#[derive(Debug, Clone, Copy)]
pub struct #metadata_name {
#(#annotation_fields)*
}
impl #metadata_name {
pub const INSTANCE: Self = Self {
#(#annotation_values)*
};
}
}
}
#[allow(dead_code)] pub(crate) fn generate_annotation_attributes(annotations: &crate::ast::Annotations) -> TokenStream {
let mut attrs = Vec::new();
if let Some(priority) = annotations.priority() {
let p = priority.to_string();
attrs.push(quote! { #[priority = #p] });
}
if let Some(timeout) = annotations.custom("timeout") {
attrs.push(quote! { #[timeout = #timeout] });
}
if annotations.custom("async").is_some_and(|v| v == "true") {
attrs.push(quote! { #[async_trait] });
}
if let Some(retry_count) = annotations
.iter()
.find_map(|annotation| annotation.retry_config().map(|(attempts, _)| attempts))
{
attrs.push(quote! { #[retry = #retry_count] });
}
quote! { #(#attrs)* }
}
pub fn generate_runtime_annotation_access(name: &str, protocol: &Protocol) -> TokenStream {
let fn_name = format_ident!("get_{}_annotations", name.to_lowercase());
let all_annotations = protocol.get_annotations();
if all_annotations.is_empty() {
return quote! {};
}
let annotation_map = all_annotations.dsl_map();
let mut annotation_entries: Vec<_> = annotation_map.iter().collect();
annotation_entries.sort_by(|(key_a, _), (key_b, _)| key_a.cmp(key_b));
let annotation_inserts: Vec<TokenStream> = annotation_entries
.into_iter()
.map(|(key, value)| {
quote! { map.insert(#key.to_string(), #value.to_string()); }
})
.collect();
quote! {
pub fn #fn_name() -> std::collections::HashMap<String, String> {
let mut map = std::collections::HashMap::new();
#(#annotation_inserts)*
map
}
}
}