| Crates.io | apiresponse |
| lib.rs | apiresponse |
| version | 0.1.1 |
| created_at | 2025-12-21 10:10:01.467864+00 |
| updated_at | 2026-01-07 07:10:16.044469+00 |
| description | A flexible API response wrapper with framework-agnostic support |
| homepage | |
| repository | https://github.com/zhenglongbing/api-response |
| max_upload_size | |
| id | 1997793 |
| size | 77,129 |
A Rust library for standardized API error handling with automatic module-based error code management and message formatting.
[module] message formattransparent variantsAdd to your Cargo.toml:
[dependencies]
apiresponse = "0.1"
thiserror = "1.0" # For error definitions
For web framework integration:
[dependencies]
apiresponse = { version = "0.1", features = ["axum"] } # or "actix", "poem"
use api_response::Response;
use thiserror::Error;
#[derive(Debug, Error, Response)]
#[response(module = "auth", base = 1000)]
pub enum AuthError {
#[error("User not found")]
#[response(status = 404)]
UserNotFound,
#[error("Invalid password")]
#[response(status = 401)]
InvalidPassword,
}
let error = AuthError::UserNotFound;
// Automatic module prefix
println!("{}", error.message());
// Output: [auth] User not found
// Error code (auto-numbered)
println!("{}", error.error_code());
// Output: 1000
// HTTP status code
println!("{}", error.http_status_code());
// Output: 404
use apiresponse::ApiResponse;
// Method 1: From error
let response = ApiResponse::from_error(error);
// Method 2: From Result
let result: Result<String, AuthError> = Err(AuthError::UserNotFound);
let response: ApiResponse = result.into();
// Serialize to JSON
let json = serde_json::to_string(&response).unwrap();
// {
// "code": 1000,
// "message": "[auth] User not found",
// "data": null
// }
Error messages automatically include module paths:
#[derive(Debug, Error, Response)]
#[response(module = "payment.stripe", base = 2000)]
pub enum PaymentError {
#[error("Insufficient balance")]
InsufficientBalance, // Error code: 2000
}
let err = PaymentError::InsufficientBalance;
err.message() // → "[payment.stripe] Insufficient balance"
err.module_path() // → "payment.stripe"
err.raw_message() // → "Insufficient balance"
Proper error propagation when wrapping errors:
#[derive(Debug, Error, Response)]
#[response(module = "api")]
pub enum ApiError {
#[error(transparent)]
#[response(transparent)]
Auth(#[from] AuthError), // Delegates to inner error
}
// When ApiError::Auth(AuthError::UserNotFound) occurs:
let error = ApiError::Auth(AuthError::UserNotFound);
error.message() // → "[auth] User not found" (from AuthError)
error.module_path() // → "auth" (from AuthError)
error.error_code() // → 1000 (from AuthError)
base#[derive(Debug, Error, Response)]
#[response(module = "user", base = 1000)]
pub enum UserError {
#[error("Not found")]
NotFound, // Code: 1000
#[error("Unauthorized")]
Unauthorized, // Code: 1001
#[error("Forbidden")]
Forbidden, // Code: 1002
}
base)#[derive(Debug, Error, Response)]
#[response(module = "validation")]
pub enum ValidationError {
#[error("Invalid email")]
#[response(code = 4001, status = 400)]
InvalidEmail,
#[error("Weak password")]
#[response(code = 4002, status = 400)]
WeakPassword,
}
#[derive(Debug, Error, Response)]
#[response(module = "auth", base = 1000)]
pub enum AuthError {
#[error("User not found")]
NotFound, // Code: 1000 (auto)
#[error("Locked")]
#[response(code = 1099)] // Override: 1099 instead of 1001
Locked,
}
#[derive(Debug, Error, Response)]
#[response(base = 6000)]
pub enum SimpleError {
#[error("Generic error")]
GenericError, // Code: 6000, no module prefix
#[error("Another error")]
AnotherError, // Code: 6001, no module prefix
}
// Usage
let err = SimpleError::GenericError;
err.message() // → "Generic error" (no module prefix)
err.module_path() // → ""
#[derive(Debug, Error, Response)]
#[response(module = "user", base = 1000)]
pub enum UserError {
#[error("User not found")]
#[response(status = 404)]
NotFound,
#[error("Permission denied")]
#[response(status = 403)]
PermissionDenied,
}
// In API handler
async fn get_user(id: u64) -> Result<Json<User>, UserError> {
let user = find_user(id).ok_or(UserError::NotFound)?;
check_permission(&user).ok_or(UserError::PermissionDenied)?;
Ok(Json(user))
}
#[derive(Debug, Error, Response)]
#[response(module = "app")]
pub enum AppError {
#[error(transparent)]
#[response(transparent)]
User(#[from] UserError),
#[error(transparent)]
#[response(transparent)]
Database(#[from] DbError),
#[error("Internal error")]
#[response(code = 5000, status = 500)]
Internal,
}
use axum::{routing::get, Router};
use apiresponse::ApiResponse;
async fn handler() -> ApiResponse {
// ApiResponse automatically implements IntoResponse
// HTTP status code is set from the status_code field
ApiResponse::from_error(AuthError::UserNotFound)
}
let app = Router::new().route("/user", get(handler));
use actix_web::{get, HttpResponse};
use apiresponse::ApiResponse;
#[get("/user")]
async fn handler() -> ApiResponse {
// ApiResponse implements Responder
ApiResponse::from_error(AuthError::UserNotFound)
}
use poem::{get, handler, Route};
use apiresponse::ApiResponse;
#[handler]
async fn handler() -> ApiResponse {
// ApiResponse implements IntoResponse
ApiResponse::from_error(AuthError::UserNotFound)
}
let app = Route::new().at("/user", get(handler));
We provide comprehensive examples:
# Complete feature demonstration
cargo run --example complete_example
# Web framework integration
cargo run --example web_framework_example
pub trait Response: Display {
/// Returns the error code
fn error_code(&self) -> u64;
/// Returns the module path (e.g., "auth.login")
fn module_path(&self) -> &'static str;
/// Returns the raw error message without module prefix
fn raw_message(&self) -> String;
/// Returns the formatted message with module prefix: "[module] message"
fn message(&self) -> String;
/// Returns the HTTP status code
fn http_status_code(&self) -> u16;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiResponse {
pub code: u64,
pub message: String,
pub data: serde_json::Value,
#[serde(skip)]
pub status_code: u16,
}
Methods:
ApiResponse::success(data) - Create success response with dataApiResponse::success_with_message(data, message) - Success with custom messageApiResponse::ok() - Create empty success response#[response(module = "module_name", base = base_code)]
// ^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^
// Optional (default "") Optional (if omitted, variants must specify code)
#[response(code = error_code, status = http_status, message = "custom_message")]
// ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^
// Optional (override) Optional (default 200) Optional (override Display)
#[response(transparent)] // Delegate to inner error
// Get module path
let module = error.module_path(); // "auth.login"
// Raw vs Formatted messages
error.raw_message() // → "User not found"
error.message() // → "[auth.login] User not found"
#[response(module = "api.v1.auth")]
pub enum AuthError { /* ... */ }
// Messages will be prefixed with [api.v1.auth]
thiserror for error definitions (recommended)serde and serde_json for serializationMIT OR Apache-2.0
Contributions are welcome! Please feel free to submit a Pull Request.