Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,9 @@ inward (core/macros) when changing fundamentals, outward (main) only to adjust t

- **`crates/standalone/error/`** — pluggable error backends implementing the `cgp-error` traits:
`cgp-error-anyhow`, `cgp-error-eyre`, `cgp-error-std`. These are opt-in and not part of the
default `cgp` facade.
default `cgp` facade. The knowledge base documents them as a project of their own, under
[`projects/error/`](https://github.com/contextgeneric/cgp-knowledge-base/tree/main/projects/error),
and their tests are the `error_backends` target of `cgp-tests`.

- **`crates/tests/`** — `cgp-tests` exercises real wiring and the user-facing macros end-to-end;
`cgp-macro-tests` covers parser corner cases plus **expansion snapshots** via the `snapshot_*`
Expand Down
18 changes: 14 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ cgp-dispatch = { version = "0.8.0-alpha", path = "./crates/extra/
cgp-run = { version = "0.8.0-alpha", path = "./crates/extra/cgp-run" }
cgp-runtime = { version = "0.8.0-alpha", path = "./crates/extra/cgp-runtime" }

cgp-error-anyhow = { version = "0.8.0-alpha", path = "./crates/standalone/error/cgp-error-anyhow" }
cgp-error-eyre = { version = "0.8.0-alpha", path = "./crates/standalone/error/cgp-error-eyre" }
cgp-error-std = { version = "0.8.0-alpha", path = "./crates/standalone/error/cgp-error-std" }

cgp-async-macro = { version = "0.8.0-alpha", path = "./crates/macros/cgp-async-macro" }
cgp-macro = { version = "0.8.0-alpha", path = "./crates/macros/cgp-macro" }
cgp-macro-core = { version = "0.8.0-alpha", path = "./crates/macros/cgp-macro-core" }
Expand Down
9 changes: 5 additions & 4 deletions crates/standalone/error/cgp-error-anyhow/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
[package]
name = "cgp-error-anyhow"
version = "0.8.0-alpha"
readme = "./README.md"
edition = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
keywords = { workspace = true }
description = """
Context-generic programming error handlers implemented using anyhow
"""
categories = { workspace = true }
description = "CGP error providers that make anyhow::Error a context's abstract error type."

[dependencies]
cgp = { version = "0.8.0-alpha", path = "../../../main/cgp-core", package = "cgp-core" }
anyhow = { version = "1.0.95", default-features = false }
anyhow = { version = "1.0.104", default-features = false }
45 changes: 45 additions & 0 deletions crates/standalone/error/cgp-error-anyhow/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# cgp-error-anyhow

`cgp-error-anyhow` makes [`anyhow::Error`](https://docs.rs/anyhow) the abstract error type of a
[CGP](https://contextgeneric.dev) context, and supplies the providers that raise errors into it and
add context to it. Code written against `HasErrorType`, `CanRaiseError`, and `CanWrapError` stays
generic; only the context's wiring names anyhow.

| Provider | Wire it to | What it does |
|---|---|---|
| `UseAnyhowError` | `ErrorTypeProviderComponent` | sets the context's `Error` to `anyhow::Error` |
| `RaiseAnyhowError` | `ErrorRaiserComponent`, `ErrorWrapperComponent` | raises a standard error without formatting it, so `downcast_ref` still finds it; wraps a `Display + Send + Sync + 'static` detail as anyhow context |
| `DebugAnyhowError` | `ErrorRaiserComponent`, `ErrorWrapperComponent` | raises or wraps any `Debug` value as a message formatted with `{:?}` |
| `DisplayAnyhowError` | `ErrorRaiserComponent`, `ErrorWrapperComponent` | raises or wraps any `Display` value as a message formatted with `{}` |

A context usually routes each source type to the provider that suits it:

```rust,ignore
use cgp::core::error::{ErrorRaiserComponent, ErrorTypeProviderComponent, ErrorWrapperComponent};
use cgp::prelude::*;
use cgp_error_anyhow::{DisplayAnyhowError, RaiseAnyhowError, UseAnyhowError};

pub struct App;

delegate_components! {
App {
open ErrorRaiserComponent;

ErrorTypeProviderComponent: UseAnyhowError,
@ErrorRaiserComponent.std::io::Error: RaiseAnyhowError,
@ErrorRaiserComponent.String: DisplayAnyhowError,
ErrorWrapperComponent: RaiseAnyhowError,
}
}

let error = App::raise_error(std::io::Error::other("disk full"));
let error = App::wrap_error(error, "while saving");
assert_eq!(format!("{error:#}"), "while saving: disk full");
```

A `String` needs `DisplayAnyhowError` or `DebugAnyhowError`, because it is not a standard error.
`DebugAnyhowError` prints it with quotes. The crate is `no_std` and builds anyhow without its `std`
feature. Enabling that feature elsewhere in the dependency graph turns on the parts of anyhow that
need `std`, such as backtrace capture.

The crate re-exports `anyhow::Error` as `cgp_error_anyhow::Error`.
18 changes: 10 additions & 8 deletions crates/standalone/error/cgp-error-anyhow/src/impls/debug_error.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
use alloc::format;
use core::fmt::Debug;

use anyhow::{Error, anyhow};
use anyhow::anyhow;
use cgp::error::{ErrorRaiser, ErrorRaiserComponent, ErrorWrapper, ErrorWrapperComponent};
use cgp::prelude::*;

/// Raises any `Debug` value into [`anyhow::Error`] as a message formatted with `{:?}`, and wraps a
/// `Debug` detail the same way. The original value is not kept.
pub struct DebugAnyhowError;

#[cgp_provider]
impl<Context, E> ErrorRaiser<Context, E> for DebugAnyhowError
#[cgp_impl(DebugAnyhowError)]
#[use_type(HasErrorType.{Error = anyhow::Error})]
impl<E> ErrorRaiser<E>
where
Context: HasErrorType<Error = Error>,
E: Debug,
{
fn raise_error(e: E) -> Error {
anyhow!("{:?}", e)
anyhow!("{e:?}")
}
}

#[cgp_provider]
impl<Context, Detail> ErrorWrapper<Context, Detail> for DebugAnyhowError
#[cgp_impl(DebugAnyhowError)]
#[use_type(HasErrorType.{Error = anyhow::Error})]
impl<Detail> ErrorWrapper<Detail>
where
Context: HasErrorType<Error = Error>,
Detail: Debug,
{
fn wrap_error(error: Error, detail: Detail) -> Error {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
use alloc::string::ToString;
use core::fmt::Display;

use anyhow::{Error, anyhow};
use anyhow::anyhow;
use cgp::error::{ErrorRaiser, ErrorRaiserComponent, ErrorWrapper, ErrorWrapperComponent};
use cgp::prelude::*;

/// Raises any `Display` value into [`anyhow::Error`] as a message formatted with `{}`, and wraps a
/// `Display` detail the same way. The original value is not kept.
pub struct DisplayAnyhowError;

#[cgp_provider]
impl<Context, E> ErrorRaiser<Context, E> for DisplayAnyhowError
#[cgp_impl(DisplayAnyhowError)]
#[use_type(HasErrorType.{Error = anyhow::Error})]
impl<E> ErrorRaiser<E>
where
Context: HasErrorType<Error = Error>,
E: Display,
{
fn raise_error(e: E) -> Error {
anyhow!("{e}")
}
}

#[cgp_provider]
impl<Context, Detail> ErrorWrapper<Context, Detail> for DisplayAnyhowError
#[cgp_impl(DisplayAnyhowError)]
#[use_type(HasErrorType.{Error = anyhow::Error})]
impl<Detail> ErrorWrapper<Detail>
where
Context: HasErrorType<Error = Error>,
Detail: Display,
{
fn wrap_error(error: Error, detail: Detail) -> Error {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
use core::error::Error as StdError;
use core::fmt::Display;

use anyhow::Error;
use cgp::error::{ErrorRaiser, ErrorRaiserComponent, ErrorWrapper, ErrorWrapperComponent};
use cgp::prelude::*;

/// Raises a standard error into [`anyhow::Error`] without formatting it, so the source stays
/// available to `downcast_ref` and to the error chain, and wraps a detail as anyhow context.
pub struct RaiseAnyhowError;

#[cgp_impl(RaiseAnyhowError)]
impl<Context, E> ErrorRaiser<E> for Context
#[use_type(HasErrorType.{Error = anyhow::Error})]
impl<E> ErrorRaiser<E>
where
Context: HasErrorType<Error = Error>,
E: StdError + Send + Sync + 'static,
{
fn raise_error(e: E) -> Error {
Expand All @@ -19,9 +20,9 @@ where
}

#[cgp_impl(RaiseAnyhowError)]
impl<Context, Detail> ErrorWrapper<Detail> for Context
#[use_type(HasErrorType.{Error = anyhow::Error})]
impl<Detail> ErrorWrapper<Detail>
where
Context: HasErrorType<Error = Error>,
Detail: Display + Send + Sync + 'static,
{
fn wrap_error(error: Error, detail: Detail) -> Error {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use anyhow::Error;
use cgp::error::{ErrorTypeProvider, ErrorTypeProviderComponent};
use cgp::prelude::*;

#[cgp_new_provider]
impl<Context> ErrorTypeProvider<Context> for UseAnyhowError {
type Error = Error;
/// Sets the context's abstract error type to [`anyhow::Error`].
pub struct UseAnyhowError;

#[cgp_impl(UseAnyhowError)]
impl ErrorTypeProvider {
type Error = anyhow::Error;
}
1 change: 1 addition & 0 deletions crates/standalone/error/cgp-error-anyhow/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#![doc = include_str!("../README.md")]
#![no_std]

extern crate alloc;
Expand Down
15 changes: 11 additions & 4 deletions crates/standalone/error/cgp-error-eyre/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
[package]
name = "cgp-error-eyre"
version = "0.8.0-alpha"
readme = "./README.md"
edition = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
keywords = { workspace = true }
description = """
Context-generic programming error handlers implemented using eyre
"""
categories = [ "rust-patterns" ]
description = "CGP error providers that make eyre::Report a context's abstract error type."

[dependencies]
cgp = { version = "0.8.0-alpha", path = "../../../main/cgp-core", package = "cgp-core" }
eyre = { version = "0.6.12", default-features = false }
# `auto-install` installs eyre's default report handler on first use. Without it, building any
# `eyre::Report` panics unless the application has called `eyre::set_hook` first.
#
# `track-caller` is left off: every report is built inside one of this crate's providers, behind
# CGP's generated forwarding impls, so the location it records is always a line in this crate
# rather than the caller's.
eyre = { version = "0.6.14", default-features = false, features = [ "auto-install" ] }
50 changes: 50 additions & 0 deletions crates/standalone/error/cgp-error-eyre/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# cgp-error-eyre

`cgp-error-eyre` makes [`eyre::Report`](https://docs.rs/eyre) the abstract error type of a
[CGP](https://contextgeneric.dev) context, and supplies the providers that raise errors into it and
add context to it. Code written against `HasErrorType`, `CanRaiseError`, and `CanWrapError` stays
generic; only the context's wiring names eyre.

| Provider | Wire it to | What it does |
|---|---|---|
| `UseEyreError` | `ErrorTypeProviderComponent` | sets the context's `Error` to `eyre::Report` |
| `RaiseEyreError` | `ErrorRaiserComponent`, `ErrorWrapperComponent` | raises a standard error without formatting it, so `downcast_ref` still finds it; wraps a `Display + Send + Sync + 'static` detail with `wrap_err` |
| `DebugEyreError` | `ErrorRaiserComponent`, `ErrorWrapperComponent` | raises or wraps any `Debug` value as a message formatted with `{:?}` |
| `DisplayEyreError` | `ErrorRaiserComponent`, `ErrorWrapperComponent` | raises or wraps any `Display` value as a message formatted with `{}` |

A context usually routes each source type to the provider that suits it:

```rust,ignore
use cgp::core::error::{ErrorRaiserComponent, ErrorTypeProviderComponent, ErrorWrapperComponent};
use cgp::prelude::*;
use cgp_error_eyre::{DisplayEyreError, RaiseEyreError, UseEyreError};

pub struct App;

delegate_components! {
App {
open ErrorRaiserComponent;

ErrorTypeProviderComponent: UseEyreError,
@ErrorRaiserComponent.std::io::Error: RaiseEyreError,
@ErrorRaiserComponent.String: DisplayEyreError,
ErrorWrapperComponent: RaiseEyreError,
}
}

let error = App::raise_error(std::io::Error::other("disk full"));
let error = App::wrap_error(error, "while saving");
assert_eq!(format!("{error:#}"), "while saving: disk full");
```

A `String` needs `DisplayEyreError` or `DebugEyreError`, because it is not a standard error.
`DebugEyreError` prints it with quotes.

The crate enables eyre's `auto-install` feature, so eyre's default report handler is installed the
first time a report is built. To use another handler, such as `color-eyre`, install it with
`eyre::set_hook` before the first error is raised; once a report exists, `set_hook` returns an
error. The crate leaves eyre's `track-caller` feature off, because every report is built inside one
of its providers and the recorded location would name that line rather than the caller. eyre
requires `std`, so this crate does too.

The crate re-exports `eyre::Error`, eyre's alias for `Report`, as `cgp_error_eyre::Error`.
18 changes: 10 additions & 8 deletions crates/standalone/error/cgp-error-eyre/src/impls/debug_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,27 @@ use core::fmt::Debug;

use cgp::error::{ErrorRaiser, ErrorRaiserComponent, ErrorWrapper, ErrorWrapperComponent};
use cgp::prelude::*;
use eyre::{Error, eyre};
use eyre::eyre;

/// Raises any `Debug` value into [`eyre::Report`] as a message formatted with `{:?}`, and wraps a
/// `Debug` detail the same way. The original value is not kept.
pub struct DebugEyreError;

#[cgp_provider]
impl<Context, E> ErrorRaiser<Context, E> for DebugEyreError
#[cgp_impl(DebugEyreError)]
#[use_type(HasErrorType.{Error = eyre::Report})]
impl<E> ErrorRaiser<E>
where
Context: HasErrorType<Error = Error>,
E: Debug,
{
fn raise_error(e: E) -> Error {
eyre!("{:?}", e)
eyre!("{e:?}")
}
}

#[cgp_provider]
impl<Context, Detail> ErrorWrapper<Context, Detail> for DebugEyreError
#[cgp_impl(DebugEyreError)]
#[use_type(HasErrorType.{Error = eyre::Report})]
impl<Detail> ErrorWrapper<Detail>
where
Context: HasErrorType<Error = Error>,
Detail: Debug,
{
fn wrap_error(error: Error, detail: Detail) -> Error {
Expand Down
Loading
Loading