zoe 0.0.31

A nightly library for viral genomics
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
//! Error types and convenience traits for handling [`Result`].
//!
//! This module provides:
//!
//! - The error type [`ErrorWithContext`], along with the traits
//!   [`ResultWithErrorContext`] and [`WithErrorContext`], for wrapping errors
//!   with additional context while preserving the error source chain.
//! - [`GetCode`], [`OrFail`], and [`Fail`] for graceful CLI error handling with
//!   exit codes.
//!
//! ## Error Handling Philosophy in *Zoe*
//!
//! As a library, *Zoe* aims to avoid making assumptions on the style of error
//! handling chosen by users, in particular by not adopting any error handling
//! crate as a dependency.
//!
//! For specific applications, *Zoe* has enum-style error types such as
//! [`ProfileError`] or [`KmerError`], which the user can match on or display.
//! For working with files and record types, however, *Zoe* elects to use
//! [`std::io::Error`], allowing for system IO errors to be propagated and
//! function-specific error messages to be represented with
//! [`ErrorKind::InvalidData`] or [`ErrorKind::Other`].
//!
//! Similar to [`std::io::Error`], [`std::fmt::Display`] is implemented only at
//! the immediate error level. To see the full error stack when handling errors,
//! it is important to do one of the following:
//!
//! - Iterate through the source chain via [`Error::source`]
//! - Use *Zoe*'s [`OrFail`] or [`Fail`] traits
//! - Use an external crate like `anyhow`
//!
//! ## Error Context
//!
//! *Zoe* elects to add context to error messages by default when available,
//! such as in [`FastQReader::from_path`] which will include the path of a
//! missing/empty file. In bioinformatics, this context is very useful in
//! complex pipelines, and any runtime penalty is considered negligible compared
//! to the algorithms being run.
//!
//! This context is added using the [`WithErrorContext`] and
//! [`ResultWithErrorContext`] traits. They add context by creating a
//! [`ErrorWithContext`] struct, containing the original error (boxed) as the
//! [`Error::source`] and the context as the new top-level error, stored as a
//! [`String`].
//!
//! [`ErrorWithContext`] can also be constructed directly without a source
//! error. In applications that are avoiding dependencies such as `anyhow` and
//! do not want to use [`std::io::Error::other`], [`ErrorWithContext::new`] is a
//! viable option.
//!
//! [`ErrorWithContext`]: crate::data::err::ErrorWithContext
//! [`ResultWithErrorContext`]: crate::data::err::ResultWithErrorContext
//! [`WithErrorContext`]: crate::data::err::WithErrorContext
//! [`GetCode`]: crate::data::err::GetCode
//! [`OrFail`]: crate::data::err::OrFail
//! [`FastQReader::from_path`]: crate::prelude::FastQReader::from_path
//! [`Error::source`]: std::error::Error::source
//! [`ProfileError`]: crate::alignment::ProfileError
//! [`KmerError`]: crate::kmer::KmerError
//! [`ErrorKind::InvalidData`]: std::io::ErrorKind::InvalidData
//! [`ErrorKind::Other`]: std::io::ErrorKind::Other

use std::{
    error::Error,
    fmt::{Debug, Display, Write},
    hint::cold_path,
    path::Path,
};

/// A macro for unwrapping a [`Result`] and propagating any error as a
/// `Some(Err(e))`.
///
/// This is especially useful for fallible iterators, where results need to be
/// wrapped in [`Some`].
#[macro_export]
macro_rules! unwrap_or_return_some_err {
    ($expression:expr) => {
        match $expression {
            Ok(v) => v,
            Err(e) => return Some(Err(e)),
        }
    };
}

/// Trait for specifying getting exit codes originating from IO errors.
///
/// Implementing this trait allows the error type to work with [`OrFail`]. If
/// this is being implemented for a top-level error containing a nested error,
/// one should manually implement [`get_code`] to retrieve the underlying code
/// for the nested error, rather than using the blanket implementation.
///
/// [`get_code`]: GetCode::get_code
pub trait GetCode {
    /// Retrieves the exit code associated with a given error.
    ///
    /// ## Validity
    ///
    /// If this method is manually implemented, then it must recursively call
    /// [`get_code`] on [`Error::source`]. Any other behavior or logic is not
    /// guaranteed to be consistent within *Zoe*, and may or may not be
    /// correctly applied when using wrapped errors ([`ErrorWithContext`]).
    ///
    /// The blanket implementation returns `1`. We also implement on
    /// [`std::io::Error`] to return the [`raw_os_error`] if available.
    ///
    /// [`raw_os_error`]: std::io::Error::raw_os_error
    /// [`get_code`]: GetCode::get_code
    #[inline]
    #[must_use]
    fn get_code(&self) -> i32 {
        1
    }
}

