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: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,10 @@ utoipa = "5"

~~~rust
// features = ["frontend"]
use masterror::{AppError, AppErrorKind};
use masterror::{AppError, AppErrorKind, AppResult};
use masterror::frontend::{BrowserConsoleError, BrowserConsoleExt};

fn report() -> Result<(), BrowserConsoleError> {
fn report() -> AppResult<(), BrowserConsoleError> {
let err = AppError::bad_request("missing field");
let payload = err.to_js_value()?;
assert!(payload.is_object());
Expand Down
29 changes: 28 additions & 1 deletion src/app_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,29 @@ pub struct AppError {
}

/// Conventional result alias for application code.
pub type AppResult<T> = Result<T, AppError>;
///
/// The alias defaults to [`AppError`] but accepts a custom error type when the
/// context requires a different domain error.
///
/// # Examples
///
/// ```rust
/// use std::io::Error;
///
/// use masterror::AppResult;
///
/// fn app_logic() -> AppResult<u8> {
/// Ok(7)
/// }
///
/// fn io_logic() -> AppResult<(), Error> {
/// Ok(())
/// }
///
/// assert_eq!(app_logic().unwrap(), 7);
/// assert!(io_logic().is_ok());
/// ```
pub type AppResult<T, E = AppError> = Result<T, E>;

impl AppError {
/// Create a new [`AppError`] with a kind and message.
Expand Down Expand Up @@ -526,15 +548,20 @@ mod tests {
fn err() -> AppResult<u8> {
Err(AppError::internal("x"))
}
fn other_err() -> AppResult<u8, &'static str> {
Err("boom")
}

let a: AppResult<u8> = ok();
let b: AppResult<u8> = err();
let c: AppResult<u8, &'static str> = other_err();

assert_eq!(a.unwrap(), 1);
assert!(b.is_err());
if let Err(e) = b {
assert!(matches!(e.kind, AppErrorKind::Internal));
}
assert_eq!(c.unwrap_err(), "boom");
}

// --- Logging path sanity check -------------------------------------------
Expand Down
8 changes: 5 additions & 3 deletions src/convert/actix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@
//! Err(AppError::new(AppErrorKind::Forbidden, "no access"))
//! }
//!
//! use std::io::Error;
//!
//! #[actix_web::main]
//! async fn main() -> std::io::Result<()> {
//! async fn main() -> AppResult<(), Error> {
//! HttpServer::new(|| App::new().service(forbidden))
//! .bind(("127.0.0.1", 8080))?
//! .run()
Expand Down Expand Up @@ -103,7 +105,7 @@ mod actix_tests {
http::header::{RETRY_AFTER, WWW_AUTHENTICATE}
};

use crate::{AppCode, AppError, AppErrorKind, ErrorResponse};
use crate::{AppCode, AppError, AppErrorKind, AppResult, ErrorResponse};

#[test]
fn maps_status_consistently() {
Expand All @@ -112,7 +114,7 @@ mod actix_tests {
}

#[actix_web::test] // ← вот это
async fn error_response_sets_body_and_headers() -> Result<(), Box<dyn std::error::Error>> {
async fn error_response_sets_body_and_headers() -> AppResult<(), Box<dyn std::error::Error>> {
let err = AppError::unauthorized("no token")
.with_retry_after_secs(7)
.with_www_authenticate("Bearer");
Expand Down
14 changes: 7 additions & 7 deletions src/frontend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ use thiserror::Error;
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;

use crate::{AppError, ErrorResponse};
use crate::{AppError, AppResult, ErrorResponse};

/// Error returned when emitting to the browser console fails or is unsupported.
#[derive(Debug, Error, PartialEq, Eq)]
Expand Down Expand Up @@ -85,20 +85,20 @@ pub enum BrowserConsoleError {
#[cfg_attr(docsrs, doc(cfg(feature = "frontend")))]
pub trait BrowserConsoleExt {
/// Convert the error into a [`JsValue`] suitable for passing to JavaScript.
fn to_js_value(&self) -> Result<JsValue, BrowserConsoleError>;
fn to_js_value(&self) -> AppResult<JsValue, BrowserConsoleError>;

/// Emit the error as a structured payload via `console.error`.
///
/// On non-WASM targets this returns
/// [`BrowserConsoleError::UnsupportedTarget`].
fn log_to_browser_console(&self) -> Result<(), BrowserConsoleError> {
fn log_to_browser_console(&self) -> AppResult<(), BrowserConsoleError> {
let payload = self.to_js_value()?;
log_js_value(&payload)
}
}

impl BrowserConsoleExt for ErrorResponse {
fn to_js_value(&self) -> Result<JsValue, BrowserConsoleError> {
fn to_js_value(&self) -> AppResult<JsValue, BrowserConsoleError> {
#[cfg(target_arch = "wasm32")]
{
to_value(self).map_err(|err| BrowserConsoleError::Serialization {
Expand All @@ -114,7 +114,7 @@ impl BrowserConsoleExt for ErrorResponse {
}

impl BrowserConsoleExt for AppError {
fn to_js_value(&self) -> Result<JsValue, BrowserConsoleError> {
fn to_js_value(&self) -> AppResult<JsValue, BrowserConsoleError> {
#[cfg(target_arch = "wasm32")]
{
let response: ErrorResponse = self.into();
Expand All @@ -129,7 +129,7 @@ impl BrowserConsoleExt for AppError {
}

#[cfg(target_arch = "wasm32")]
fn log_js_value(value: &JsValue) -> Result<(), BrowserConsoleError> {
fn log_js_value(value: &JsValue) -> AppResult<(), BrowserConsoleError> {
let global = js_sys::global();
let console = Reflect::get(&global, &JsValue::from_str("console")).map_err(|err| {
BrowserConsoleError::ConsoleUnavailable {
Expand Down Expand Up @@ -168,7 +168,7 @@ fn log_js_value(value: &JsValue) -> Result<(), BrowserConsoleError> {
}

#[cfg(not(target_arch = "wasm32"))]
fn log_js_value(_value: &JsValue) -> Result<(), BrowserConsoleError> {
fn log_js_value(_value: &JsValue) -> AppResult<(), BrowserConsoleError> {
Err(BrowserConsoleError::UnsupportedTarget)
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//! - [`AppError`] — thin wrapper around a semantic error kind and optional
//! message
//! - [`AppErrorKind`] — stable internal taxonomy of application errors
//! - [`AppResult`] — convenience alias for returning [`AppError`]
//! - [`AppResult`] — convenience result alias (defaults to [`AppError`])
//! - [`ErrorResponse`] — stable wire-level JSON payload for HTTP APIs
//! - [`AppCode`] — public, machine-readable error code for clients
//!
Expand Down