Skip to content

Commit

Permalink
add rustdoc for public items
Browse files Browse the repository at this point in the history
Signed-off-by: Bugen Zhao <[email protected]>
  • Loading branch information
BugenZhao committed Dec 7, 2023
1 parent 18724cd commit 43e4071
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 14 deletions.
259 changes: 250 additions & 9 deletions derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,39 @@ use syn::{parse_macro_input, DeriveInput};
mod expand;
mod thiserror;

/// Generates constructor functions for different variants of the error type.
///
/// The arguments of the constructor functions can be any types that implement
/// [`Into`] for the corresponding fields of the variant, enabling convenient
/// construction of the error type.
///
/// # Example
///
/// ```no_run
/// #[derive(Debug, thiserror::Error, thiserror_ext::Construct)]
/// enum Error {
/// #[error("unsupported feature: {0}")]
/// UnsupportedFeature { name: String },
///
/// #[error("internal error: {0}")]
/// #[construct(skip)] // to skip generating the constructor
/// InternalError(String),
/// }
///
/// // Any type that implements `Into<String>` is accepted as the argument.
/// let _: Error = Error::unsupported_feature("foo");
/// ```
///
/// # New type
///
/// If a new type is specified with `#[thiserror_ext(newtype(..))]`, the
/// constructors will be implemented on the new type instead.
///
/// See the documentation of [`thiserror_ext::Box`] or [`thiserror_ext::Arc`]
/// for more details.
///
/// [`thiserror_ext::Box`]: derive@Box
/// [`thiserror_ext::Arc`]: derive@Arc
#[proc_macro_derive(Construct, attributes(thiserror_ext, construct))]
pub fn derive_construct(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
Expand All @@ -14,6 +47,52 @@ pub fn derive_construct(input: TokenStream) -> TokenStream {
.into()
}

/// Generates extension traits for converting the external error type into the
/// the provided one, with extra context.
///
/// This can be helpful when the external error type does not provide enough
/// context for the application. `thiserror` does not allow specifying `#[from]`
/// on the error field if there're extra fields in the variant.
///
/// The extension trait is only generated when there's a field named `source`
/// or marked with `#[source]` but not `#[from]`.
///
/// # Example
///
/// ```no_run
/// #[derive(Debug, thiserror::Error, thiserror_ext::ContextInto)]
/// enum Error {
/// #[error("cannot parse int from `{from}`")]
/// ParseInt {
/// source: std::num::ParseIntError,
/// from: String,
/// },
///
/// #[error("cannot parse float from `{from}`")]
/// #[context_into(skip)] // to skip generating the extension
/// ParseFloat {
/// source: std::num::ParseIntError,
/// from: String,
/// },
/// }
///
/// // Specify the `from` as "foo" and convert it into `Error::ParseInt`.
/// let _: Error = "foo".parse::<i32>().unwrap_err().into_parse_int("foo");
///
/// // Can also be called on `Result<T, ExternalError>`
/// let _: Result<i32, Error> = "foo".parse().into_parse_int("foo");
/// ```
///
/// # New type
///
/// If a new type is specified with `#[thiserror_ext(newtype(..))]`, the
/// extensions will convert the errors into the new type instead.
///
/// See the documentation of [`thiserror_ext::Box`] or [`thiserror_ext::Arc`]
/// for more details.
///
/// [`thiserror_ext::Box`]: derive@Box
/// [`thiserror_ext::Arc`]: derive@Arc
#[proc_macro_derive(ContextInto, attributes(thiserror_ext, context_into))]
pub fn derive_context_into(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
Expand All @@ -23,29 +102,191 @@ pub fn derive_context_into(input: TokenStream) -> TokenStream {
.into()
}

#[proc_macro_derive(Box, attributes(thiserror_ext))]
pub fn derive_box(input: TokenStream) -> TokenStream {
/// Generates macros for different variants of the error type to construct
/// it or directly bail out.
///
/// # Inline formatting
///
/// It's common to put a message string in the error variant. With this macro,
/// one can directly format the message in the macro call, instead of calling
/// [`format!`].
///
/// To mark a field as the message to be formatted, name it `message` or mark
/// it with `#[message]`. The message field can be any type that implements
/// `From<String>`.
///
/// ## Example
///
/// ```no_run
/// #[derive(Debug, thiserror::Error, thiserror_ext::Macro)]
/// enum Error {
/// #[error("internal error: {msg}")]
/// Internal { #[message] msg: Box<str> },
/// }
///
/// // Equivalent to `Error::Internal { msg: format!(..).into() }`.
/// let _: Error = internal!("{} is a bad number", 42);
///
/// // Equivalent to `return Err(Error::Internal { msg: format!(..).into() }.into())`.
/// bail_internal!("{} is a bad number", 42);
/// ```
///
/// # Extra fields
///
/// If there're extra fields along with the message field, one can specify
/// the values of them with `field = value` syntax before the message. The
/// values can be any types that implement [`Into`] for the corresponding
/// fields.
///
/// Fields can be omitted, in which case [`Default::default()`] will be used.
///
/// ## Example
///
/// ```no_run
/// #[derive(Debug, thiserror::Error, thiserror_ext::Macro)]
/// #[error("not yet implemented: {message}")]
/// struct NotYetImplemented {
/// issue: Option<i32>,
/// pr: Option<i32>,
/// message: String,
/// }
///
/// let _: Error = not_yet_implemented!(issue = 42, pr = 88, "foo");
/// let _: Error = not_yet_implemented!(issue = 42, "foo"); // pr = None
/// let _: Error = not_yet_implemented!(pr = 88, "foo"); // issue = None
/// let _: Error = not_yet_implemented!("foo"); // issue = None, pr = None
/// ```
///
/// # Visibility
///
/// There's a different rule set for the visibility of the macros. The macros
/// generated by this proc-macro are marked with `#[macro_export]` only if the
/// visibility of the error type is `pub`, or they're just re-exported with
/// the same visibility as the error type and only work in the same crate.
///
/// There're some extra configurations to help to better handle the visibility,
/// specified in `#[thiserror_ext(macro(..))]`:
///
/// - `mangle`: mangle the macro names so that they don't conflict with other
/// macros with the same name in the crate root.
/// - `path = "crate::.."`: the path to the current module. When specified,
/// types in the generated macros will use the qualified path like
/// `$crate::foo::bar::Error`, enabling the callers to use the macros without
/// importing the error type.
///
/// # New type
///
/// If a new type is specified with `#[thiserror_ext(newtype(..))]`, the macros
/// will generate the new type instead.
///
/// See the documentation of [`thiserror_ext::Box`] or [`thiserror_ext::Arc`]
/// for more details.
///
/// [`thiserror_ext::Box`]: derive@Box
/// [`thiserror_ext::Arc`]: derive@Arc
#[proc_macro_derive(Macro, attributes(thiserror_ext, message))]
pub fn derive_macro(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);

expand::derive_new_type(&input, DeriveNewType::Box)
expand::derive_macro(&input)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

#[proc_macro_derive(Arc, attributes(thiserror_ext))]
pub fn derive_arc(input: TokenStream) -> TokenStream {
/// Generates a new type that wraps the original error type in a [`struct@Box`].
///
/// Specify the name of the new type with `#[thiserror_ext(newtype(name = ..))]`.
///
/// # Reduce size
///
/// The most common motivation for using this macro is to reduce the size of
/// the original error type. As a sum-type, a [`Result`] is at least as large
/// as its largest variant. Large error type may hurt the performance of a
/// function call returning a [`Result`]. With this macro, the new type always
/// has the same size as a [`struct@Box`].
///
/// On the other hand, returning an error should be an exceptional case in most
/// cases. Therefore, even though boxing the error type may lead to extra
/// allocation, it's usually acceptable.
///
/// ## Example
///
/// ```no_run
/// #[derive(Debug, thiserror::Error, thiserror_ext::Box)]
/// #[thiserror_ext(newtype(name = Error))]
/// enum ErrorKind {
/// #[error("foo")]
/// Foo,
/// #[error("io")]
/// Io(#[from] std::io::Error),
/// }
///
/// // The size of `Error` is one pointer.
/// assert_eq!(std::mem::size_of::<Error>(), std::mem::size_of::<usize>());
///
/// // Convert to `Error`, from `ErrorKind` or other types that can be converted
/// // to `ErrorKind`.
/// let error: Error = ErrorKind::Foo.into();
/// let error: Error = io_error().into();
///
/// // Get the reference or the value of the inner error.
/// let _: &ErrorKind = error.inner();
/// let _: ErrorKind = error.into_inner();
/// ```
///
/// # Backtrace
///
/// Another use case is to capture backtrace when the error is created. Without
/// a new type, one has to manually add a [`Backtrace`] field to each variant
/// of the error type. The new type allows one to capture backtrace in a single
/// place.
///
/// Specify `#[thiserror_ext(newtype(.., backtrace))]` to enable capturing
/// backtrace. The extra backtrace is captured **only if** the original error
/// type does not [`provide`] one. Typically, this should be maintained by the
/// `#[backtrace]` attribute from `thiserror`.
///
/// ## Example
///
/// ```no_run
/// # use std::backtrace::Backtrace;
/// #[derive(Debug, thiserror::Error, thiserror_ext::Box)]
/// #[thiserror_ext(newtype(name = Error, backtrace))]
/// enum ErrorKind {
/// #[error("foo")]
/// Foo,
/// }
///
/// let error: Error = ErrorKind::Foo.into();
/// let backtrace: &Backtrace = std::error::request_ref(&error).unwrap();
/// ```
///
/// [`Backtrace`]: std::backtrace::Backtrace
/// [`provide`]: std::error::Error::provide
#[proc_macro_derive(Box, attributes(thiserror_ext))]
pub fn derive_box(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);

expand::derive_new_type(&input, DeriveNewType::Arc)
expand::derive_new_type(&input, DeriveNewType::Box)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

#[proc_macro_derive(Macro, attributes(thiserror_ext, message))]
pub fn derive_macro(input: TokenStream) -> TokenStream {
/// Generates a new type that wraps the original error type in an [`Arc`].
///
/// Specify the name of the new type with `#[thiserror_ext(newtype(name = ..))]`.
///
/// This is similar to [`thiserror_ext::Box`] but wraps the original error type
/// in an [`Arc`], so that it can always be cloned and shared across threads.
/// See [`thiserror_ext::Box`] for the explanation and examples.
///
/// [`Arc`]: std::sync::Arc
/// [`thiserror_ext::Box`]: derive@Box
#[proc_macro_derive(Arc, attributes(thiserror_ext))]
pub fn derive_arc(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);

expand::derive_macro(&input)
expand::derive_new_type(&input, DeriveNewType::Arc)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
5 changes: 3 additions & 2 deletions src/as_dyn.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// Extension trait for [`std::error::Error`] that casts the error to
/// a trait object.
/// Extension trait for [`Error`] that casts the error to a trait object.
///
/// [`Error`]: std::error::Error
pub trait AsDyn: crate::error_sealed::Sealed {
/// Casts the error to a trait object.
fn as_dyn(&self) -> &(dyn std::error::Error + '_);
Expand Down
1 change: 1 addition & 0 deletions src/backtrace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub trait WithBacktrace {
}

/// Do not capture extra backtrace.
#[derive(Clone, Copy)]
pub struct NoExtraBacktrace;

/// Capture backtrace if the error does not already have one.
Expand Down
3 changes: 2 additions & 1 deletion src/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::sync::Arc;

use crate::backtrace::WithBacktrace;

/// Workaround for https://github.com/rust-lang/rust/issues/117432.
/// A [`Box`] with optional backtrace.
#[derive(Clone)]
#[repr(transparent)]
pub struct ErrorBox<T, B>(Box<(T, B)>);
Expand All @@ -23,6 +23,7 @@ impl<T, B> std::ops::DerefMut for ErrorBox<T, B> {
}
}

/// A [`Arc`] with optional backtrace.
#[repr(transparent)]
pub struct ErrorArc<T, B>(Arc<(T, B)>);

Expand Down
19 changes: 17 additions & 2 deletions src/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,24 @@

use std::{backtrace::Backtrace, fmt};

/// Extension trait for [`std::error::Error`] that provides a [`Report`]
/// that formats the error and its sources in a cleaned-up way.
/// Extension trait for [`Error`] that provides a [`Report`] which formats
/// the error and its sources in a cleaned-up way.
///
/// [`Error`]: std::error::Error
pub trait AsReport: crate::error_sealed::Sealed {
/// Returns a [`Report`] that formats the error and its sources in a
/// cleaned-up way.
///
/// See the documentation for [`Report`] for what the formatting looks
/// like under different options.
///
/// # Example
/// ```no_run
/// use thiserror::AsReport;
///
/// let error = fallible_action().unwrap_err();
/// println!("{}", error.as_report());
/// ```
fn as_report(&self) -> Report<'_>;

/// Converts the error to a [`Report`] and formats it in a compact way.
Expand Down Expand Up @@ -110,6 +123,8 @@ crate::for_dyn_error_types! { impl_as_report }
/// A wrapper around an error that provides a cleaned up error trace for
/// display and debug formatting.
///
/// Constructed using [`AsReport::as_report`].
///
/// # Formatting
///
/// The report can be formatted using [`fmt::Display`] or [`fmt::Debug`],
Expand Down

0 comments on commit 43e4071

Please sign in to comment.