Skip to content

Commit

Permalink
Inital commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
olson-sean-k committed Jul 21, 2022
0 parents commit 80fcdb8
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
version: 2
updates:
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "daily"
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-patch"]
65 changes: 65 additions & 0 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: CI
on: [pull_request, push]
jobs:
rustfmt:
name: Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
components: rustfmt
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
clippy:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
components: clippy
- uses: actions-rs/cargo@v1
with:
command: clippy
args: --all-targets --no-default-features -- -D clippy::all
- uses: actions-rs/cargo@v1
with:
command: clippy
args: --all-features --all-targets -- -D clippy::all
test:
name: Test
needs: [clippy, rustfmt]
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macOS-latest, ubuntu-latest, windows-latest]
toolchain:
- 1.56.0 # Minimum.
- stable
- beta
- nightly
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.toolchain }}
override: true
- uses: actions-rs/cargo@v1
with:
command: test
args: --no-default-features --verbose
- uses: actions-rs/cargo@v1
with:
command: test
args: --all-features --verbose
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.idea/
target/
**/*.rs.bk
*.iml
Cargo.lock
21 changes: 21 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "tardar"
version = "0.0.0"
rust-version = "1.56.0"
edition = "2021"
authors = ["Sean Olson <[email protected]>"]
description = "Extensions for diagnostic error handling with `miette`."
repository = "https://github.com/olson-sean-k/tardar"
readme = "README.md"
license = "MIT"
keywords = ["diagnostics"]

[dependencies]

[dependencies.miette]
version = "^5.1.0"
default-features = false

[dependencies.vec1]
version = "^1.8.0"
default-features = false
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
**Tardar** is a Rust library that provides extensions for the [`miette`] crate.
Diagnostic `Result`s are the primary extension, which pair an output with
aggregated `Diagnostic`s in both the `Ok` and `Err` variants.

[![GitHub](https://img.shields.io/badge/GitHub-olson--sean--k/tardar-8da0cb?logo=github&style=for-the-badge)](https://github.com/olson-sean-k/tardar)
[![docs.rs](https://img.shields.io/badge/docs.rs-tardar-66c2a5?logo=rust&style=for-the-badge)](https://docs.rs/tardar)
[![crates.io](https://img.shields.io/crates/v/tardar.svg?logo=rust&style=for-the-badge)](https://crates.io/crates/tardar)

[`miette`]: https://crates.io/crates/miette
2 changes: 2 additions & 0 deletions rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
control_brace_style = "ClosingNextLine"
format_code_in_doc_comments = true
189 changes: 189 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
//! **Tardar** is a library that provides extensions for the [`miette`] crate. [Diagnostic
//! `Result`][`DiagnosticResult`]s are the primary extension, which pair an output with aggregated
//! [`Diagnostic`]s in both the `Ok` and `Err` variants.
//!
//! [`Diagnostic`]: miette::Diagnostic
//! [`DiagnosticResult`]: crate::DiagnosticResult
//! [`Result`]: std::result::Result
//!
//! [`miette`]: https://crates.io/crates/miette
use miette::Diagnostic;
use vec1::{vec1, Vec1};

pub mod integration {
pub mod miette {
#[doc(hidden)]
pub use ::miette::*;
}
}

pub mod prelude {
pub use crate::{DiagnosticResultExt as _, IteratorExt as _, ResultExt as _};
}

pub type BoxedDiagnostic<'d> = Box<dyn Diagnostic + 'd>;

/// `Result` that includes [`Diagnostic`]s on both success and failure.
///
/// On success, the `Ok` variant contains zero or more diagnostics and a non-diagnostic output `T`.
/// On failure, the `Err` variant contains one or more diagnostics, where at least one of the
/// diagnostics is an error.
///
/// See [`DiagnosticResultExt`].
///
/// [`Diagnostic`]: miette::Diagnostic
/// [`DiagnosticResultExt`]: crate::DiagnosticResultExt
pub type DiagnosticResult<'d, T> = Result<(T, Vec<BoxedDiagnostic<'d>>), Vec1<BoxedDiagnostic<'d>>>;

/// Extension methods for [`Iterator`]s.
///
/// [`Iterator`]: std::iter::Iterator
pub trait IteratorExt: Iterator + Sized {
/// Converts from a type that implements `Iterator<Item = BoxedDiagnostic<'d>>` into
/// `DiagnosticResult<'d, ()>`.
///
/// The [`Diagnostic`] items of the iterator are interpreted as non-errors. Note that the
/// [`Severity`] is not examined and so the [`Diagnostic`]s may have error-level severities
/// despite being interpreted as non-errors.
///
/// [`Diagnostic`]: miette::Diagnostic
/// [`Severity`]: miette::Severity
fn into_non_error_diagnostic<'d>(self) -> DiagnosticResult<'d, ()>
where
Self: Iterator<Item = BoxedDiagnostic<'d>>;
}

impl<I> IteratorExt for I
where
I: Iterator,
{
fn into_non_error_diagnostic<'d>(self) -> DiagnosticResult<'d, ()>
where
Self: Iterator<Item = BoxedDiagnostic<'d>>,
{
Ok(((), self.collect()))
}
}

/// Extension methods for [`Result`]s.
///
/// [`Result`]: std::result::Result
pub trait ResultExt<T, E> {
/// Converts from `Result<T, E>` into `DiagnosticResult<'d, T>`.
///
/// The error type `E` must be a [`Diagnostic`] and is interpreted as an error. Note that the
/// [`Severity`] is not examined and so the [`Diagnostic`] may have a non-error severity
/// despite being interpreted as an error.
///
/// [`Diagnostic`]: miette::Diagnostic
/// [`Severity`]: miette::Severity
fn into_error_diagnostic<'d>(self) -> DiagnosticResult<'d, T>
where
E: 'd + Diagnostic;
}

impl<T, E> ResultExt<T, E> for Result<T, E> {
fn into_error_diagnostic<'d>(self) -> DiagnosticResult<'d, T>
where
E: 'd + Diagnostic,
{
match self {
Ok(output) => Ok((output, vec![])),
Err(error) => Err(vec1![Box::new(error) as Box<dyn Diagnostic + 'd>]),
}
}
}

/// Extension methods for [`DiagnosticResult`]s.
///
/// [`DiagnosticResult`]: crate::DiagnosticResult
pub trait DiagnosticResultExt<'d, T> {
/// Converts from `DiagnosticResult<'_, T>` into `Option<T>`.
///
/// This function is similar to [`Result::ok`], but gets only the non-diagnostic output `T`
/// from the `Ok` variant in [`DiagnosticResult`], discarding diagnostics.
///
/// [`DiagnosticResult`]: crate::DiagnosticResult
/// [`Result::ok`]: std::result::Result::ok
fn ok_output(self) -> Option<T>;

/// Gets the [`Diagnostic`]s associated with the [`DiagnosticResult`].
///
/// Both the success and failure case may include diagnostics.
///
/// [`Diagnostic`]: miette::Diagnostic
/// [`DiagnosticResult`]: crate::DiagnosticResult
fn diagnostics(&self) -> &[BoxedDiagnostic<'d>];

/// Maps `DiagnosticResult<'d, T>` into `DiagnosticResult<'d, U>` by applying a function over
/// the non-diagnostic output of the `Ok` variant.
///
/// This function is similar to [`Result::map`], but maps only the non-diagnostic output `T`
/// from the `Ok` variant in [`DiagnosticResult`], ignoring diagnostics.
///
/// [`DiagnosticResult`]: crate::DiagnosticResult
/// [`Result::map`]: std::result::Result::map
fn map_output<U, F>(self, f: F) -> DiagnosticResult<'d, U>
where
F: FnOnce(T) -> U;

/// Calls the given function if the `DiagnosticResult` is `Ok` and otherwise returns the `Err`
/// variant of the `DiagnosticResult`.
///
/// This function is similar to [`Result::and_then`], but additionally forwards and collects
/// diagnostics.
///
/// [`DiagnosticResult`]: crate::DiagnosticResult
/// [`Result::and_then`]: std::result::Result::and_then
fn and_then_diagnose<U, F>(self, f: F) -> DiagnosticResult<'d, U>
where
F: FnOnce(T) -> DiagnosticResult<'d, U>;
}

impl<'d, T> DiagnosticResultExt<'d, T> for DiagnosticResult<'d, T> {
fn ok_output(self) -> Option<T> {
match self {
Ok((output, _)) => Some(output),
_ => None,
}
}

fn diagnostics(&self) -> &[BoxedDiagnostic<'d>] {
match self {
Ok((_, ref diagnostics)) => diagnostics,
Err(ref diagnostics) => diagnostics,
}
}

fn map_output<U, F>(self, f: F) -> DiagnosticResult<'d, U>
where
F: FnOnce(T) -> U,
{
match self {
Ok((output, diagnostics)) => Ok((f(output), diagnostics)),
Err(diagnostics) => Err(diagnostics),
}
}

fn and_then_diagnose<U, F>(self, f: F) -> DiagnosticResult<'d, U>
where
F: FnOnce(T) -> DiagnosticResult<'d, U>,
{
match self {
Ok((output, mut diagnostics)) => match f(output) {
Ok((output, tail)) => {
diagnostics.extend(tail);
Ok((output, diagnostics))
}
Err(tail) => {
diagnostics.extend(tail);
Err(diagnostics
.try_into()
.expect("diagnostic failure with no errors"))
}
},
Err(diagnostics) => Err(diagnostics),
}
}
}

0 comments on commit 80fcdb8

Please sign in to comment.