impl GetCode for std::io::Error {
    #[inline]
    fn get_code(&self) -> i32 {
        if let Some(code) = self.raw_os_error() {
            return code;
        }

        let mut source = self.source();
        while let Some(err) = source {
            if let Some(e) = err.downcast_ref::<std::io::Error>()
                && let Some(code) = e.raw_os_error()
            {
                return code;
            }
            source = err.source();
        }

        1
    }
}

/// A trait for providing more graceful error reporting and aborting.
///
/// A status code is provided by [`GetCode`], and any context available in
/// [`Error::source`] is displayed.
///
/// <div class="warning note">
///
/// **Note**
///
/// To get full utility out of this trait, custom top-level errors should
/// manually implement [`std::error::Error::source`] and get whatever field or
/// variants contains the nested errors. In addition, [`GetCode`] should
/// likewise be implemented manually to retrieve the underlying codes for nested
/// [`std::io::Error`].
///
/// </div>
pub trait OrFail<T> {
    /// Unwraps the result, writing the error and any information in
    /// [`Error::source`] to stderr.
    fn unwrap_or_fail(self) -> T;

    /// Unwraps the result, writing the provided message, the error, and any
    /// information in [`Error::source`] to stderr.
    fn unwrap_or_die(self, msg: &str) -> T;
}

impl<T, E> OrFail<T> for Result<T, E>
where
    E: GetCode + Display + Error + 'static,
{
    fn unwrap_or_fail(self) -> T {
        match self {
            Ok(result) => result,
            Err(e) => {
                cold_path();
                e.fail()
            }
        }
    }

    fn unwrap_or_die(self, msg: &str) -> T {
        match self {
            Ok(result) => result,
            Err(e) => {
                cold_path();
                e.die(msg)
            }
        }
    }
}

/// A trait for providing more graceful error reporting and aborting. For
/// similar methods on [`Result`], see [`OrFail`].
///
/// A status code is provided by [`GetCode`], and any context available in
/// [`Error::source`] is displayed.
///
/// <div class="warning note">
///
/// **Note**
///
/// To get full utility out of this trait, custom top-level errors should
/// manually implement [`std::error::Error::source`] and get whatever field or
/// variants contains the nested errors. In addition, [`GetCode`] should
/// likewise be implemented manually to retrieve the underlying codes for nested
/// [`std::io::Error`].
///
/// </div>
pub trait Fail {
    /// Exits the program, writing the error and any information in
    /// [`Error::source`] to stderr.
    fn fail(self) -> !;

    /// Exits the program, writing the provided message, the error, and any
    /// information in [`Error::source`] to stderr.
    fn die(self, msg: &str) -> !;
}

impl<E> Fail for E
where
    E: GetCode + Display + Error + 'static,
{
    #[cold]
    fn fail(self) -> ! {
        if let Ok(bin) = std::env::current_exe() {
            eprintln!("Error in {b}", b = bin.display());
        } else {
            eprintln!("Error in program");
        }

        eprint!("{}", self.display_stack());
        std::process::exit(self.get_code());
    }

    #[cold]
    fn die(self, msg: &str) -> ! {
        if let Ok(bin) = std::env::current_exe() {
            eprintln!("Error in {b}: {msg}", b = bin.display());
        } else {
            eprintln!("Error: {msg}");
        }

        eprint!("{}", self.display_stack());
        std::process::exit(self.get_code());
    }
}

