apiresponse 0.2.1

A flexible API response wrapper with framework-agnostic support
Documentation
# apiresponse

A Rust library for standardized API error handling with derive macro support.

## Features

- **Derive Macro**: Implement `Response` trait automatically via `#[derive(Response)]`
- **Explicit Error Codes**: Every error variant requires an explicit code for API stability
- **Transparent Error Delegation**: Proper error propagation through `transparent` variants
- **HTTP Status Code Support**: Attach HTTP status codes to error variants
- **Axum Integration**: Built-in support for Axum web framework
- **Type-safe**: Compile-time validation of error codes

## Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
apiresponse = "0.2.1"
thiserror = "1.0"  # For error definitions
```

For Axum integration:

```toml
[dependencies]
apiresponse = { version = "0.2.1", features = ["axum"] }
```

## Quick Start

### 1. Define Your Error Type

```rust
use apiresponse::Response;
use thiserror::Error;

#[derive(Debug, Error, Response)]
pub enum AuthError {
    #[error("User not found")]
    #[response(code = 1000, status = 404)]
    UserNotFound,

    #[error("Invalid password")]
    #[response(code = 1001, status = 401)]
    InvalidPassword,
}
```

### 2. Use the Error

```rust
let error = AuthError::UserNotFound;

// Error code (from #[response(code = ...)])
println!("{}", error.error_code());
// Output: 1000

// Error message (from #[error(...)]) / Display
println!("{}", error.message());
// Output: User not found

// HTTP status code
println!("{}", error.http_status_code());
// Output: 404
```

### 3. Convert to API Response

```rust
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": "User not found",
//   "data": null
// }
```

## Core Features

### 🔢 Explicit Error Codes

Every variant must specify an error code. No implicit numbering means API contracts stay stable:

```rust
#[derive(Debug, Error, Response)]
pub enum PaymentError {
    #[error("Insufficient balance")]
    #[response(code = 2000)]
    InsufficientBalance,

    #[error("Payment failed: {0}")]
    #[response(code = 2001)]
    PaymentFailed(String),

    #[error("Order not found")]
    #[response(code = 2002, status = 404)]
    OrderNotFound,
}
```

### 🔄 Transparent Error Delegation

Delegates `error_code()`, `message()`, and `http_status_code()` to the wrapped inner error:

```rust
#[derive(Debug, Error, Response)]
pub enum AppError {
    #[error("Internal server error")]
    #[response(code = 5000, status = 500)]
    Internal,

    #[error(transparent)]
    #[response(transparent)]
    Auth(#[from] AuthError),

    #[error(transparent)]
    #[response(transparent)]
    Database(#[from] DbError),
}

// When AppError::Auth(AuthError::UserNotFound) occurs:
let error = AppError::Auth(AuthError::UserNotFound);
error.error_code()      // → 1000 (from AuthError)
error.message()         // → "User not found" (from AuthError)
error.http_status_code() // → 404 (from AuthError)
```

### 🌐 HTTP Status Codes

Optionally attach HTTP status codes to variants. Defaults to `200`:

```rust
#[derive(Debug, Error, Response)]
pub enum DbError {
    #[error("Connection failed")]
    #[response(code = 3000, status = 503)]
    ConnectionFailed,

    #[error("Data not found")]
    #[response(code = 3001, status = 404)]
    RecordNotFound,
}
```

## Common Usage Patterns

### RESTful API Error Handling

```rust
#[derive(Debug, Error, Response)]
pub enum UserError {
    #[error("User not found")]
    #[response(code = 1000, status = 404)]
    NotFound,

    #[error("Permission denied")]
    #[response(code = 1001, 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))
}
```

### Error Aggregation

```rust
#[derive(Debug, Error, Response)]
pub enum AppError {
    #[error(transparent)]
    #[response(transparent)]
    User(#[from] UserError),

    #[error(transparent)]
    #[response(transparent)]
    Database(#[from] DbError),

    #[error("Internal error")]
    #[response(code = 9000, status = 500)]
    Internal,
}
```

## Axum Integration

```rust
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));
```

## API Reference

### Response Trait

```rust
pub trait Response: Display {
    /// Returns the error code
    fn error_code(&self) -> u64;

    /// Returns the error message via the Display implementation
    fn message(&self) -> String {
        self.to_string()
    }

    /// Returns the HTTP status code (defaults to 200)
    fn http_status_code(&self) -> u16 {
        200
    }
}
```

### ApiResponse Struct

```rust
#[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 data
- `ApiResponse::ok()` — Create empty success response
- `ApiResponse::from_error(error)` — Create error response from a `Response` implementor

### Attributes

#### Variant-level Attributes

```rust
#[response(code = error_code, status = http_status)]
//        ^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^^
//        Required             Optional (default 200)

#[response(transparent)]  // Delegate to inner error (code and status not needed)
```

## Design Philosophy

- **Explicit over Implicit**: Error codes must be explicitly assigned for API stability
- **Minimal by Default**: Only three trait methods — `error_code()`, `message()`, `http_status_code()`
- **Type Safety**: Compile-time validation ensures every variant has an error code
- **Framework Agnostic**: Core library has no web framework dependencies; Axum is optional

## Requirements

- Rust 1.70 or later
- `thiserror` for error definitions (recommended)
- `serde` and `serde_json` for serialization

## License

MIT OR Apache-2.0

## Related Projects

- [thiserror]https://github.com/dtolnay/thiserror — Error derive macros
- [anyhow]https://github.com/dtolnay/anyhow — Flexible error handling