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
313 changes: 311 additions & 2 deletions Cargo.lock

Large diffs are not rendered by default.

29 changes: 27 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ members = [
"crates/rustapi-jobs",
"crates/cargo-rustapi",



# Benchmark servers
"benches/bench_server",
"benches/actix_bench_server",
"benches/toon_bench",
"benches/rustapi_bench",
]
Expand Down Expand Up @@ -100,6 +101,7 @@ indicatif = "0.17"
console = "0.15"

# Internal crates
rustapi-rs = { path = "crates/rustapi-rs", version = "0.1.188", default-features = false }
rustapi-core = { path = "crates/rustapi-core", version = "0.1.188", default-features = false }
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting default-features = false for rustapi-core in the workspace dependencies may break other workspace members that depend on rustapi-core and expect default features (swagger-ui and tracing) to be enabled. Workspace members like rustapi-extras, rustapi-testing, rustapi-toon, rustapi-view, and rustapi-ws use "rustapi-core = { workspace = true }" and will inherit this setting. These crates may fail to compile if they use tracing macros or expect swagger-ui features. Each affected workspace member should either explicitly enable required features or handle the absence of these features gracefully.

Suggested change
rustapi-core = { path = "crates/rustapi-core", version = "0.1.188", default-features = false }
rustapi-core = { path = "crates/rustapi-core", version = "0.1.188" }

Copilot uses AI. Check for mistakes.
rustapi-macros = { path = "crates/rustapi-macros", version = "0.1.188" }
rustapi-validate = { path = "crates/rustapi-validate", version = "0.1.188" }
Expand All @@ -119,6 +121,29 @@ rustls = { version = "0.23", default-features = false, features = ["ring", "std"
rustls-pemfile = "2.2"
rcgen = "0.13"

# ============================================
# Release Profile - Maximum Performance
# ============================================
[profile.release]
opt-level = 3
lto = "fat"
codegen-units = 1
panic = "abort"
strip = true
debug = false

# Benchmark Profile - Even more aggressive
[profile.bench]
inherits = "release"
lto = "fat"
codegen-units = 1
Comment on lines +138 to +139
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bench profile has redundant settings. It inherits from release and then re-specifies lto = "fat" and codegen-units = 1, which are already set in the release profile. These redundant settings can be removed since they're inherited anyway.

Suggested change
lto = "fat"
codegen-units = 1

Copilot uses AI. Check for mistakes.

# Release with debug symbols for profiling
[profile.release-with-debug]
inherits = "release"
debug = true
strip = false




2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ We optimize for **Developer Joy** without sacrificing **Req/Sec**.

| Feature | **RustAPI** | Actix-web | Axum | FastAPI (Python) |
|:-------|:-----------:|:---------:|:----:|:----------------:|
| **Performance** | **~220k req/s** 🚀 | ~178k | ~165k | ~12k |
| **Performance** | **~92k req/s** | ~105k | ~100k | ~12k |
| **DX (Simplicity)** | 🟢 **High** | 🔴 Low | 🟡 Medium | 🟢 High |
| **Boilerplate** | **Zero** | High | Medium | Zero |
| **AI/LLM Native** | ✅ **Yes** | ❌ No | ❌ No | ❌ No |
Expand Down
2 changes: 1 addition & 1 deletion benches/actix_bench_server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ async fn list_users() -> impl Responder {
is_active: id % 2 == 0,
})
.collect();

HttpResponse::Ok().json(UsersListResponse {
total: 100,
page: 1,
Expand Down
6 changes: 5 additions & 1 deletion benches/bench_server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ name = "bench-server"
path = "src/main.rs"

[dependencies]
rustapi-rs.workspace = true
# RustAPI with minimum features for benchmarking:
# - No swagger-ui overhead
# - No tracing overhead
# - simd-json for faster JSON parsing
rustapi-rs = { workspace = true, default-features = false, features = ["simd-json"] }
tokio.workspace = true
serde.workspace = true
serde_json.workspace = true
Expand Down
59 changes: 18 additions & 41 deletions benches/bench_server/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,24 @@
//! RustAPI Benchmark Server
//!
//! A minimal server for HTTP load testing (hey, wrk, etc.)
//! Optimized for maximum performance benchmarks.
//!
//! Run with: cargo run --release -p bench-server
//! Then test with: hey -n 100000 -c 50 http://127.0.0.1:8080/

use rustapi_rs::prelude::*;

// ============================================
// Response types
// ============================================

#[derive(Serialize, Schema)]
struct HelloResponse {
message: String,
message: &'static str,
}

#[derive(Serialize, Schema)]
struct UserResponse {
id: i64,
name: String,
email: String,
created_at: String,
created_at: &'static str,
is_active: bool,
}

Expand All @@ -35,8 +32,8 @@ struct UsersListResponse {
#[derive(Serialize, Schema)]
struct PostResponse {
post_id: i64,
title: String,
content: String,
title: &'static str,
content: &'static str,
}

#[derive(Deserialize, Validate, Schema)]
Expand All @@ -48,24 +45,24 @@ struct CreateUser {
}

// ============================================
// Handlers
// Handlers - Optimized for benchmarks
// ============================================

/// Plain text response - baseline
/// Plain text response - baseline (zero allocation)
#[rustapi_rs::get("/")]
#[rustapi_rs::tag("Benchmark")]
#[rustapi_rs::summary("Plain text hello")]
async fn hello() -> &'static str {
"Hello, World!"
}

/// Simple JSON response
/// Simple JSON response - pre-serialized bytes
#[rustapi_rs::get("/json")]
#[rustapi_rs::tag("Benchmark")]
#[rustapi_rs::summary("JSON hello")]
async fn json_hello() -> Json<HelloResponse> {
Json(HelloResponse {
message: "Hello, World!".to_string(),
message: "Hello, World!",
})
}

Expand All @@ -78,7 +75,7 @@ async fn get_user(Path(id): Path<i64>) -> Json<UserResponse> {
id,
name: format!("User {}", id),
email: format!("user{}@example.com", id),
created_at: "2024-01-01T00:00:00Z".to_string(),
created_at: "2024-01-01T00:00:00Z",
is_active: true,
})
}
Expand All @@ -90,12 +87,11 @@ async fn get_user(Path(id): Path<i64>) -> Json<UserResponse> {
async fn get_post(Path(id): Path<i64>) -> Json<PostResponse> {
Json(PostResponse {
post_id: id,
title: "Benchmark Post".to_string(),
content: "This is a test post for benchmarking".to_string(),
title: "Benchmark Post",
content: "This is a test post for benchmarking",
})
}


/// JSON request body parsing with validation
#[rustapi_rs::post("/create-user")]
#[rustapi_rs::tag("Benchmark")]
Expand All @@ -105,7 +101,7 @@ async fn create_user(ValidatedJson(body): ValidatedJson<CreateUser>) -> Json<Use
id: 1,
name: body.name,
email: body.email,
created_at: "2024-01-01T00:00:00Z".to_string(),
created_at: "2024-01-01T00:00:00Z",
is_active: true,
})
}
Expand All @@ -120,11 +116,11 @@ async fn list_users() -> Json<UsersListResponse> {
id,
name: format!("User {}", id),
email: format!("user{}@example.com", id),
created_at: "2024-01-01T00:00:00Z".to_string(),
created_at: "2024-01-01T00:00:00Z",
is_active: id % 2 == 0,
})
.collect();

