use proc_macro2::TokenStream;
use std::fmt::Display;
use std::sync::OnceLock;
use std::sync::atomic::{AtomicUsize, Ordering};
#[derive(Debug, Clone)]
enum DebugMode {
Disabled,
All,
Patterns(Vec<String>),
}
static DEBUG_MODE: OnceLock<DebugMode> = OnceLock::new();
fn get_debug_mode() -> &'static DebugMode {
DEBUG_MODE.get_or_init(parse_debug_env)
}
fn parse_debug_env() -> DebugMode {
match std::env::var("PROTTO_DEBUG") {
Ok(env_debug) => match env_debug.as_str() {
"1" | "true" | "all" => DebugMode::All,
"0" | "false" | "none" | "" => DebugMode::Disabled,
_ => {
let patterns = env_debug
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
DebugMode::Patterns(patterns)
}
},
Err(_) => DebugMode::Disabled,
}
}
pub fn should_output_debug(name: impl Display, _field_name: impl Display) -> bool {
let name = name.to_string();
match get_debug_mode() {
DebugMode::Disabled => false,
DebugMode::All => true,
DebugMode::Patterns(patterns) => patterns
.iter()
.any(|pattern| matches_debug_pattern(pattern, &name)),
}
}
fn matches_debug_pattern(pattern: impl AsRef<str>, name: impl AsRef<str>) -> bool {
let pattern = pattern.as_ref();
let name = name.as_ref();
if pattern == "all" {
return true;
}
if pattern == name {
return true;
}
if pattern.contains('*') {
if pattern.starts_with('*') && pattern.ends_with('*') {
let middle = &pattern[1..pattern.len() - 1];
return name.contains(middle);
} else if let Some(suffix) = pattern.strip_prefix('*') {
return name.ends_with(suffix);
} else if let Some(prefix) = pattern.strip_suffix('*') {
return name.starts_with(prefix);
}
}
false
}
#[allow(unused)]
pub fn print_debug_help() {
eprintln!("Protto Debug Options:");
eprintln!(" Environment variable PROTTO_DEBUG supports:");
eprintln!(" all # Debug all structs");
eprintln!(" Request # Debug exact struct name");
eprintln!(" Request,Response # Debug multiple structs (comma-separated)");
eprintln!(" Track* # Debug structs starting with 'Track'");
eprintln!(" *Request # Debug structs ending with 'Request'");
eprintln!(" *Track* # Debug structs containing 'Track'");
eprintln!(" Request,Track*,*Response # Combine patterns");
eprintln!(" 0 | false | none # Disable all debug");
eprintln!();
eprintln!(" Usage during proc macro expansion:");
eprintln!(" PROTTO_DEBUG=Request cargo build");
eprintln!(" PROTTO_DEBUG=Track* cargo test");
}
static CALL_DEPTH: AtomicUsize = AtomicUsize::new(0);
pub struct CallStackDebug {
function_name: String,
context: String,
depth: usize,
enabled: bool,
}
impl CallStackDebug {
pub fn new(
code_module: &str,
function_name: &str,
struct_name: impl Display,
field_name: impl Display,
) -> Self {
let function_name = format!("{code_module}::{function_name}");
let struct_name = struct_name.to_string();
let field_name = field_name.to_string();
let context = format!("{}.{}", struct_name, field_name);
let enabled = should_output_debug(&struct_name, &field_name);
let depth = CALL_DEPTH.fetch_add(1, Ordering::SeqCst);
if enabled {
let indent = " ".repeat(depth);
eprintln!("{}┌─ ENTER: {} [{}]", indent, function_name, context);
}
Self {
function_name,
context,
depth,
enabled,
}
}
#[allow(unused)]
pub fn with_struct_field(
code_module: &str,
function_name: &str,
struct_name: impl Display,
field: &syn::Field,
) -> Self {
let field_name = field
.ident
.as_ref()
.map(|f| f.to_string())
.unwrap_or_default();
Self::new(code_module, function_name, struct_name, field_name)
}
pub fn with_context(
code_module: &str,
function_name: &str,
struct_name: impl Display,
field_name: impl Display,
extra_context: &[(&str, &str)],
) -> Self {
let tracker = Self::new(code_module, function_name, struct_name, field_name);
if tracker.enabled && !extra_context.is_empty() {
let indent = " ".repeat(tracker.depth);
for (key, value) in extra_context {
eprintln!("{}│ 📊 {}: {}", indent, key, value);
}
}
tracker
}
#[allow(unused)]
pub fn field_analysis(&self, phase: &str, data: &[(&str, &str)]) {
if self.enabled {
let indent = " ".repeat(self.depth);
eprintln!("{}│ 🔍 {}", indent, phase);
for (key, value) in data {
eprintln!("{}│ 📋 {}: {}", indent, key, value);
}
}
}
#[allow(unused)]
pub fn type_analysis(&self, rust_type: &str, proto_info: &[(&str, &str)]) {
if self.enabled {
let indent = " ".repeat(self.depth);
eprintln!("{}│ 🎯 TYPE ANALYSIS", indent);
eprintln!("{}│ 🦀 Rust: {}", indent, rust_type);
for (key, value) in proto_info {
eprintln!("{}│ 📦 {}: {}", indent, key, value);
}
}
}
pub fn generated_code(
&self,
code: &TokenStream,
struct_name: impl Display,
field_name: impl Display,
context: &str,
info: &[(&str, &str)],
) {
if self.enabled {
let indent = " ".repeat(self.depth);
eprintln!(
"\n{indent}=== 🛠️ GENERATED CODE: {struct_name}.{field_name} - {context} ==="
);
for (key, value) in info {
eprintln!("{}│ 📌 {}: {}", indent, key, value);
}
let code_str = code.to_string();
let formatted_code = format_rust_code(&code_str);
eprintln!("{}│ 📝 Generated code:", indent);
for (i, line) in formatted_code.lines().enumerate() {
eprintln!(" {:3} | {}", i + 1, line);
}
eprintln!("{indent}=== END GENERATED CODE ===\n");
}
}
pub fn checkpoint(&self, message: impl AsRef<str>) {
if self.enabled {
let indent = " ".repeat(self.depth);
eprintln!("{}│ ✓ {}", indent, message.as_ref());
}
}
pub fn checkpoint_data(&self, message: impl AsRef<str>, data: &[(&str, &str)]) {
if self.enabled {
let indent = " ".repeat(self.depth);
eprintln!("{}│ ✓ {}", indent, message.as_ref());
for (key, value) in data {
eprintln!("{}│ {}: {}", indent, key, value);
}
}
}
pub fn error(&self, message: impl AsRef<str>) {
if self.enabled {
let indent = " ".repeat(self.depth);
eprintln!("{}│ ❌ ERROR: {}", indent, message.as_ref());
}
}
#[allow(unused)]
pub fn error_data(&self, message: impl AsRef<str>, data: &[(&str, &str)]) {
if self.enabled {
let indent = " ".repeat(self.depth);
eprintln!("{}│ ⌘ ERROR: {}", indent, message.as_ref());
for (key, value) in data {
eprintln!("{}│ {}: {}", indent, key, value);
}
}
}
pub fn decision(&self, condition: &str, choice: &str) -> &Self {
if self.enabled {
let indent = " ".repeat(self.depth);
eprintln!("{}│ 🔀 IF {} THEN {}", indent, condition, choice);
}
self
}
#[allow(unused)]
pub fn conditional_exprs(
&self,
label: &str,
optionality: impl Display,
exprs: &[(&str, &TokenStream)],
) {
if self.enabled {
let indent = " ".repeat(self.depth);
eprintln!("{}│ 🔀 CONDITIONAL: {} ({})", indent, label, optionality);
for (name, expr) in exprs {
let formatted_code = format_rust_code(expr.to_string());
eprintln!("{}│ 📝 Generated code:", indent);
eprintln!("{}│ {} -> {{", indent, name);
for (i, line) in formatted_code.lines().enumerate() {
eprintln!(" {:3} | {}", i + 1, line);
}
eprintln!("{}│ }}", indent);
}
}
}
#[allow(unused)]
pub fn type_mismatch(
&self,
expected_rust_type: &str,
actual_proto_type: &str,
conversion_attempt: &str,
suggested_fixes: &[&str],
) -> &Self {
if self.enabled {
let indent = " ".repeat(self.depth);
eprintln!("{}│ 🎯 TYPE MISMATCH", indent);
eprintln!("{}│ 🦀 Expected: {}", indent, expected_rust_type);
eprintln!("{}│ 📦 Actual: {}", indent, actual_proto_type);
eprintln!("{}│ 🔄 Attempted: {}", indent, conversion_attempt);
if !suggested_fixes.is_empty() {
eprintln!("{}│ 💡 Fixes: {}", indent, suggested_fixes.join(", "));
}
}
self
}
#[allow(unused)]
pub fn type_resolution(
&self,
rust_type: &str,
proto_field_name: &str,
proto_type_info: &str,
metadata_result: Option<bool>,
) -> &Self {
if self.enabled {
let indent = " ".repeat(self.depth);
eprintln!("{}│ 🎯 TYPE RESOLUTION", indent);
eprintln!("{}│ 🦀 Rust: {}", indent, rust_type);
eprintln!("{}│ 📦 Proto field: {}", indent, proto_field_name);
eprintln!("{}│ 🔧 Type info: {}", indent, proto_type_info);
eprintln!("{}│ 📋 Metadata: {:?}", indent, metadata_result);
}
self
}
#[allow(unused)]
pub fn metadata_lookup(
&self,
proto_message: &str,
proto_field: &str,
metadata_result: Option<bool>,
strategy: &str,
) -> &Self {
if self.enabled {
let indent = " ".repeat(self.depth);
eprintln!("{}│ 📋 METADATA LOOKUP", indent);
eprintln!(
"{}│ 🏷️ Proto: {}.{}",
indent, proto_message, proto_field
);
eprintln!("{}│ ✅ Result: {:?}", indent, metadata_result);
eprintln!("{}│ 🚩 Strategy: {}", indent, strategy);
}
self
}
#[allow(unused)]
pub fn error_condition(
&self,
error_type: &str,
details: impl AsRef<str>,
suggested_fix: Option<&str>,
) -> &Self {
if self.enabled {
let indent = " ".repeat(self.depth);
eprintln!("{}│ ❌ ERROR: {}", indent, error_type);
eprintln!("{}│ 📝 Details: {}", indent, details.as_ref());
if let Some(fix) = suggested_fix {
eprintln!("{}│ 💡 Fix: {}", indent, fix);
}
}
self
}
#[allow(unused)]
pub fn struct_generation(&self, phase: &str, info: &[(&str, &str)]) -> &Self {
if self.enabled {
let indent = " ".repeat(self.depth);
eprintln!("{}│ 🏢 STRUCT: {}", indent, phase);
for (key, value) in info {
eprintln!("{}│ 🔧 {}: {}", indent, key, value);
}
}
self
}
}
impl Drop for CallStackDebug {
fn drop(&mut self) {
CALL_DEPTH.fetch_sub(1, Ordering::SeqCst);
if self.enabled {
let indent = " ".repeat(self.depth);
eprintln!(
"{}└─ EXIT: {} [{}]",
indent, self.function_name, self.context
);
}
}
}
#[allow(unused)]
pub fn debug_struct_conversion_generation(
struct_name: impl Display,
phase: &str,
from_proto_impl: &TokenStream,
from_my_impl: &TokenStream,
final_impl: &TokenStream,
additional_info: &[(&str, String)],
) {
let name = struct_name.to_string();
if !should_output_debug(&name, "") {
return;
}
eprintln!("\n=== 🏢 STRUCT: {} - {} ===", name, phase);
for (key, value) in additional_info {
eprintln!(" 📊 {}: {}", key, value);
}
eprintln!(
" FROM_PROTO: {}",
format_rust_code(from_proto_impl.to_string())
);
eprintln!(" FROM_MY: {}", format_rust_code(from_my_impl.to_string()));
eprintln!(
" FINAL_IMPL:\n{}",
format_rust_code(final_impl.to_string())
);
eprintln!("=== END STRUCT ===\n");
}
pub fn format_rust_code(code: impl AsRef<str>) -> String {
let code = code.as_ref();
let mut result = String::new();
let mut indent_level = 0;
let mut chars = code.chars().peekable();
let mut current_line = String::new();
while let Some(ch) = chars.next() {
match ch {
'{' => {
current_line.push(ch);
result.push_str(current_line.trim());
result.push('\n');
indent_level += 1;
current_line.clear();
}
'}' => {
if !current_line.trim().is_empty() {
result.push_str(&format!(
"{}{}\n",
" ".repeat(indent_level),
current_line.trim()
));
current_line.clear();
}
indent_level = indent_level.saturating_sub(1);
result.push_str(&format!("{}}}", " ".repeat(indent_level)));
if chars.peek().is_some() {
result.push('\n');
}
}
';' => {
current_line.push(ch);
result.push_str(&format!(
"{}{}\n",
" ".repeat(indent_level),
current_line.trim()
));
current_line.clear();
}
',' => {
current_line.push(ch);
if !is_in_method_chain(¤t_line) {
result.push_str(&format!(
"{}{}\n",
" ".repeat(indent_level),
current_line.trim()
));
current_line.clear();
} else {
current_line.push(' ');
}
}
'.' => {
current_line.push(ch);
if let Some(next_ch) = chars.peek()
&& next_ch.is_alphabetic()
{
if current_line.trim().len() > 60 {
result.push_str(&format!(
"{}{}\n",
" ".repeat(indent_level),
current_line.trim()
));
current_line.clear();
current_line.push_str(&format!("{}.", " ".repeat(indent_level + 1)));
}
}
}
'?' => {
current_line.push(ch);
if let Some(&next_ch) = chars.peek() {
if next_ch == '.' {
current_line.push(' ');
} else if next_ch.is_whitespace() || next_ch == ';' {
current_line.push(' ');
}
}
}
ch if ch.is_whitespace() => {
if !current_line.ends_with(' ') && !current_line.is_empty() {
current_line.push(' ');
}
}
_ => {
current_line.push(ch);
}
}
}
if !current_line.trim().is_empty() {
result.push_str(&format!(
"{}{}",
" ".repeat(indent_level),
current_line.trim()
));
}
cleanup_formatting(&result)
}
fn is_in_method_chain(line: &str) -> bool {
let trimmed = line.trim();
let open_parens = trimmed.matches('(').count();
let close_parens = trimmed.matches(')').count();
let open_angles = trimmed.matches('<').count();
let close_angles = trimmed.matches('>').count();
open_parens > close_parens || open_angles > close_angles
}
fn cleanup_formatting(code: &str) -> String {
code.lines()
.map(|line| line.trim_end()) .filter(|line| !line.trim().is_empty() || code.lines().count() < 5) .collect::<Vec<_>>()
.join("\n")
.replace("\n\n\n", "\n\n") .trim()
.to_string()
}