Skip to main content

keramics_core/
macros.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
14/// Debug print data.
15#[macro_export]
16macro_rules! debug_trace_data {
17    ( $description:expr, $offset:expr, $data:expr, $data_size:expr $(,)? ) => {
18        #[cfg(feature = "debug-trace")]
19        {
20            let mediator = $crate::mediator::Mediator::current();
21
22            if mediator.debug_output {
23                mediator.debug_print(format!(
24                    "{} data of size: {} at offset: {} (0x{:08x})\n",
25                    $description, $data_size, $offset, $offset
26                ));
27                mediator.debug_print_data($data, true);
28            }
29        }
30    };
31}
32
33/// Debug print data and a structure representation.
34#[macro_export]
35macro_rules! debug_trace_data_and_structure {
36    ( $description:expr, $offset:expr, $data:expr, $data_size:expr, $structure:expr $(,)? ) => {
37        #[cfg(feature = "debug-trace")]
38        {
39            let mediator = $crate::mediator::Mediator::current();
40
41            if mediator.debug_output {
42                mediator.debug_print(format!(
43                    "{} data of size: {} at offset: {} (0x{:08x})\n",
44                    $description, $data_size, $offset, $offset
45                ));
46                mediator.debug_print_data($data, true);
47                mediator.debug_print($structure);
48            }
49        }
50    };
51}
52
53/// Debug print a structure representation.
54#[macro_export]
55macro_rules! debug_trace_structure {
56    ( $structure:expr $(,)? ) => {
57        #[cfg(feature = "debug-trace")]
58        {
59            let mediator = $crate::mediator::Mediator::current();
60
61            if mediator.debug_output {
62                mediator.debug_print($structure);
63            }
64        }
65    };
66}
67
68/// Determines the name of the current function.
69#[macro_export]
70macro_rules! error_trace_function {
71    () => {{
72        fn f() {}
73        fn type_name_of<T>(_: T) -> &'static str {
74            std::any::type_name::<T>()
75        }
76        let name = type_name_of(f);
77        name.strip_suffix("::f").unwrap()
78    }};
79}
80
81/// Creates a new [`ErrorTrace`].
82#[macro_export]
83macro_rules! error_trace_new {
84    ( $message:expr $(,)? ) => {
85        $crate::ErrorTrace::new(format!("{}: {}", $crate::error_trace_function!(), $message))
86    };
87}
88
89/// Creates a new [`ErrorTrace`] based on an existing error.
90#[macro_export]
91macro_rules! error_trace_new_with_error {
92    ( $message:expr, $error:expr $(,)? ) => {
93        $crate::ErrorTrace::new(format!(
94            "{}: {} with error: {}",
95            $crate::error_trace_function!(),
96            $message,
97            $error.to_string(),
98        ))
99    };
100}
101
102/// Adds a frame to an existing [`ErrorTrace`].
103#[macro_export]
104macro_rules! error_trace_add_frame {
105    ( $error:expr, $message:expr $(,)? ) => {
106        $error.add_frame(format!("{}: {}", $crate::error_trace_function!(), $message))
107    };
108}
109
110/// Retrieves the size of a [`DataStreamReference`].
111#[macro_export]
112macro_rules! data_stream_get_size {
113    ( $data_stream:expr $(,)? ) => {
114        match $data_stream.write() {
115            Ok(mut data_stream) => match data_stream.get_size() {
116                Ok(size) => size,
117                Err(mut error) => {
118                    $crate::error_trace_add_frame!(
119                        error,
120                        "Unable to determine size of data stream"
121                    );
122                    return Err(error);
123                }
124            },
125            Err(error) => {
126                return Err($crate::error_trace_new_with_error!(
127                    "Unable to obtain write lock on data stream",
128                    error
129                ));
130            }
131        }
132    };
133}
134
135/// Reads data at a specific position from a [`DataStreamReference`].
136#[macro_export]
137macro_rules! data_stream_read_at_position {
138    ( $data_stream:expr, $buf:expr, $pos:expr $(,)? ) => {
139        match $data_stream.write() {
140            Ok(mut data_stream) => match data_stream.read_at_position($buf, $pos) {
141                Ok(read_count) => read_count,
142                Err(mut error) => {
143                    $crate::error_trace_add_frame!(error, "Unable to read from data stream");
144                    return Err(error);
145                }
146            },
147            Err(error) => {
148                return Err($crate::error_trace_new_with_error!(
149                    "Unable to obtain write lock on data stream",
150                    error
151                ));
152            }
153        }
154    };
155}
156
157/// Reads an exact amount of data at a specific position from a [`DataStreamReference`].
158#[macro_export]
159macro_rules! data_stream_read_exact_at_position {
160    ( $data_stream:expr, $buf:expr, $pos:expr $(,)? ) => {
161        match $data_stream.write() {
162            Ok(mut data_stream) => match data_stream.read_exact_at_position($buf, $pos) {
163                Ok(offset) => offset,
164                Err(mut error) => {
165                    $crate::error_trace_add_frame!(error, "Unable to read from data stream");
166                    return Err(error);
167                }
168            },
169            Err(error) => {
170                return Err($crate::error_trace_new_with_error!(
171                    "Unable to obtain write lock on data stream",
172                    error
173                ));
174            }
175        }
176    };
177}
178
179#[cfg(test)]
180mod tests {
181    use std::io::SeekFrom;
182    use std::sync::mpsc::channel;
183    use std::sync::{Arc, RwLock};
184    use std::thread::spawn;
185
186    use crate::data_stream::{DataStream, DataStreamReference};
187    use crate::errors::ErrorTrace;
188    use crate::fake_data_stream::open_fake_data_stream;
189
190    /// Test data stream, where all operations fail.
191    struct TestFailingDataStream {}
192
193    impl DataStream for TestFailingDataStream {
194        /// Retrieves the current position.
195        fn get_offset(&mut self) -> Result<u64, ErrorTrace> {
196            Err(ErrorTrace::new(String::from("test error")))
197        }
198
199        /// Retrieves the size of the data.
200        fn get_size(&mut self) -> Result<u64, ErrorTrace> {
201            Err(ErrorTrace::new(String::from("test error")))
202        }
203
204        /// Reads data at the current position.
205        fn read(&mut self, _buf: &mut [u8]) -> Result<usize, ErrorTrace> {
206            Err(ErrorTrace::new(String::from("test error")))
207        }
208
209        /// Sets the current position of the data.
210        fn seek(&mut self, _pos: SeekFrom) -> Result<u64, ErrorTrace> {
211            Err(ErrorTrace::new(String::from("test error")))
212        }
213    }
214
215    fn get_test_data() -> Vec<u8> {
216        (0..16).collect()
217    }
218
219    fn get_test_data_stream() -> DataStreamReference {
220        open_fake_data_stream(&get_test_data())
221    }
222
223    fn get_test_failing_data_stream() -> DataStreamReference {
224        Arc::new(RwLock::new(TestFailingDataStream {}))
225    }
226
227    fn get_test_poisoned_data_stream() -> DataStreamReference {
228        let data_stream: DataStreamReference = get_test_data_stream();
229        let poisoned_data_stream: DataStreamReference = data_stream.clone();
230        let (sender, receiver) = channel::<()>();
231        let thread = spawn(move || {
232            let _guard = poisoned_data_stream
233                .write()
234                .expect("unable to obtain write lock");
235
236            sender.send(()).expect("unable to send");
237            panic!("poison the data stream");
238        });
239        receiver.recv().expect("unable to receive");
240        let _thread_result = thread.join();
241
242        data_stream
243    }
244
245    /// Determines the size of a data stream.
246    fn data_stream_get_size_helper(data_stream: DataStreamReference) -> Result<u64, ErrorTrace> {
247        let size: u64 = data_stream_get_size!(data_stream);
248
249        Ok(size)
250    }
251
252    /// Reads data at a position from a data stream.
253    fn data_stream_read_at_position_helper(
254        data_stream: DataStreamReference,
255        buf: &mut [u8],
256        pos: SeekFrom,
257    ) -> Result<usize, ErrorTrace> {
258        let read_count: usize = data_stream_read_at_position!(data_stream, buf, pos);
259
260        Ok(read_count)
261    }
262
263    /// Reads an exact amount of data at a position from a data stream.
264    fn data_stream_read_exact_at_position_helper(
265        data_stream: DataStreamReference,
266        buf: &mut [u8],
267        pos: SeekFrom,
268    ) -> Result<u64, ErrorTrace> {
269        let offset: u64 = data_stream_read_exact_at_position!(data_stream, buf, pos);
270
271        Ok(offset)
272    }
273
274    #[test]
275    fn test_error_trace_function() {
276        let function_name: &str = error_trace_function!();
277
278        assert!(function_name.contains("test_error_trace_function"));
279    }
280
281    #[test]
282    fn test_error_trace_new() {
283        let error: ErrorTrace = error_trace_new!("test error");
284
285        let error_message: String = error.to_string();
286        assert!(error_message.starts_with("#0 "));
287        assert!(error_message.contains("test_error_trace_new: test error"));
288    }
289
290    #[test]
291    fn test_error_trace_new_with_error() {
292        let error: ErrorTrace = error_trace_new_with_error!(
293            "test message",
294            ErrorTrace::new(String::from("inner error")),
295        );
296
297        let error_message: String = error.to_string();
298        assert!(error_message.starts_with("#0 "));
299        assert!(
300            error_message.contains(
301                "test_error_trace_new_with_error: test message with error: #0 inner error"
302            )
303        );
304    }
305
306    #[test]
307    fn test_error_trace_add_frame() {
308        let mut error: ErrorTrace = ErrorTrace::new(String::from("original error"));
309
310        error_trace_add_frame!(error, "additional frame");
311
312        let error_message: String = error.to_string();
313        assert!(error_message.contains("#0 original error"));
314        assert!(error_message.contains("#1 "));
315        assert!(error_message.contains("test_error_trace_add_frame: additional frame"));
316    }
317
318    #[test]
319    fn test_data_stream_get_size() -> Result<(), ErrorTrace> {
320        let data_stream: DataStreamReference = get_test_data_stream();
321
322        let size: u64 = data_stream_get_size!(data_stream);
323        assert_eq!(size, 16);
324
325        Ok(())
326    }
327
328    #[test]
329    fn test_data_stream_get_size_with_failing_size() {
330        let test_data_stream: DataStreamReference = get_test_failing_data_stream();
331
332        let result: Result<u64, ErrorTrace> = data_stream_get_size_helper(test_data_stream);
333        let error: ErrorTrace = result.expect_err("expected error");
334
335        let error_message: String = error.to_string();
336        assert!(error_message.contains("Unable to determine size of data stream"));
337        assert!(error_message.contains("test error"));
338    }
339
340    #[test]
341    fn test_data_stream_get_size_with_poisoned_data_stream() {
342        let test_data_stream: DataStreamReference = get_test_poisoned_data_stream();
343
344        let result: Result<u64, ErrorTrace> = data_stream_get_size_helper(test_data_stream);
345        let error: ErrorTrace = result.expect_err("expected error");
346
347        let error_message: String = error.to_string();
348        assert!(error_message.contains("Unable to obtain write lock on data stream"));
349    }
350
351    #[test]
352    fn test_data_stream_read_at_position() -> Result<(), ErrorTrace> {
353        let data_stream: DataStreamReference = get_test_data_stream();
354
355        let mut data: Vec<u8> = vec![0; 4];
356        let read_count: usize =
357            data_stream_read_at_position!(data_stream, &mut data, SeekFrom::Start(2));
358
359        assert_eq!(read_count, 4);
360        assert_eq!(&data, &[2, 3, 4, 5]);
361
362        Ok(())
363    }
364
365    #[test]
366    fn test_data_stream_read_at_position_beyond_size() -> Result<(), ErrorTrace> {
367        let data_stream: DataStreamReference = get_test_data_stream();
368
369        let mut data: Vec<u8> = vec![0; 24];
370        let read_count: usize =
371            data_stream_read_at_position!(data_stream, &mut data, SeekFrom::Start(8));
372
373        assert_eq!(read_count, 8);
374        assert_eq!(&data[0..8], &[8, 9, 10, 11, 12, 13, 14, 15]);
375        assert_eq!(&data[8..24], &[0; 16]);
376
377        Ok(())
378    }
379
380    #[test]
381    fn test_data_stream_read_at_position_with_failing_read() {
382        let test_data_stream: DataStreamReference = get_test_failing_data_stream();
383
384        let mut data: Vec<u8> = vec![0; 4];
385        let result: Result<usize, ErrorTrace> =
386            data_stream_read_at_position_helper(test_data_stream, &mut data, SeekFrom::Start(0));
387        let error: ErrorTrace = result.expect_err("expected error");
388
389        let error_message: String = error.to_string();
390        assert!(error_message.contains("Unable to read from data stream"));
391        assert!(error_message.contains("test error"));
392    }
393
394    #[test]
395    fn test_data_stream_read_at_position_with_poisoned_data_stream() {
396        let test_data_stream: DataStreamReference = get_test_poisoned_data_stream();
397
398        let mut data: Vec<u8> = vec![0; 4];
399        let result: Result<usize, ErrorTrace> =
400            data_stream_read_at_position_helper(test_data_stream, &mut data, SeekFrom::Start(0));
401        let error: ErrorTrace = result.expect_err("expected error");
402
403        let error_message: String = error.to_string();
404        assert!(error_message.contains("Unable to obtain write lock on data stream"));
405    }
406
407    #[test]
408    fn test_data_stream_read_exact_at_position() -> Result<(), ErrorTrace> {
409        let data_stream: DataStreamReference = get_test_data_stream();
410
411        let mut data: Vec<u8> = vec![0; 4];
412        let offset: u64 =
413            data_stream_read_exact_at_position!(data_stream, &mut data, SeekFrom::Start(2));
414
415        assert_eq!(offset, 2);
416        assert_eq!(&data, &[2, 3, 4, 5]);
417
418        Ok(())
419    }
420
421    #[test]
422    fn test_data_stream_read_exact_at_position_beyond_size() {
423        let data_stream: DataStreamReference = get_test_data_stream();
424
425        let mut data: Vec<u8> = vec![0; 24];
426        let result: Result<u64, ErrorTrace> =
427            data_stream_read_exact_at_position_helper(data_stream, &mut data, SeekFrom::Start(0));
428        let error: ErrorTrace = result.expect_err("expected error");
429
430        let error_message: String = error.to_string();
431        assert!(error_message.contains("Unable to read the exact amount"));
432        assert!(error_message.contains("Unable to read from data stream"));
433    }
434
435    #[test]
436    fn test_data_stream_read_exact_at_position_with_failing_read() {
437        let test_data_stream: DataStreamReference = get_test_failing_data_stream();
438
439        let mut data: Vec<u8> = vec![0; 4];
440        let result: Result<u64, ErrorTrace> = data_stream_read_exact_at_position_helper(
441            test_data_stream,
442            &mut data,
443            SeekFrom::Start(0),
444        );
445        let error: ErrorTrace = result.expect_err("expected error");
446
447        let error_message: String = error.to_string();
448        assert!(error_message.contains("Unable to read from data stream"));
449        assert!(error_message.contains("test error"));
450    }
451
452    #[cfg(feature = "debug-trace")]
453    #[test]
454    fn test_debug_trace_data() {
455        crate::mediator::Mediator::new(true).make_current();
456
457        let data: Vec<u8> = get_test_data();
458        debug_trace_data!("test data", 0, &data, 16);
459    }
460
461    #[cfg(feature = "debug-trace")]
462    #[test]
463    fn test_debug_trace_data_with_debug_output_disabled() {
464        crate::mediator::Mediator::new(false).make_current();
465
466        let data: Vec<u8> = get_test_data();
467        debug_trace_data!("test data", 0, &data, 16);
468    }
469
470    #[cfg(feature = "debug-trace")]
471    #[test]
472    fn test_debug_trace_data_and_structure() {
473        crate::mediator::Mediator::new(true).make_current();
474
475        let data: Vec<u8> = get_test_data();
476        debug_trace_data_and_structure!("test data", 0, &data, 16, String::from("structure"));
477    }
478
479    #[cfg(feature = "debug-trace")]
480    #[test]
481    fn test_debug_trace_data_and_structure_with_debug_output_disabled() {
482        crate::mediator::Mediator::new(false).make_current();
483
484        let data: Vec<u8> = get_test_data();
485        debug_trace_data_and_structure!("test data", 0, &data, 16, String::from("structure"));
486    }
487
488    #[cfg(feature = "debug-trace")]
489    #[test]
490    fn test_debug_trace_structure() {
491        crate::mediator::Mediator::new(true).make_current();
492
493        debug_trace_structure!(String::from("structure"));
494    }
495
496    #[cfg(feature = "debug-trace")]
497    #[test]
498    fn test_debug_trace_structure_with_debug_output_disabled() {
499        crate::mediator::Mediator::new(false).make_current();
500
501        debug_trace_structure!(String::from("structure"));
502    }
503}