From 899b32c9755f305afe3ead446d33132c75ed831e Mon Sep 17 00:00:00 2001
From: RA <70325462+RAprogramm@users.noreply.github.com>
Date: Wed, 17 Sep 2025 15:25:04 +0700
Subject: [PATCH] docs: capture post-0.4 updates
---
CHANGELOG.md | 25 +++++++++++++++++--
README.md | 60 +++++++++++++++++++++++++++++++++++++++++++---
README.template.md | 60 +++++++++++++++++++++++++++++++++++++++++++---
src/lib.rs | 45 +++++++++++++++++++++++++++++-----
4 files changed, 176 insertions(+), 14 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b990fe7..627579c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,8 +4,29 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
### Added
-- `#[error(transparent)]` support in the derive macro: validates wrapper shape,
- delegates `Display`/`source` to the inner error, and works with `#[from]`.
+- Re-exported `thiserror::Error` as `masterror::Error`, making it possible to
+ derive domain errors without an extra dependency. The derive supports
+ `#[from]` conversions, validates `#[error(transparent)]` wrappers, and mirrors
+ `thiserror`'s ergonomics.
+- Added `BrowserConsoleError::context()` for retrieving browser-provided
+ diagnostics when console logging fails.
+
+### Changed
+- README generation now pulls from crate metadata via the build script while
+ staying inert during `cargo package`, preventing dirty worktrees in release
+ workflows.
+
+### Documentation
+- Documented deriving custom errors via `masterror::Error` and expanded the
+ browser console section with context-handling guidance.
+- Added a release checklist and described the automated README sync process.
+
+### Tests
+- Added regression tests covering derive behaviour (including `#[from]` and
+ transparent wrappers) and ensuring the README stays in sync with its
+ template.
+- Added a guard test that enforces the `AppResult<_>` alias over raw
+ `Result<_, AppError>` usages within the crate.
## [0.4.0] - 2025-09-15
### Added
diff --git a/README.md b/README.md
index 1912398..ec1421f 100644
--- a/README.md
+++ b/README.md
@@ -34,6 +34,7 @@ masterror = { version = "0.4.0", default-features = false }
# ] }
~~~
+*Unreleased: derive custom errors via `#[derive(Error)]` (`use masterror::Error;`) and inspect browser logging failures with `BrowserConsoleError::context()`.*
*Since v0.4.0: optional `frontend` feature for WASM/browser console logging.*
*Since v0.3.0: stable `AppCode` enum and extended `ErrorResponse` with retry/authentication metadata.*
@@ -47,8 +48,9 @@ masterror = { version = "0.4.0", default-features = false }
- **Opt-in integrations.** Zero default features; you enable what you need.
- **Clean wire contract.** `ErrorResponse { status, code, message, details?, retry?, www_authenticate? }`.
- **One log at boundary.** Log once with `tracing`.
-- **Less boilerplate.** Built-in conversions, compact prelude,
- derive macro support for transparent wrappers via `#[error(transparent)]`.
+- **Less boilerplate.** Built-in conversions, compact prelude, and the
+ `masterror::Error` re-export of `thiserror::Error` with `#[from]` /
+ `#[error(transparent)]` support.
- **Consistent workspace.** Same error surface across crates.
@@ -102,6 +104,49 @@ fn do_work(flag: bool) -> AppResult<()> {
+
+ Derive custom errors
+
+~~~rust
+use std::io;
+
+use masterror::Error;
+
+#[derive(Debug, Error)]
+#[error("I/O failed: {source}")]
+pub struct DomainError {
+ #[from]
+ #[source]
+ source: io::Error,
+}
+
+#[derive(Debug, Error)]
+#[error(transparent)]
+pub struct WrappedDomainError(
+ #[from]
+ #[source]
+ DomainError
+);
+
+fn load() -> Result<(), DomainError> {
+ Err(io::Error::other("disk offline").into())
+}
+
+let err = load().unwrap_err();
+assert_eq!(err.to_string(), "I/O failed: disk offline");
+
+let wrapped = WrappedDomainError::from(err);
+assert_eq!(wrapped.to_string(), "I/O failed: disk offline");
+~~~
+
+- `use masterror::Error;` re-exports `thiserror::Error`.
+- `#[from]` automatically implements `From<...>` while ensuring wrapper shapes are
+ valid.
+- `#[error(transparent)]` enforces single-field wrappers that forward
+ `Display`/`source` to the inner error.
+
+
+
Error response payload
@@ -131,7 +176,14 @@ assert_eq!(resp.status, 401);
assert!(payload.is_object());
#[cfg(target_arch = "wasm32")]
- err.log_to_browser_console()?;
+ {
+ if let Err(console_err) = err.log_to_browser_console() {
+ eprintln!(
+ "failed to log to browser console: {:?}",
+ console_err.context()
+ );
+ }
+ }
Ok(())
}
@@ -139,6 +191,8 @@ assert_eq!(resp.status, 401);
- On non-WASM targets `log_to_browser_console` returns
`BrowserConsoleError::UnsupportedTarget`.
+- `BrowserConsoleError::context()` exposes optional browser diagnostics for
+ logging/telemetry when console logging fails.
diff --git a/README.template.md b/README.template.md
index 82dc5fc..735285a 100644
--- a/README.template.md
+++ b/README.template.md
@@ -31,6 +31,7 @@ masterror = { version = "{{CRATE_VERSION}}", default-features = false }
# ] }
~~~
+*Unreleased: derive custom errors via `#[derive(Error)]` (`use masterror::Error;`) and inspect browser logging failures with `BrowserConsoleError::context()`.*
*Since v0.4.0: optional `frontend` feature for WASM/browser console logging.*
*Since v0.3.0: stable `AppCode` enum and extended `ErrorResponse` with retry/authentication metadata.*
@@ -44,8 +45,9 @@ masterror = { version = "{{CRATE_VERSION}}", default-features = false }
- **Opt-in integrations.** Zero default features; you enable what you need.
- **Clean wire contract.** `ErrorResponse { status, code, message, details?, retry?, www_authenticate? }`.
- **One log at boundary.** Log once with `tracing`.
-- **Less boilerplate.** Built-in conversions, compact prelude,
- derive macro support for transparent wrappers via `#[error(transparent)]`.
+- **Less boilerplate.** Built-in conversions, compact prelude, and the
+ `masterror::Error` re-export of `thiserror::Error` with `#[from]` /
+ `#[error(transparent)]` support.
- **Consistent workspace.** Same error surface across crates.
@@ -96,6 +98,49 @@ fn do_work(flag: bool) -> AppResult<()> {
+
+ Derive custom errors
+
+~~~rust
+use std::io;
+
+use masterror::Error;
+
+#[derive(Debug, Error)]
+#[error("I/O failed: {source}")]
+pub struct DomainError {
+ #[from]
+ #[source]
+ source: io::Error,
+}
+
+#[derive(Debug, Error)]
+#[error(transparent)]
+pub struct WrappedDomainError(
+ #[from]
+ #[source]
+ DomainError
+);
+
+fn load() -> Result<(), DomainError> {
+ Err(io::Error::other("disk offline").into())
+}
+
+let err = load().unwrap_err();
+assert_eq!(err.to_string(), "I/O failed: disk offline");
+
+let wrapped = WrappedDomainError::from(err);
+assert_eq!(wrapped.to_string(), "I/O failed: disk offline");
+~~~
+
+- `use masterror::Error;` re-exports `thiserror::Error`.
+- `#[from]` automatically implements `From<...>` while ensuring wrapper shapes are
+ valid.
+- `#[error(transparent)]` enforces single-field wrappers that forward
+ `Display`/`source` to the inner error.
+
+
+
Error response payload
@@ -125,7 +170,14 @@ assert_eq!(resp.status, 401);
assert!(payload.is_object());
#[cfg(target_arch = "wasm32")]
- err.log_to_browser_console()?;
+ {
+ if let Err(console_err) = err.log_to_browser_console() {
+ eprintln!(
+ "failed to log to browser console: {:?}",
+ console_err.context()
+ );
+ }
+ }
Ok(())
}
@@ -133,6 +185,8 @@ assert_eq!(resp.status, 401);
- On non-WASM targets `log_to_browser_console` returns
`BrowserConsoleError::UnsupportedTarget`.
+- `BrowserConsoleError::context()` exposes optional browser diagnostics for
+ logging/telemetry when console logging fails.
diff --git a/src/lib.rs b/src/lib.rs
index 8214c92..cbb406a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -203,8 +203,10 @@ pub use app_error::{AppError, AppResult};
pub use code::AppCode;
pub use kind::AppErrorKind;
pub use response::{ErrorResponse, RetryAdvice};
-#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
-/// Derive macro replicating the ergonomics of `thiserror::Error`.
+/// Derive macro re-export providing the same ergonomics as `thiserror::Error`.
+///
+/// Supports `#[from]` conversions and `#[error(transparent)]` wrappers out of
+/// the box while keeping compile-time validation of wrapper shapes.
///
/// ```
/// use std::error::Error as StdError;
@@ -218,11 +220,42 @@ pub use response::{ErrorResponse, RetryAdvice};
/// message: &'static str
/// }
///
-/// let err = MiniError {
+/// #[derive(Debug, Error)]
+/// #[error("wrapper -> {0}")]
+/// struct MiniWrapper(
+/// #[from]
+/// #[source]
+/// MiniError
+/// );
+///
+/// #[derive(Debug, Error)]
+/// #[error(transparent)]
+/// struct MiniTransparent(#[from] MiniError);
+///
+/// let wrapped = MiniWrapper::from(MiniError {
/// code: 500,
/// message: "boom"
-/// };
-/// assert_eq!(err.to_string(), "500: boom");
-/// assert!(StdError::source(&err).is_none());
+/// });
+/// assert_eq!(wrapped.to_string(), "wrapper -> 500: boom");
+/// assert_eq!(
+/// StdError::source(&wrapped).map(|err| err.to_string()),
+/// Some(String::from("500: boom"))
+/// );
+///
+/// let expected_source = StdError::source(&MiniError {
+/// code: 503,
+/// message: "oops"
+/// })
+/// .map(|err| err.to_string());
+///
+/// let transparent = MiniTransparent::from(MiniError {
+/// code: 503,
+/// message: "oops"
+/// });
+/// assert_eq!(transparent.to_string(), "503: oops");
+/// assert_eq!(
+/// StdError::source(&transparent).map(|err| err.to_string()),
+/// expected_source
+/// );
/// ```
pub use thiserror::Error;