/// An error type supporting context and a backtrace.
///
/// Specifically, this error can hold up to three things:
///
/// 1. An optional source error message, which this error wraps. Using
///    [`unwrap_or_fail`] or [`unwrap_or_die`] cause the source error to be
///    shown in the backtrace. This source is accessible via [`Error::source`].
/// 2. A line of context describing the error. This appears as one item in the
///    [`OrFail`] backtrace.
/// 3. Any subitems (additional indented lines with more information that appear
///    below the line of context). This is useful for including the values of
///    variables or other useful information.
///
/// This can be converted to [`std::io::Error`] with [`Into`]. Hence, in
/// functions returning [`std::io::Result`], the `?` operator can be used after
/// adding context.
///
/// [`with_subitem`]: WithSubitem::with_subitem
/// [`unwrap_or_fail`]: OrFail::unwrap_or_fail
/// [`unwrap_or_die`]: OrFail::unwrap_or_die
#[must_use]
#[derive(Debug)]
pub struct ErrorWithContext {
    /// The inner representation. Using a single fat pointer is better than
    /// storing the description and source fields directly, since it minimizes
    /// the size of [`ErrorWithContext`] and hence the size of `Result<T,
    /// ErrorWithContext>`.
    repr: Box<ErrorWithContextRepr>,
}

impl ErrorWithContext {
    /// Constructs a new [`ErrorWithContext`] with the given description,
    /// without a source error or any subitems.
    ///
    /// The `description` may be anything implementing `Into<String>`. Passing
    /// an owned `String` avoids an extra allocation.
    pub fn new(description: impl Into<String>) -> Self {
        ErrorWithContext {
            repr: Box::new(ErrorWithContextRepr {
                description: description.into(),
                subitem:     None,
                source:      None,
            }),
        }
    }
}

/// The inner representation for an [`ErrorWithContext`]. This is wrapped in a
/// [`Box`] in [`ErrorWithContext`] to reduce the memory of `Result<T,
/// ErrorWithContext>` in the `Ok` case.
#[derive(Debug)]
struct ErrorWithContextRepr {
    /// The context that was added to the error.
    description: String,

    /// Any subitems attached to the error, separated by new lines.
    subitem: Option<String>,

    /// The source error which the context is added to.
    source: Option<Box<dyn Error + Send + Sync>>,
}

impl Display for ErrorWithContextRepr {
    /// Displays the description of the error as well as any subitems.
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.description)?;
        if let Some(subitem) = &self.subitem {
            write!(
                f,
                "\n| {}",
                IndentWrapper {
                    val:    subitem,
                    indent: "| ",
                }
            )?;
        }
        Ok(())
    }
}

impl std::fmt::Display for ErrorWithContext {
    #[inline]
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.repr)
    }
}

impl Error for ErrorWithContext {
    #[inline]
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match &self.repr.source {
            Some(source) => Some(source.as_ref()),
            None => None,
        }
    }
}

impl From<ErrorWithContext> for std::io::Error {
    #[inline]
    fn from(e: ErrorWithContext) -> Self {
        std::io::Error::other(e)
    }
}

/// An extension trait for [`Error`] allowing additional context to be added via
/// a [`ErrorWithContext`].
pub trait WithErrorContext {
    /// Wraps the error in an [`ErrorWithContext`] with the given description.
    ///
    /// The `description` may be anything implementing `Into<String>`. Passing
    /// an owned `String` avoids an extra allocation.
    fn with_context(self, description: impl Into<String>) -> ErrorWithContext;

    /// Wraps the error in an [`ErrorWithContext`] by adding type context.
    fn with_type_context<T>(self) -> ErrorWithContext;

    /// Wraps the error in an [`ErrorWithContext`] by adding path context.
    ///
    /// The context will be formatted as `msg: 'path'`. The `msg` may be
    /// anything implementing [`Display`].
    fn with_path_context(self, msg: impl Display, file: impl AsRef<Path>) -> ErrorWithContext;
}

impl<E: Error + Send + Sync + 'static> WithErrorContext for E {
    // Do not inline, since this is cold code
    fn with_context(self, description: impl Into<String>) -> ErrorWithContext {
        ErrorWithContext {
            repr: Box::new(ErrorWithContextRepr {
                description: description.into(),
                subitem:     None,
                source:      Some(Box::new(self)),
            }),
        }
    }

    // Do not inline, since this is cold code
    fn with_type_context<T>(self) -> ErrorWithContext {
        let name = std::any::type_name::<T>();
        let description = format!(
            "Failure in {}",
            name.split('<').next().unwrap_or(name).rsplit("::").next().unwrap_or(name)
        );

        ErrorWithContext {
            repr: Box::new(ErrorWithContextRepr {
                description,
                subitem: None,
                source: Some(Box::new(self)),
            }),
        }
    }

    // Do not inline, since this is cold code
    fn with_path_context(self, msg: impl Display, file: impl AsRef<Path>) -> ErrorWithContext {
        Self::with_context(self, format!("{msg}: '{path}'", path = file.as_ref().display()))
    }
}