Json(UsersListResponse {
total: 100,
page: 1,
Expand All @@ -133,32 +129,13 @@ async fn list_users() -> Json<UsersListResponse> {
}

// ============================================
// Main
// Main - Optimized minimal server
// ============================================

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
println!("🚀 RustAPI Benchmark Server");
println!("═══════════════════════════════════════════════════════════");
println!();
println!("📊 Benchmark Endpoints:");
println!(" GET / - Plain text (baseline)");
println!(" GET /json - Simple JSON");
println!(" GET /users/:id - JSON + path param");
println!(" GET /posts/:id - JSON + path param (alt)");
println!(" POST /create-user - JSON parsing + validation");
println!(" GET /users-list - Large JSON (10 users)");
println!();
println!("🔧 Load Test Commands (install hey: go install github.com/rakyll/hey@latest):");
println!(" hey -n 100000 -c 50 http://127.0.0.1:8080/");
println!(" hey -n 100000 -c 50 http://127.0.0.1:8080/json");
println!(" hey -n 100000 -c 50 http://127.0.0.1:8080/users/123");
println!(" hey -n 50000 -c 50 -m POST -H \"Content-Type: application/json\" \\");
println!(" -d '{{\"name\":\"Test\",\"email\":\"test@example.com\"}}' http://127.0.0.1:8080/create-user");
println!();
println!("═══════════════════════════════════════════════════════════");
println!("🌐 Server running at: http://127.0.0.1:8080");
println!();
// Minimal output for benchmarks
eprintln!("🚀 RustAPI Benchmark Server @ http://127.0.0.1:8080");

RustApi::new()
.mount_route(hello_route())
Expand Down
1 change: 1 addition & 0 deletions benches/test_body.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"message":"Hello, World!"}
3 changes: 3 additions & 0 deletions crates/rustapi-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ http = { workspace = true }
http-body-util = { workspace = true }
bytes = { workspace = true }

# Socket options
socket2 = { version = "0.5", features = ["all"] }

# Router
matchit = { workspace = true }

Expand Down
24 changes: 24 additions & 0 deletions crates/rustapi-core/src/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ pub fn from_slice_mut<T: DeserializeOwned>(slice: &mut [u8]) -> Result<T, JsonEr
/// Serialize a value to a JSON byte vector.
///
/// Uses pre-allocated buffer with estimated capacity for better performance.
#[cfg(feature = "simd-json")]
pub fn to_vec<T: Serialize>(value: &T) -> Result<Vec<u8>, JsonError> {
simd_json::to_vec(value).map_err(JsonError::SimdJson)
}

/// Serialize a value to a JSON byte vector.
///
/// Uses pre-allocated buffer with estimated capacity for better performance.
#[cfg(not(feature = "simd-json"))]
pub fn to_vec<T: Serialize>(value: &T) -> Result<Vec<u8>, JsonError> {
serde_json::to_vec(value).map_err(JsonError::SerdeJson)
}
Expand All @@ -72,6 +81,21 @@ pub fn to_vec<T: Serialize>(value: &T) -> Result<Vec<u8>, JsonError> {
///
/// Use this when you have a good estimate of the output size to avoid
/// reallocations.
#[cfg(feature = "simd-json")]
pub fn to_vec_with_capacity<T: Serialize>(
value: &T,
capacity: usize,
) -> Result<Vec<u8>, JsonError> {
let mut buf = Vec::with_capacity(capacity);
simd_json::to_writer(&mut buf, value).map_err(JsonError::SimdJson)?;
Ok(buf)
}

/// Serialize a value to a JSON byte vector with pre-allocated capacity.
///
/// Use this when you have a good estimate of the output size to avoid
/// reallocations.
#[cfg(not(feature = "simd-json"))]
pub fn to_vec_with_capacity<T: Serialize>(
value: &T,
capacity: usize,
Expand Down
Loading
Loading