Skip to main content

scoped_error/
conversion.rs

1// Copyright (C) 2026 Kan-Ru Chen <kanru@kanru.info>
2//
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4
5//! Functions for adding context to errors.
6//!
7//! This module provides the core functions that enable context propagation:
8//! [`expect_error`] for use with [`scoped_error::Error`](crate::Error) or custom
9//! error types, and [`expect_error_fn`] for advanced use cases.
10//!
11//! These functions work by:
12//! 1. Executing a closure that returns `Result<T, Frame>`
13//! 2. If the result is `Err`, attaching the context message and location
14//! 3. Returning `Result<T, E>` where `E` is the target error type
15//!
16//! # How It Works
17//!
18//! The `?` operator automatically converts errors to `Frame` via the
19//! `From` impl in [`error`](crate::error), capturing the source location.
20//! The conversion functions then wrap this context with an additional message.
21
22use std::borrow::Cow;
23
24use crate::error::Frame;
25use crate::error::WithContext;
26
27/// Low-level function for adding context with a custom error constructor.
28///
29/// This function is the foundation for other conversion functions. It allows
30/// complete control over how the final error is constructed.
31///
32/// # Type Parameters
33///
34/// - `F`: A function that constructs the target error type `E`
35/// - `T`: The success type
36/// - `E`: The target error type (must implement [`WithContext`])
37///
38/// # Example
39///
40/// ```
41/// # use scoped_error::impl_context_error;
42/// use scoped_error::{expect_error_fn, WithContext, Frame};
43///
44/// # impl_context_error!(MyError);
45/// # impl MyError {
46/// #   fn new(msg: &'static str) -> MyError {
47/// #     MyError { message: msg.into(), source: None, location: None }
48/// #   }
49/// # }
50///
51/// # fn operation_that_might_fail() -> Result<(), MyError> { Ok(()) }
52///
53/// fn do_work() -> Result<(), MyError> {
54///     let err = || MyError::new("custom");
55///
56///     expect_error_fn(err, || {
57///         operation_that_might_fail()?;
58///         Ok(())
59///     })
60/// }
61/// ```
62#[inline(always)]
63pub fn expect_error_fn<F, T, E>(err: F, body: impl FnOnce() -> Result<T, Frame>) -> Result<T, E>
64where
65    F: FnOnce() -> E,
66    E: WithContext,
67{
68    body().map_err(|context| err().with_context(context))
69}
70
71/// Add context to errors, returning a custom error type.
72///
73/// # Type Parameters
74///
75/// - `T`: The success type
76/// - `E`: The custom error type
77///
78/// # Example
79///
80/// The simplest way to add context is using it with the ready to use
81/// [`scoped_error::Error`](crate::Error) type when you don't need a custom error type.
82///
83/// ```
84/// use scoped_error::{Error, expect_error};
85///
86/// fn read_file() -> Result<String, Error> {
87///     expect_error("Failed to read configuration", || {
88///         let cfg = std::fs::read_to_string("config.toml")?;
89///         Ok(cfg)
90///     })
91/// }
92/// ```
93///
94/// Use this function with custom error types that implement
95/// `From<(Cow<'static, str>, Frame)>`. The [`impl_context_error!`](crate::impl_context_error)
96/// macro generates this implementation for you.
97///
98/// ```
99/// use scoped_error::{expect_error, impl_context_error};
100///
101/// impl_context_error!(MyError);
102///
103/// fn do_work() -> Result<String, MyError> {
104///     expect_error("Failed to do work", || {
105///         let cfg = std::fs::read_to_string("config.toml")?;
106///         Ok(cfg)
107///     })
108/// }
109/// ```
110#[inline(always)]
111pub fn expect_error<T, E>(
112    msg: impl Into<Cow<'static, str>>,
113    body: impl FnOnce() -> Result<T, Frame>,
114) -> Result<T, E>
115where
116    E: From<(Cow<'static, str>, Frame)>,
117{
118    body().map_err(|context| (msg.into(), context).into())
119}