#diagnostics #error #miette #result

tardar

Extensions for diagnostic error handling with miette

3 releases (breaking)

0.2.0 Nov 21, 2024
0.1.0 Sep 12, 2023
0.0.0 Jul 21, 2022

#405 in Rust patterns

Download history 318/week @ 2024-08-12 660/week @ 2024-08-19 621/week @ 2024-08-26 480/week @ 2024-09-02 662/week @ 2024-09-09 488/week @ 2024-09-16 587/week @ 2024-09-23 297/week @ 2024-09-30 612/week @ 2024-10-07 754/week @ 2024-10-14 969/week @ 2024-10-21 686/week @ 2024-10-28 1269/week @ 2024-11-04 685/week @ 2024-11-11 1211/week @ 2024-11-18 682/week @ 2024-11-25

3,902 downloads per month
Used in 24 crates (via wax)

MIT license

35KB
592 lines

Tardar is a Rust library that provides extensions for the miette crate. Diagnostic Results are the primary extension, which pair an output with accumulated Diagnostics for both success and failure (the Ok and Err variants).

GitHub docs.rs crates.io


lib.rs:

Tardar is a Rust library that provides extensions for the miette crate. These extensions primarily provide more ergonomic diagnostic Results and collation of Diagnostics.

Diagnostic Results

DiagnosticResult is a Result type that accumulates and associates Diagnostics with an output type T for both success and failure (Ok and Err variants). The Ok variant contains a Diagnosed with a T and zero or more non-error Diagnostics. The Err variant contains an Error with one or more Diagnostics, at least one of which is considered an error.

Together with extension methods, DiagnosticResult supports fluent and ergonomic composition of diagnostic functions. Here, a diagnostic function is one that returns a DiagnosticResult or other container of Diagnostics. For example, a library that parses a data structure or language from text may use diagnostic functions for parsing and analysis.

use tardar::DiagnosticResult;

#
/// Parses an expression into an abstract syntax tree (AST).
fn parse(expression: &str) -> DiagnosticResult<Ast<'_>> {
    ...
}

/// Checks an AST for token, syntax, and rule correctness.
fn check<'x>(tree: Ast<'x>) -> DiagnosticResult<Checked<Ast<'x>>> {
    ...
}

These diagnostic functions can be composed with extension methods.

use tardar::prelude::*;
use tardar::DiagnosticResult;

#
#
#
/// Parses an expression into a checked AST.
pub fn parse_and_check(expression: &str) -> DiagnosticResult<Checked<Ast<'_>>> {
    parse(expression).and_then_diagnose(check)
}

The parse_and_check function forwards the output of parse to check with and_then_diagnose. This function is much like the standard Result::and_then, but accepts a diagnostic function and so preserves any input Diagnostics. If parse succeeds with some warnings but check fails with an error, then the output Error will contain both the warning and error Diagnostics.

Other shapes of diagnostic functions can also be composed. For example, an analysis function may accept a shared reference and return an iterator rather than a result, since it cannot conceptually fail.

use tardar::BoxedDiagnostic;

#
/// Analyzes a checked AST and returns non-error diagnostics.
fn analyze<'x>(tree: &Checked<Ast<'x>>) -> impl Iterator<Item = BoxedDiagnostic> {
    ...
}

This diagnostic function can too be composed into parse_and_check using extension methods.

use tardar::prelude::*;
use tardar::{BoxedDiagnostic, DiagnosticResult};

#
#
#
#
/// Parses an expression into a checked AST with analysis.
pub fn parse_and_check(expression: &str) -> DiagnosticResult<Checked<Ast<'_>>> {
    parse(expression)
        .and_then_diagnose(check)
        .diagnose_non_errors(analyze)
}

The output of check is forwarded to analyze with diagnose_non_errors. This function is more bespoke and accepts a diagnostic function that itself accepts the output T by shared reference. Any Diagnostics returned by the accepted function are interpreted as non-errors and are accumulated into the Diagnosed.

DiagnosticResults can be constructed from related types, such as singular Result types and iterators with Diagnostic items. When extension functions like and_then_diagnose are not immediately compatible, it is often possible to perform conversions in a closure.

Collation

miette primarily groups Diagnostics via Diagnostic::related. However, it can be inflexible or cumbersome to provide such an implementation and Diagnostics are commonly and more easily organized into collections or iterators. Collation is a Diagnostic type that relates arbitrary non-empty vectors and slices of Diagnostics.

use tardar::{Diagnosed, DiagnosticResult, OwnedCollation};

#
/// Performs an active scan for the given BSSID.
pub fn scan(
    client: &Client,
    bssid: Bssid,
) -> Result<ActiveScan, OwnedCollation> {
    let result: DiagnosticResult<ActiveScan> = {
        ...
    };
    // The try operator `?` can be used here, because `Error` can be converted into
    // `Collation`. If the result is `Err`, then the `Collation` relates the error diagnostics.
    let scan = result.map(Diagnosed::into_output)?;
    ...
}

Note that DiagnosticResults accumulate Diagnostics, but do not relate them by design: neither Diagnosed nor Error implement Diagnostic.

Dependencies

~2.4–3.5MB
~61K SLoC