/// An extension trait for [`ErrorWithContext`] allowing an indented subitem to
/// be added to the error (without adding a new error to the backtrace).
pub trait WithSubitem {
    /// Adds a subitem with the given `message` to the error without adding a
    /// new error to the backtrace.
    ///
    /// [`WithErrorContext::with_context`] creates a new entry in the backtrace
    /// (displayed using `→`), whereas this method adds a message that is
    /// indented beneath the error and indicated using `|`. For example:
    ///
    /// ```text
    /// Error in /path/to/binary
    ///   → Failed to load reads from file: input.fastq
    ///   → Failed to deinterleave records due to mismatching headers
    ///     | Header 1: SIM:1:FCX:1:15:6329:1045 1:N:0:2
    ///     | Header 2: SIM:1:FCX:1:15:2345:1001 2:N:0:2
    ///   → x_pos fields did not agree!
    /// ```
    fn with_subitem(self, message: impl Into<String>) -> ErrorWithContext;
}

impl WithSubitem for ErrorWithContext {
    fn with_subitem(mut self, message: impl Into<String>) -> ErrorWithContext {
        let subitem = &mut self.repr.subitem;
        let message = message.into();
        if let Some(subitem) = subitem {
            subitem.push('\n');
            subitem.push_str(&message);
        } else {
            *subitem = Some(message);
        }
        self
    }
}

/// An extension trait for [`Result`] allowing additional context to be added to
/// an [`Err`] variant via a [`ErrorWithContext`].
///
/// The methods are similar to [`WithErrorContext`], but are implemented for
/// results.
pub trait ResultWithErrorContext {
    /// The type of the [`Ok`] variant in the result.
    type Ok;

    /// Wraps the [`Err`] variant in an [`ErrorWithContext`] with the given
    /// description.
    ///
    /// The `description` may be anything implementing `Into<String>`. Passing
    /// an owned `String` avoids an extra allocation.
    ///
    /// ## Errors
    ///
    /// Propagates errors in `self`, with the added context.
    fn with_context(self, description: impl Into<String>) -> Result<Self::Ok, ErrorWithContext>;

    /// Wraps the [`Err`] variant in an [`ErrorWithContext`] by adding type
    /// context.
    ///
    /// ## Errors
    ///
    /// Propagates errors in `self`, with the added context.
    fn with_type_context<T>(self) -> Result<Self::Ok, ErrorWithContext>;

    /// Wraps the [`Err`] variant in an [`ErrorWithContext`] by adding path
    /// context.
    ///
    /// The context will be formatted as `msg: 'path'`. The `msg` may be
    /// anything implementing [`Display`].
    ///
    /// ## Errors
    ///
    /// Propagates errors in `self`, with the added context.
    fn with_path_context(self, msg: impl Display, file: impl AsRef<Path>) -> Result<Self::Ok, ErrorWithContext>;
}

impl<Ok, E: WithErrorContext> ResultWithErrorContext for Result<Ok, E> {
    type Ok = Ok;

    #[inline]
    fn with_context(self, description: impl Into<String>) -> Result<Ok, ErrorWithContext> {
        self.map_err(|e| {
            cold_path();
            e.with_context(description)
        })
    }

    #[inline]
    fn with_type_context<T>(self) -> Result<Ok, ErrorWithContext> {
        self.map_err(|e| {
            cold_path();
            e.with_type_context::<T>()
        })
    }

    #[inline]
    fn with_path_context(self, msg: impl Display, file: impl AsRef<Path>) -> Result<Ok, ErrorWithContext> {
        self.map_err(|e| {
            cold_path();
            e.with_path_context(msg, file)
        })
    }
}

