Skip to main content

keramics_core/
debug_trace.rs

1/* Copyright 2024-2026 Joachim Metz <joachim.metz@gmail.com>
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may
5 * obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software
8 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 * License for the specific language governing permissions and limitations
11 * under the License.
12 */
13
14use std::fmt::{Display, Write};
15use std::sync::Arc;
16
17use super::formatters::format_as_hexdump;
18use super::mediator::Mediator;
19
20/// Debug trace.
21pub struct DebugTrace {}
22
23impl DebugTrace {
24    /// Prints text.
25    #[inline(always)]
26    pub fn print<T: Display>(text: T) {
27        let mediator: Arc<Mediator> = Mediator::current();
28
29        if mediator.debug_output {
30            mediator.debug_print(text);
31        }
32    }
33
34    /// Prints data.
35    #[inline(always)]
36    pub fn print_data(description: &str, offset: u64, data: &[u8], data_size: usize, group: bool) {
37        let mediator: Arc<Mediator> = Mediator::current();
38
39        if mediator.debug_output {
40            mediator.debug_print(format!(
41                "{} data of size: {} at offset: {} (0x{:08x})\n",
42                description, data_size, offset, offset
43            ));
44            mediator.debug_print(format_as_hexdump(data, group));
45        }
46    }
47
48    /// Prints a data field.
49    #[inline(always)]
50    pub fn print_data_field(identifier: &str, data: &[u8]) {
51        let mediator: Arc<Mediator> = Mediator::current();
52
53        if mediator.debug_output {
54            mediator.debug_print(format!("    {}\n", identifier));
55            mediator.debug_print(format_as_hexdump(data, true));
56        }
57    }
58
59    /// Prints the end of a trace.
60    #[inline(always)]
61    pub fn print_end() {
62        let mediator: Arc<Mediator> = Mediator::current();
63
64        if mediator.debug_output {
65            mediator.debug_print("}\n\n");
66        }
67    }
68
69    /// Prints a field.
70    #[inline(always)]
71    pub fn print_field<V: Display>(identifier: &str, value: V) {
72        let mediator: Arc<Mediator> = Mediator::current();
73
74        if mediator.debug_output {
75            mediator.debug_print(format!("    {}: {},\n", identifier, value));
76        }
77    }
78
79    /// Prints the start of a trace.
80    #[inline(always)]
81    pub fn print_start(identifier: &str) {
82        let mediator: Arc<Mediator> = Mediator::current();
83
84        if mediator.debug_output {
85            mediator.debug_print(format!("{} {{\n", identifier));
86        }
87    }
88
89    /// Prints a structure representation.
90    #[inline(always)]
91    pub fn print_structure(debug_read_data: fn(&[u8]) -> String, data: &[u8]) {
92        let mediator: Arc<Mediator> = Mediator::current();
93
94        if mediator.debug_output {
95            mediator.debug_print(debug_read_data(data));
96        }
97    }
98
99    /// Prints a value.
100    #[inline(always)]
101    pub fn print_value<V: Display>(description: &str, value: V) {
102        let mediator: Arc<Mediator> = Mediator::current();
103
104        if mediator.debug_output {
105            mediator.debug_print(format!("{}: {}\n", description, value));
106        }
107    }
108
109    /// Creates a debug trace scope that caches output.
110    #[inline]
111    pub fn scope<F, R, E>(function: F) -> Result<R, E>
112    where
113        F: FnOnce(&mut DebugTraceScope) -> Result<R, E>,
114    {
115        let mediator = Mediator::current();
116        let debug_output: bool = mediator.debug_output;
117
118        let mut scope = DebugTraceScope {
119            mediator: &mediator,
120            debug_output,
121            output: if debug_output {
122                String::with_capacity(1024)
123            } else {
124                String::new()
125            },
126        };
127        let result = function(&mut scope);
128
129        if debug_output && !scope.output.is_empty() {
130            scope.mediator.debug_print(scope.output);
131        }
132        result
133    }
134
135    /// Creates a debug trace scope.
136    #[inline]
137    pub fn static_scope<F>(function: F)
138    where
139        F: FnOnce(&DebugTraceStaticScope),
140    {
141        let mediator = Mediator::current();
142
143        if mediator.debug_output {
144            let scope = DebugTraceStaticScope {
145                mediator: &mediator,
146            };
147            function(&scope);
148        }
149    }
150}
151
152/// Debug trace scope that caches output.
153pub struct DebugTraceScope<'a> {
154    /// Mediator.
155    mediator: &'a Mediator,
156
157    /// Debug output.
158    pub debug_output: bool,
159
160    /// Output.
161    pub output: String,
162}
163
164impl<'a> DebugTraceScope<'a> {
165    /// Prints text.
166    #[inline(always)]
167    pub fn print<T: Display>(&mut self, text: T) {
168        if self.debug_output {
169            _ = write!(self.output, "{}", text);
170        }
171    }
172
173    /// Prints data.
174    #[inline(always)]
175    pub fn print_data(
176        &mut self,
177        description: &str,
178        offset: u64,
179        data: &[u8],
180        data_size: usize,
181        group: bool,
182    ) {
183        if self.debug_output {
184            _ = writeln!(
185                self.output,
186                "{} data of size: {} at offset: {} (0x{:08x})",
187                description, data_size, offset, offset
188            );
189            self.output
190                .push_str(format_as_hexdump(data, group).as_str());
191        }
192    }
193
194    /// Prints a data field.
195    #[inline(always)]
196    pub fn print_data_field(&mut self, identifier: &str, data: &[u8]) {
197        if self.debug_output {
198            _ = writeln!(self.output, "    {}", identifier);
199            self.output.push_str(format_as_hexdump(data, true).as_str());
200        }
201    }
202
203    /// Prints the end of a trace.
204    #[inline(always)]
205    pub fn print_end(&mut self) {
206        if self.debug_output {
207            _ = write!(self.output, "}}\n\n");
208        }
209    }
210
211    /// Prints a field.
212    #[inline(always)]
213    pub fn print_field<V: Display>(&mut self, identifier: &str, value: V) {
214        if self.debug_output {
215            _ = writeln!(self.output, "    {}: {},", identifier, value);
216        }
217    }
218
219    /// Prints the start of a trace.
220    #[inline(always)]
221    pub fn print_start(&mut self, identifier: &str) {
222        if self.debug_output {
223            _ = writeln!(self.output, "{} {{", identifier);
224        }
225    }
226
227    /// Prints a structure representation.
228    #[inline(always)]
229    pub fn print_structure(&mut self, debug_read_data: fn(&[u8]) -> String, data: &[u8]) {
230        if self.debug_output {
231            self.output.push_str(&debug_read_data(data));
232        }
233    }
234
235    /// Prints a value.
236    #[inline(always)]
237    pub fn print_value<V: Display>(&mut self, description: &str, value: V) {
238        if self.debug_output {
239            _ = writeln!(self.output, "{}: {}", description, value);
240        }
241    }
242}
243
244/// Debug trace scope.
245pub struct DebugTraceStaticScope<'a> {
246    /// Mediator.
247    mediator: &'a Mediator,
248}
249
250impl<'a> DebugTraceStaticScope<'a> {
251    /// Prints text.
252    #[inline(always)]
253    pub fn print<T: Display>(&self, text: T) {
254        self.mediator.debug_print(text);
255    }
256
257    /// Prints data.
258    #[inline(always)]
259    pub fn print_data(
260        &self,
261        description: &str,
262        offset: u64,
263        data: &[u8],
264        data_size: usize,
265        group: bool,
266    ) {
267        self.mediator.debug_print(format!(
268            "{} data of size: {} at offset: {} (0x{:08x})\n",
269            description, data_size, offset, offset
270        ));
271        self.mediator.debug_print(format_as_hexdump(data, group));
272    }
273
274    /// Prints a data field.
275    #[inline(always)]
276    pub fn print_data_field(&self, identifier: &str, data: &[u8]) {
277        self.mediator.debug_print(format!("    {}\n", identifier));
278        self.mediator.debug_print(format_as_hexdump(data, true));
279    }
280
281    /// Prints the end of a trace.
282    #[inline(always)]
283    pub fn print_end(&self) {
284        self.mediator.debug_print("}\n\n");
285    }
286
287    /// Prints a field.
288    #[inline(always)]
289    pub fn print_field<V: Display>(&self, identifier: &str, value: V) {
290        self.mediator
291            .debug_print(format!("    {}: {},\n", identifier, value));
292    }
293
294    /// Prints the start of a trace.
295    #[inline(always)]
296    pub fn print_start(&self, identifier: &str) {
297        self.mediator.debug_print(format!("{} {{\n", identifier));
298    }
299
300    /// Prints a structure representation.
301    #[inline(always)]
302    pub fn print_structure(&self, debug_read_data: fn(&[u8]) -> String, data: &[u8]) {
303        self.mediator.debug_print(debug_read_data(data));
304    }
305
306    /// Prints a value.
307    #[inline(always)]
308    pub fn print_value<V: Display>(&self, description: &str, value: V) {
309        self.mediator
310            .debug_print(format!("{}: {}\n", description, value));
311    }
312}
313
314#[cfg(test)]
315mod tests {
316    use super::*;
317
318    use std::cell::Cell;
319    use std::sync::Arc;
320
321    use crate::errors::ErrorTrace;
322    use crate::formatters::format_as_hexdump;
323
324    fn get_test_data() -> Vec<u8> {
325        vec![
326            0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad,
327            0xbe, 0xef,
328        ]
329    }
330
331    fn test_debug_read_data(data: &[u8]) -> String {
332        format!("structure (size: {})", data.len())
333    }
334
335    #[test]
336    fn test_print_with_debug_output_enabled() {
337        Mediator::new(true).make_current();
338
339        DebugTrace::print("test\n");
340        DebugTrace::print(String::from("test\n"));
341        DebugTrace::print(42);
342    }
343
344    #[test]
345    fn test_print_with_debug_output_disabled() {
346        DebugTrace::print("test\n");
347        DebugTrace::print(String::from("test\n"));
348        DebugTrace::print(42);
349    }
350
351    #[test]
352    fn test_print_data_with_debug_output_enabled() {
353        Mediator::new(true).make_current();
354
355        let test_data: Vec<u8> = get_test_data();
356        DebugTrace::print_data("test", 0, &test_data, 16, false);
357        DebugTrace::print_data("test", 0, &test_data, 16, true);
358    }
359
360    #[test]
361    fn test_print_data_with_debug_output_disabled() {
362        let test_data: Vec<u8> = get_test_data();
363        DebugTrace::print_data("test", 0, &test_data, 16, false);
364        DebugTrace::print_data("test", 0, &test_data, 16, true);
365    }
366
367    #[test]
368    fn test_print_data_field_with_debug_output_enabled() {
369        Mediator::new(true).make_current();
370
371        let test_data: Vec<u8> = get_test_data();
372        DebugTrace::print_data_field("test", &test_data);
373    }
374
375    #[test]
376    fn test_print_data_field_with_debug_output_disabled() {
377        let test_data: Vec<u8> = get_test_data();
378        DebugTrace::print_data_field("test", &test_data);
379    }
380
381    #[test]
382    fn test_print_end_with_debug_output_enabled() {
383        Mediator::new(true).make_current();
384
385        DebugTrace::print_end();
386    }
387
388    #[test]
389    fn test_print_end_with_debug_output_disabled() {
390        DebugTrace::print_end();
391    }
392
393    #[test]
394    fn test_print_field_with_debug_output_enabled() {
395        Mediator::new(true).make_current();
396
397        DebugTrace::print_field("identifier", "value");
398        DebugTrace::print_field("identifier", 42);
399    }
400
401    #[test]
402    fn test_print_field_with_debug_output_disabled() {
403        DebugTrace::print_field("identifier", "value");
404        DebugTrace::print_field("identifier", 42);
405    }
406
407    #[test]
408    fn test_print_start_with_debug_output_enabled() {
409        Mediator::new(true).make_current();
410
411        DebugTrace::print_start("test");
412    }
413
414    #[test]
415    fn test_print_start_with_debug_output_disabled() {
416        DebugTrace::print_start("test");
417    }
418
419    #[test]
420    fn test_print_structure_with_debug_output_enabled() {
421        Mediator::new(true).make_current();
422
423        let test_data: Vec<u8> = get_test_data();
424        DebugTrace::print_structure(test_debug_read_data, &test_data);
425    }
426
427    #[test]
428    fn test_print_structure_with_debug_output_disabled() {
429        let test_data: Vec<u8> = get_test_data();
430        DebugTrace::print_structure(test_debug_read_data, &test_data);
431    }
432
433    #[test]
434    fn test_print_value_with_debug_output_enabled() {
435        Mediator::new(true).make_current();
436
437        DebugTrace::print_value("test", "value");
438        DebugTrace::print_value("test", 42);
439    }
440
441    #[test]
442    fn test_print_value_with_debug_output_disabled() {
443        DebugTrace::print_value("test", "value");
444        DebugTrace::print_value("test", 42);
445    }
446
447    #[test]
448    fn test_scope_with_debug_output_enabled() -> Result<(), ErrorTrace> {
449        Mediator::new(true).make_current();
450
451        let result: u64 = DebugTrace::scope(
452            |scope: &mut DebugTraceScope<'_>| -> Result<u64, ErrorTrace> {
453                scope.print_start("test");
454                scope.print_field("value", 42);
455                scope.print_end();
456                Ok(42)
457            },
458        )?;
459        assert_eq!(result, 42);
460
461        let result: u64 = DebugTrace::scope(
462            |scope: &mut DebugTraceScope<'_>| -> Result<u64, ErrorTrace> {
463                let _ = scope;
464                Ok(0)
465            },
466        )?;
467        assert_eq!(result, 0);
468
469        Ok(())
470    }
471
472    #[test]
473    fn test_scope_with_debug_output_disabled() -> Result<(), ErrorTrace> {
474        let result: u64 = DebugTrace::scope(
475            |scope: &mut DebugTraceScope<'_>| -> Result<u64, ErrorTrace> {
476                assert!(!scope.debug_output);
477                scope.print_start("test");
478                assert_eq!(scope.output, "");
479                Ok(42)
480            },
481        )?;
482        assert_eq!(result, 42);
483
484        Ok(())
485    }
486
487    #[test]
488    fn test_scope_with_error() -> Result<(), ErrorTrace> {
489        Mediator::new(true).make_current();
490
491        let error: ErrorTrace = DebugTrace::scope(
492            |scope: &mut DebugTraceScope<'_>| -> Result<u64, ErrorTrace> {
493                scope.print("error\n");
494                Err(ErrorTrace::new(String::from("test error")))
495            },
496        )
497        .unwrap_err();
498
499        assert_eq!(error.to_string(), "#0 test error");
500
501        Ok(())
502    }
503
504    #[test]
505    fn test_trace_scope_with_debug_output_enabled() -> Result<(), ErrorTrace> {
506        Mediator::new(true).make_current();
507
508        let mediator: Arc<Mediator> = Mediator::current();
509        let debug_output: bool = mediator.debug_output;
510
511        let mut scope = DebugTraceScope {
512            mediator: &mediator,
513            debug_output,
514            output: if debug_output {
515                String::with_capacity(1024)
516            } else {
517                String::new()
518            },
519        };
520        assert!(scope.debug_output);
521        assert_eq!(scope.output, "");
522
523        scope.print_start("test");
524        assert_eq!(scope.output, "test {\n");
525
526        scope.print_field("identifier", "value");
527        assert_eq!(scope.output, "test {\n    identifier: value,\n");
528
529        scope.print_field("identifier", 42);
530        assert_eq!(
531            scope.output,
532            "test {\n    identifier: value,\n    identifier: 42,\n"
533        );
534
535        scope.print_value("identifier", 42);
536        assert_eq!(
537            scope.output,
538            "test {\n    identifier: value,\n    identifier: 42,\nidentifier: 42\n"
539        );
540
541        scope.print("test\n");
542        assert_eq!(
543            scope.output,
544            "test {\n    identifier: value,\n    identifier: 42,\nidentifier: 42\ntest\n"
545        );
546
547        let test_data: Vec<u8> = get_test_data();
548        let test_data_hexdump: String = format_as_hexdump(&test_data, true);
549        let mut expected_string: String = String::from(
550            "test {\n    identifier: value,\n    identifier: 42,\nidentifier: 42\ntest\n",
551        );
552
553        scope.print_data("test data", 0, &test_data, 16, true);
554        expected_string.push_str("test data data of size: 16 at offset: 0 (0x00000000)\n");
555        expected_string.push_str(&test_data_hexdump);
556        assert_eq!(scope.output, expected_string);
557
558        scope.print_data_field("test data", &test_data);
559        expected_string.push_str("    test data\n");
560        expected_string.push_str(&test_data_hexdump);
561        assert_eq!(scope.output, expected_string);
562
563        let structure_string: String = test_debug_read_data(&test_data);
564
565        scope.print_structure(test_debug_read_data, &test_data);
566        expected_string.push_str(&structure_string);
567        assert_eq!(scope.output, expected_string);
568
569        scope.print_end();
570        expected_string.push_str("}\n\n");
571        assert_eq!(scope.output, expected_string);
572
573        Ok(())
574    }
575
576    #[test]
577    fn test_trace_scope_with_debug_output_disabled() -> Result<(), ErrorTrace> {
578        let mediator: Arc<Mediator> = Mediator::current();
579
580        let mut scope = DebugTraceScope {
581            mediator: &mediator,
582            debug_output: false,
583            output: String::new(),
584        };
585        assert!(!scope.debug_output);
586
587        scope.print("test\n");
588        assert_eq!(scope.output, "");
589
590        scope.print_start("test");
591        assert_eq!(scope.output, "");
592
593        scope.print_end();
594        assert_eq!(scope.output, "");
595
596        scope.print_field("identifier", "value");
597        assert_eq!(scope.output, "");
598
599        scope.print_value("identifier", 42);
600        assert_eq!(scope.output, "");
601
602        let test_data: Vec<u8> = get_test_data();
603
604        scope.print_data("test data", 0, &test_data, 16, true);
605        assert_eq!(scope.output, "");
606
607        scope.print_data_field("test data", &test_data);
608        assert_eq!(scope.output, "");
609
610        scope.print_structure(test_debug_read_data, &test_data);
611        assert_eq!(scope.output, "");
612
613        Ok(())
614    }
615
616    #[test]
617    fn test_static_scope_with_debug_output_enabled() {
618        Mediator::new(true).make_current();
619
620        DebugTrace::static_scope(|scope: &DebugTraceStaticScope<'_>| {
621            scope.print_start("test");
622            scope.print_field("value", 42);
623            scope.print_end();
624        });
625    }
626
627    #[test]
628    fn test_static_scope_with_debug_output_disabled() {
629        let was_called: Cell<bool> = Cell::new(false);
630
631        DebugTrace::static_scope(|scope: &DebugTraceStaticScope<'_>| {
632            _ = scope;
633            was_called.set(true);
634        });
635        assert!(!was_called.get());
636    }
637
638    #[test]
639    fn test_static_scope_print() {
640        Mediator::new(true).make_current();
641
642        DebugTrace::static_scope(|scope: &DebugTraceStaticScope<'_>| {
643            scope.print("test\n");
644            scope.print(String::from("test\n"));
645            scope.print(42);
646        });
647    }
648
649    #[test]
650    fn test_static_scope_print_data() {
651        Mediator::new(true).make_current();
652
653        let test_data: Vec<u8> = get_test_data();
654
655        DebugTrace::static_scope(|scope: &DebugTraceStaticScope<'_>| {
656            scope.print_data("test data", 0, &test_data, 16, false);
657            scope.print_data("test data", 0, &test_data, 16, true);
658        });
659    }
660
661    #[test]
662    fn test_static_scope_print_data_field() {
663        Mediator::new(true).make_current();
664
665        let test_data: Vec<u8> = get_test_data();
666
667        DebugTrace::static_scope(|scope: &DebugTraceStaticScope<'_>| {
668            scope.print_data_field("test data", &test_data);
669        });
670    }
671
672    #[test]
673    fn test_static_scope_print_structure() {
674        Mediator::new(true).make_current();
675
676        let test_data: Vec<u8> = get_test_data();
677
678        DebugTrace::static_scope(|scope: &DebugTraceStaticScope<'_>| {
679            scope.print_structure(test_debug_read_data, &test_data);
680        });
681    }
682
683    #[test]
684    fn test_static_scope_print_value() {
685        Mediator::new(true).make_current();
686
687        DebugTrace::static_scope(|scope: &DebugTraceStaticScope<'_>| {
688            scope.print_value("test", "value");
689            scope.print_value("test", 42);
690        });
691    }
692}