relay_hook_transpiler/
debug.rs

1use std::sync::{Arc, Mutex};
2use std::fmt;
3
4/// Debug verbosity levels
5#[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))]
6#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
7pub enum DebugLevel {
8    Off = 0,
9    Error = 1,
10    Warn = 2,
11    Info = 3,
12    Trace = 4,
13    Verbose = 5,
14}
15
16impl Default for DebugLevel {
17    fn default() -> Self {
18        #[cfg(feature = "debug")]
19        {
20            DebugLevel::Trace
21        }
22        #[cfg(not(feature = "debug"))]
23        {
24            DebugLevel::Off
25        }
26    }
27}
28
29impl fmt::Display for DebugLevel {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        match self {
32            Self::Off => write!(f, "off"),
33            Self::Error => write!(f, "error"),
34            Self::Warn => write!(f, "warn"),
35            Self::Info => write!(f, "info"),
36            Self::Trace => write!(f, "trace"),
37            Self::Verbose => write!(f, "verbose"),
38        }
39    }
40}
41
42impl std::str::FromStr for DebugLevel {
43    type Err = String;
44
45    fn from_str(s: &str) -> Result<Self, Self::Err> {
46        match s.to_lowercase().as_str() {
47            "off" | "0" => Ok(DebugLevel::Off),
48            "error" | "1" => Ok(DebugLevel::Error),
49            "warn" | "warning" | "2" => Ok(DebugLevel::Warn),
50            "info" | "3" => Ok(DebugLevel::Info),
51            "trace" | "4" => Ok(DebugLevel::Trace),
52            "verbose" | "5" => Ok(DebugLevel::Verbose),
53            _ => Err(format!("Invalid debug level: {}", s)),
54        }
55    }
56}
57
58/// A single debug log entry
59#[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))]
60#[derive(Debug, Clone, PartialEq)]
61pub struct DebugEntry {
62    pub level: DebugLevel,
63    pub message: String,
64    pub line: Option<usize>,
65    pub column: Option<usize>,
66}
67
68/// Debug context for a transpilation session
69pub struct DebugContext {
70    level: DebugLevel,
71    logs: Arc<Mutex<Vec<DebugEntry>>>,
72}
73
74impl DebugContext {
75    pub fn new(level: DebugLevel) -> Self {
76        Self {
77            level,
78            logs: Arc::new(Mutex::new(Vec::new())),
79        }
80    }
81
82    pub fn set_level(&mut self, level: DebugLevel) {
83        self.level = level;
84    }
85
86    pub fn log(&self, entry_level: DebugLevel, message: impl Into<String>) {
87        self.log_at(entry_level, message, None, None);
88    }
89
90    pub fn log_at(&self, entry_level: DebugLevel, message: impl Into<String>, line: Option<usize>, column: Option<usize>) {
91        if entry_level <= self.level {
92            let entry = DebugEntry {
93                level: entry_level,
94                message: message.into(),
95                line,
96                column,
97            };
98            
99            if let Ok(mut logs) = self.logs.lock() {
100                logs.push(entry);
101            }
102        }
103    }
104
105    pub fn error(&self, msg: impl Into<String>) {
106        self.log(DebugLevel::Error, msg);
107    }
108
109    pub fn warn(&self, msg: impl Into<String>) {
110        self.log(DebugLevel::Warn, msg);
111    }
112
113    pub fn info(&self, msg: impl Into<String>) {
114        self.log(DebugLevel::Info, msg);
115    }
116
117    pub fn trace(&self, msg: impl Into<String>) {
118        self.log(DebugLevel::Trace, msg);
119    }
120
121    pub fn verbose(&self, msg: impl Into<String>) {
122        self.log(DebugLevel::Verbose, msg);
123    }
124
125    pub fn get_logs(&self) -> Vec<DebugEntry> {
126        self.logs.lock()
127            .map(|logs| logs.clone())
128            .unwrap_or_default()
129    }
130
131    pub fn clear_logs(&self) {
132        if let Ok(mut logs) = self.logs.lock() {
133            logs.clear();
134        }
135    }
136
137    pub fn format_logs(&self) -> String {
138        let logs = self.get_logs();
139        logs.iter()
140            .map(|entry| {
141                let location = match (entry.line, entry.column) {
142                    (Some(line), Some(col)) => format!(" [{}:{}]", line, col),
143                    (Some(line), None) => format!(" [line {}]", line),
144                    _ => String::new(),
145                };
146                format!("[{}]{}: {}", entry.level, location, entry.message)
147            })
148            .collect::<Vec<_>>()
149            .join("\n")
150    }
151}
152
153impl Default for DebugContext {
154    fn default() -> Self {
155        Self::new(DebugLevel::default())
156    }
157}
158
159impl Clone for DebugContext {
160    fn clone(&self) -> Self {
161        Self {
162            level: self.level,
163            logs: self.logs.clone(),
164        }
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    fn test_debug_level_ordering() {
174        assert!(DebugLevel::Off < DebugLevel::Error);
175        assert!(DebugLevel::Error < DebugLevel::Warn);
176        assert!(DebugLevel::Trace < DebugLevel::Verbose);
177    }
178
179    #[test]
180    fn test_debug_level_from_str() {
181        assert_eq!("trace".parse::<DebugLevel>().unwrap(), DebugLevel::Trace);
182        assert_eq!("off".parse::<DebugLevel>().unwrap(), DebugLevel::Off);
183        assert_eq!("5".parse::<DebugLevel>().unwrap(), DebugLevel::Verbose);
184    }
185
186    #[test]
187    fn test_debug_context_filtering() {
188        let ctx = DebugContext::new(DebugLevel::Warn);
189        ctx.trace("should not be logged");
190        ctx.warn("should be logged");
191        ctx.error("should be logged");
192
193        let logs = ctx.get_logs();
194        assert_eq!(logs.len(), 2);
195        assert_eq!(logs[0].level, DebugLevel::Warn);
196        assert_eq!(logs[1].level, DebugLevel::Error);
197    }
198
199    #[test]
200    fn test_debug_entry_with_position() {
201        let ctx = DebugContext::new(DebugLevel::Trace);
202        ctx.log_at(DebugLevel::Error, "syntax error", Some(42), Some(10));
203
204        let logs = ctx.get_logs();
205        assert_eq!(logs[0].line, Some(42));
206        assert_eq!(logs[0].column, Some(10));
207    }
208}