/// An extension trait for `Result<T, WithErrorContext>` allowing information to
/// be attached to an [`Err`] variant.
///
/// The methods are similar to [`WithSubitem`], but are implemented for results.
pub trait ResultWithSubitem {
    /// Adds a subitem with the given `message` to an [`Err`] variant without
    /// adding a new error to the backtrace.
    ///
    /// [`ResultWithErrorContext::with_context`] creates a new entry in the
    /// backtrace (displayed using `→`), whereas this method adds a message that
    /// is indented beneath the error and indicated using `|`. For example:
    ///
    /// ```text
    /// Error in /path/to/binary
    ///   → Failed to load reads from file: input.fastq
    ///   → Failed to deinterleave records due to mismatching headers
    ///     | Header 1: SIM:1:FCX:1:15:6329:1045 1:N:0:2
    ///     | Header 2: SIM:1:FCX:1:15:2345:1001 2:N:0:2
    ///   → x_pos fields did not agree!
    /// ```
    #[must_use]
    fn with_subitem(self, message: impl Into<String>) -> Self;
}

impl<T> ResultWithSubitem for Result<T, ErrorWithContext> {
    #[inline]
    fn with_subitem(self, message: impl Into<String>) -> Self {
        self.map_err(|e| {
            cold_path();
            e.with_subitem(message)
        })
    }
}

/// A wrapper around [`std::fmt::Formatter`] which automatically indents all new
/// lines with a specified string.
///
/// This is a helper struct for the formatting used in [`fail`] and [`die`].
///
/// [`fail`]: Fail::fail
/// [`die`]: Fail::die
struct IndentFormatter<'a, 'b> {
    formatter: &'a mut std::fmt::Formatter<'b>,
    indent:    &'static str,
}

impl Write for IndentFormatter<'_, '_> {
    fn write_str(&mut self, s: &str) -> std::fmt::Result {
        let mut parts = s.split('\n');
        let Some(first_part) = parts.next() else { return Ok(()) };
        self.formatter.write_str(first_part)?;

        for part in parts {
            self.formatter.write_char('\n')?;
            self.formatter.write_str(self.indent)?;
            self.formatter.write_str(part)?;
        }

        Ok(())
    }

    fn write_char(&mut self, c: char) -> std::fmt::Result {
        if c == '\n' {
            self.formatter.write_char('\n')?;
            self.formatter.write_str(self.indent)
        } else {
            self.formatter.write_char(c)
        }
    }
}

/// A wrapper type altering the implementation of [`Display`], such that any new
/// lines are automatically indented with a specified string.
///
/// This is a helper struct for the formatting used in [`fail`] and [`die`].
///
/// [`fail`]: Fail::fail
/// [`die`]: Fail::die
struct IndentWrapper<T> {
    /// The value to display.
    val:    T,
    /// The string to use when indenting lines after the first.
    indent: &'static str,
}

impl<T: Display> Display for IndentWrapper<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            IndentFormatter {
                formatter: f,
                indent:    self.indent,
            },
            "{}",
            self.val
        )
    }
}

/// An extension trait for an [`Error`] enabling it to be displayed alongside
/// its error stack using [`Error::source`].
///
/// If aborting, consider using [`fail`] or [`die`]. Otherwise, this can be
/// helpful in displaying warnings.
///
/// [`fail`]: Fail::fail
/// [`die`]: Fail::die
pub trait DisplayErrStack {
    /// Returns a displayable representation of the error alongside its error
    /// stack using [`Error::source`].
    ///
    /// This using `→` and two spaces of indent before each item, and includes a
    /// newline at the end.
    fn display_stack(&self) -> ErrStackDisplay<'_>;
}

impl<E> DisplayErrStack for E
where
    E: Error + 'static,
{
    fn display_stack(&self) -> ErrStackDisplay<'_> {
        ErrStackDisplay(self)
    }
}

impl DisplayErrStack for dyn Error + 'static {
    fn display_stack(&self) -> ErrStackDisplay<'_> {
        ErrStackDisplay(self)
    }
}

/// A display wrapper around an error that shows the error and its sources (with
/// [`Error::source`]) in a list, using `→` and two spaces of indent before each
/// item.
///
/// This includes a newline at the end.
pub struct ErrStackDisplay<'a>(&'a (dyn Error + 'static));

impl Display for ErrStackDisplay<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        // Wrap the error in Some so that we don't have to write the same logic
        // twice
        let mut maybe_err = Some(self.0);

        while let Some(err) = maybe_err {
            writeln!(
                f,
                "{err}",
                err = IndentWrapper {
                    val:    err,
                    indent: "    ",
                }
            )?;

            maybe_err = err.source();
        }

        Ok(())
    }
}