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
298 changes: 23 additions & 275 deletions Cargo.lock

Large diffs are not rendered by default.

12 changes: 3 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ rust-version = "1.78"

[workspace.dependencies]
# Async runtime
tokio = { version = "1.35", features = ["full"] }
tokio = "1.35"

# HTTP
hyper = { version = "1.1", features = ["full"] }
hyper-util = { version = "0.1", features = ["full"] }
hyper = "1.1"
hyper-util = "0.1"
http = "1.0"
http-body-util = "0.1"

Expand All @@ -42,8 +42,6 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

# Middleware
tower = { version = "0.4", features = ["full"] }
tower-http = { version = "0.5", features = ["full"] }
tower-service = "0.3"

# Utilities
Expand All @@ -60,7 +58,6 @@ async-trait = "0.1"
syn = { version = "2.0", features = ["full", "parsing", "extra-traits"] }
quote = "1.0"
proc-macro2 = "1.0"
inventory = "0.3"
linkme = "0.3"

# Validation
Expand All @@ -72,9 +69,6 @@ uuid = { version = "1.6", features = ["v4"] }
# Metrics
prometheus = "0.13"

# OpenAPI
utoipa = { version = "4.2", features = ["uuid", "chrono"] }

# TOON format
toon-format = { version = "0.4", default-features = false }

Expand Down
16 changes: 5 additions & 11 deletions crates/rustapi-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,17 @@ homepage.workspace = true

[dependencies]
# Async
tokio = { workspace = true }
tokio = { workspace = true, features = ["rt", "net", "time", "fs", "macros"] }
futures-util = { workspace = true }
pin-project-lite = { workspace = true }

# HTTP
hyper = { workspace = true }
hyper-util = { workspace = true }
hyper = { workspace = true, features = ["server", "http1"] }
hyper-util = { workspace = true, features = ["tokio"] }
http = { workspace = true }
http-body-util = { workspace = true }
bytes = { workspace = true }

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

# Router
matchit = { workspace = true }

Expand All @@ -38,15 +35,12 @@ simd-json = { version = "0.17", optional = true }
smallvec = "1.13"

# Middleware
tower = { workspace = true }
tower-service = { workspace = true }
tower-http = { workspace = true }

# Utilities
thiserror = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
inventory = { workspace = true }
linkme = { workspace = true }
uuid = { workspace = true }
base64 = "0.22"
Expand All @@ -59,7 +53,7 @@ brotli = { version = "6.0", optional = true }
cookie = { version = "0.18", optional = true }

# Validation
validator = { workspace = true }
validator = { workspace = true, optional = true }
rustapi-validate = { workspace = true }

# Metrics (optional)
Expand All @@ -79,7 +73,6 @@ h3-quinn = { version = "0.0.10", optional = true }
rustls = { workspace = true, optional = true }
rustls-pemfile = { workspace = true, optional = true }
rcgen = { workspace = true, optional = true }
chrono = "0.4.43"

# Replay (feature-gated)
async-trait = { workspace = true, optional = true }
Expand All @@ -95,6 +88,7 @@ async-trait = { workspace = true }
default = ["swagger-ui", "tracing"]
swagger-ui = ["rustapi-openapi/swagger-ui"]
test-utils = []
legacy-validator = ["dep:validator"]
cookies = ["dep:cookie"]
sqlx = ["dep:sqlx"]
metrics = ["dep:prometheus"]
Expand Down
2 changes: 1 addition & 1 deletion crates/rustapi-core/src/middleware/layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use tower::Service;
use tower_service::Service;

/// A boxed middleware function type
#[allow(dead_code)]
Expand Down
12 changes: 9 additions & 3 deletions crates/rustapi-core/src/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, RwLock};
use std::time::{Duration, Instant};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};

/// Configuration for the Status Page
#[derive(Clone, Debug)]
Expand Down Expand Up @@ -97,8 +97,7 @@ impl StatusMonitor {
}
entry.total_latency_ms += duration.as_millis();

let now = chrono::Utc::now().to_rfc3339();
entry.last_access = Some(now);
entry.last_access = Some(format_unix_timestamp());
}
Comment on lines 98 to 101
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

last_access is now set to a unix:<secs> string, which makes the "Last Access" column much less human-readable than the previous RFC3339 timestamp. Consider keeping a readable format (e.g., ISO-8601) or changing the UI to show a relative age ("5s ago") while still avoiding the chrono dependency.

Copilot uses AI. Check for mistakes.

pub fn get_uptime(&self) -> Duration {
Expand Down Expand Up @@ -274,3 +273,10 @@ fn format_duration(d: Duration) -> String {
format!("{}m {}s", minutes, secs)
}
}

fn format_unix_timestamp() -> String {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_else(|_| Duration::from_secs(0));
format!("unix:{}", now.as_secs())
Comment on lines +278 to +281
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

format_unix_timestamp() masks SystemTime errors by returning a synthetic epoch value (unix:0), which can silently produce misleading data. Prefer returning None/"-" on error, or propagating/logging the error so operators can tell the clock is invalid.

Suggested change
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_else(|_| Duration::from_secs(0));
format!("unix:{}", now.as_secs())
match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(duration) => format!("unix:{}", duration.as_secs()),
Err(_) => "-".to_string(),
}

Copilot uses AI. Check for mistakes.
}
2 changes: 2 additions & 0 deletions crates/rustapi-core/src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub trait Validatable {
}

// Blanket implementation for types implementing the external validator::Validate trait
#[cfg(feature = "legacy-validator")]
impl<T: validator::Validate> Validatable for T {
fn do_validate(&self) -> Result<(), ApiError> {
match validator::Validate::validate(self) {
Expand All @@ -20,6 +21,7 @@ impl<T: validator::Validate> Validatable for T {
}

/// Helper to convert validator::ValidationErrors to rustapi_core::error::ApiError
#[cfg(feature = "legacy-validator")]
pub fn convert_validator_errors(errors: validator::ValidationErrors) -> ApiError {
let field_errors =
errors
Expand Down
2 changes: 1 addition & 1 deletion crates/rustapi-extras/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ rustapi-core = { workspace = true }
rustapi-openapi = { workspace = true }

# Async
tokio = { workspace = true }
tokio = { workspace = true, features = ["rt", "sync", "time"] }
futures-util = { workspace = true }
async-trait = { workspace = true }

Expand Down
4 changes: 2 additions & 2 deletions crates/rustapi-macros/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ Enrich your auto-generated documentation.

### `#[derive(Schema)]`
Generates a JSON Schema for the struct, used by `rustapi-openapi`.
*Wraps `utoipa::ToSchema` via `rustapi-openapi` integration.*
*Uses RustAPI's native OpenAPI schema integration.*

### `#[derive(Validate)]`
Generates validation logic.
*Wraps `validator::Validate` via `rustapi-validate` integration.*
*Implements RustAPI v2 validation traits via `rustapi-validate` integration.*
2 changes: 1 addition & 1 deletion crates/rustapi-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub fn derive_schema(input: TokenStream) -> TokenStream {

/// Auto-register a schema type for zero-config OpenAPI.
///
/// Attach this to a `struct` or `enum` that also derives `Schema` (utoipa::ToSchema).
/// Attach this to a `struct` or `enum` that also derives `Schema`.
/// This ensures the type is registered into RustAPI's OpenAPI components even if it is
/// only referenced indirectly (e.g. as a nested field type).
///
Expand Down
2 changes: 1 addition & 1 deletion crates/rustapi-openapi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Automated API specifications and Swagger UI integration for RustAPI.
## How It Works

1. **Reflection**: RustAPI macros collect metadata about your routes (path, method, input types, output types) at compile time
2. **Schema Gen**: Uses `utoipa` to generate JSON Schemas for your Rust structs
2. **Schema Gen**: Uses RustAPI's native schema engine to generate OpenAPI-compatible JSON Schemas
3. **Spec Build**: At runtime, assembles the full OpenAPI 3.0 JSON specification
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

This README still claims the runtime spec build produces an OpenAPI 3.0 JSON spec, but OpenApiSpec::new() emits openapi: "3.1.0". Please update this step (and any related wording) to 3.1 or clarify when 3.0 is produced (legacy mode).

Suggested change
3. **Spec Build**: At runtime, assembles the full OpenAPI 3.0 JSON specification
3. **Spec Build**: At runtime, assembles the full OpenAPI 3.1 JSON specification

Copilot uses AI. Check for mistakes.
4. **UI Serve**: Embeds the Swagger UI assets and serves them at your specified path

Expand Down
3 changes: 1 addition & 2 deletions crates/rustapi-openapi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
//! OpenAPI documentation for RustAPI
//!
//! This crate provides OpenAPI specification generation and Swagger UI serving
//! for RustAPI applications. It wraps `utoipa` internally while providing a
//! clean public API.
//! for RustAPI applications with a native OpenAPI model and schema engine.
//!
//! # Features
//!
Expand Down
6 changes: 3 additions & 3 deletions crates/rustapi-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,13 @@ tokio = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tracing = { workspace = true }
validator = { workspace = true }
validator = { workspace = true, optional = true }
rustapi-openapi = { workspace = true, default-features = false }

[dev-dependencies]
rustapi-core = { workspace = true }
rustapi-macros = { workspace = true }
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
utoipa = { workspace = true }
doc-comment = "0.3"
uuid = { workspace = true, features = ["serde", "v4"] }

Expand All @@ -45,6 +44,7 @@ swagger-ui = ["rustapi-core/swagger-ui", "rustapi-openapi/swagger-ui"]
# Performance features
simd-json = ["rustapi-core/simd-json"]
tracing = ["rustapi-core/tracing"]
legacy-validator = ["dep:validator", "rustapi-core/legacy-validator"]

# Compression middleware
compression = ["rustapi-core/compression"]
Expand Down Expand Up @@ -87,4 +87,4 @@ replay = ["dep:rustapi-extras", "rustapi-extras/replay"]

# Meta features
extras = ["jwt", "cors", "rate-limit"]
full = ["extras", "config", "cookies", "sqlx", "toon", "insight", "compression", "ws", "view", "timeout", "guard", "logging", "circuit-breaker", "security-headers", "api-key", "cache", "dedup", "sanitization", "otel", "structured-logging", "replay"]
full = ["extras", "config", "cookies", "sqlx", "toon", "insight", "compression", "ws", "view", "timeout", "guard", "logging", "circuit-breaker", "security-headers", "api-key", "cache", "dedup", "sanitization", "otel", "structured-logging", "replay", "legacy-validator"]
2 changes: 2 additions & 0 deletions crates/rustapi-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
//! - `config` - Configuration management with `.env` file support
//! - `cookies` - Cookie parsing extractor
//! - `sqlx` - SQLx database error conversion to ApiError
//! - `legacy-validator` - Compatibility mode for `validator::Validate`
//! - `extras` - Meta feature enabling jwt, cors, and rate-limit
//! - `full` - All optional features enabled
//!
Expand Down Expand Up @@ -302,6 +303,7 @@ pub mod prelude {
// Re-export validation - use validator derive macro directly
pub use rustapi_validate::v2::AsyncValidate;
pub use rustapi_validate::v2::Validate as V2Validate;
#[cfg(feature = "legacy-validator")]
pub use validator::Validate;

// Re-export OpenAPI schema derive
Expand Down
2 changes: 2 additions & 0 deletions crates/rustapi-rs/tests/validation_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ use serde::{Deserialize, Serialize};
// ============================================================================

#[derive(Debug, Deserialize, Serialize, validator::Validate)]
#[cfg(feature = "legacy-validator")]
struct LegacyUser {
#[validate(length(min = 3))]
name: String,
}

#[test]
#[cfg(feature = "legacy-validator")]
fn test_legacy_validator_compat() {
let valid_user = LegacyUser {
name: "Bob".to_string(),
Expand Down
2 changes: 1 addition & 1 deletion crates/rustapi-view/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ http-body-util = { workspace = true }
bytes = { workspace = true }

# Async
tokio = { workspace = true }
tokio = { workspace = true, features = ["sync"] }

# Utilities
thiserror = { workspace = true }
Expand Down
4 changes: 2 additions & 2 deletions crates/rustapi-ws/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ futures-util = { workspace = true }
http = { workspace = true }
http-body-util = { workspace = true }
bytes = { workspace = true }
hyper = { workspace = true }
hyper-util = { workspace = true }
hyper = { workspace = true, features = ["server", "http1"] }
hyper-util = { workspace = true, features = ["tokio"] }

# Serialization (optional, for JSON messages)
serde = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ RustApi::auto().run("0.0.0.0:8080").await
**Automatic OpenAPI/Swagger generation.**

Features:
- Wraps `utoipa` internally (not exposed)
- Native OpenAPI model and schema registry (no external OpenAPI generator dependency)
- Auto-generates OpenAPI 3.0 spec
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

The documentation still says RustAPI auto-generates an OpenAPI 3.0 spec, but the native spec model in rustapi-openapi sets openapi: "3.1.0" by default. Please update this bullet to 3.1 (or explicitly call out 3.0 as legacy-only) to avoid misleading users.

Suggested change
- Auto-generates OpenAPI 3.0 spec
- Auto-generates OpenAPI 3.1 spec (3.0 available as legacy)

Copilot uses AI. Check for mistakes.
- Serves Swagger UI at `/docs`
- Extracts schemas from `Json<T>`, `Query<T>`, `Path<T>`
Expand Down
4 changes: 2 additions & 2 deletions docs/PHILOSOPHY.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ validator = "0.16"
|-----------|----------------|--------------|
| HTTP Server | `hyper 1.x` | `hyper 2.x`, `h3` (HTTP/3) |
| Async Runtime | `tokio` | `smol`, `async-std` (future) |
| Validation | `validator` | Custom engine (planned for v1.0) |
| Validation | Native RustAPI v2 (legacy optional) | Further optimized native engine |
| Router | `matchit` | Custom radix tree |
| OpenAPI | `utoipa` | Native implementation |
| OpenAPI | Native implementation | Further optimized native implementation |

**Example scenario:** When `hyper 2.0` releases with breaking changes:
1. We update `rustapi-core` to use `hyper 2.0`
Expand Down
5 changes: 2 additions & 3 deletions docs/cookbook/src/architecture/system_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ graph BT
Tokio[tokio]
Hyper[hyper]
Serde[serde]
Utoipa[utoipa]
Validator[validator]
Tungstenite[tungstenite]
Tera[tera]
Expand All @@ -140,8 +139,8 @@ graph BT
Core --> Tokio
Core --> Hyper
Core --> Serde
OpenAPI --> Utoipa
Validate --> Validator
OpenAPI --> Serde
Validate -.->|legacy optional| Validator
Toon --> Serde
WS --> Tungstenite
View --> Tera
Expand Down
2 changes: 1 addition & 1 deletion docs/cookbook/src/crates/rustapi_openapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

## Automatic Spec Generation

We believe that if documentation is manual, it is wrong. RustAPI uses `utoipa` to generate an OpenAPI 3.0 specification directly from your code.
We believe that if documentation is manual, it is wrong. RustAPI uses a native OpenAPI generator to build the specification directly from your code.

## The `Schema` Trait

Expand Down
6 changes: 3 additions & 3 deletions docs/cookbook/src/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub struct ListParams {

---

## 2. Don't Use `utoipa` Directly
## 2. Don't Add External OpenAPI Generators Directly

**Wrong:**
```toml
Expand All @@ -53,7 +53,7 @@ rustapi-rs = { version = "0.1.300", features = ["full"] }

**Why?**
- RustAPI has its own OpenAPI implementation (`rustapi-openapi`)
- Adding `utoipa` directly can cause dependency conflicts
- External OpenAPI derive/macros are not part of RustAPI's public API surface
- The `Schema` derive macro is already in `rustapi_rs::prelude::*`

---
Expand Down Expand Up @@ -370,7 +370,7 @@ async fn handler(
## The Golden Rules

1. **Add `Schema` derive** to any struct used with extractors or responses
2. **Don't use `utoipa`** directly - `rustapi-openapi` is already included
2. **Don't add external OpenAPI crates** directly - `rustapi-openapi` is already included
3. **Import from `rustapi_rs`** only - never use internal crates directly
4. **Use `RustApi::auto()`** with handler macros for automatic route discovery

Expand Down
4 changes: 2 additions & 2 deletions docs/native_openapi.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Native OpenAPI Design for RustAPI

## Overview
RustAPI is moving to a native OpenAPI 3.1 generator to reduce external dependencies (specifically `utoipa`) and ensure deterministic, high-quality spec generation. This document outlines the architecture for the "Native OpenAPI" implementation.
RustAPI uses a native OpenAPI 3.1 generator to reduce external dependencies and ensure deterministic, high-quality spec generation. This document outlines the architecture for the "Native OpenAPI" implementation.

## Goals
- **Zero Heavy Dependencies**: Remove `utoipa`. Use only `serde` + `serde_json` + standard library.
- **Zero Heavy Dependencies**: Use only `serde` + `serde_json` + standard library.
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

The "Zero Heavy Dependencies" goal currently says the native OpenAPI implementation uses only serde + serde_json + std, but rustapi-openapi also depends on http, http-body-util, bytes, and rustapi-macros. Consider rephrasing this goal to something like "no external OpenAPI generator" / "no heavy schema deps" or explicitly scope it to the schema engine internals to keep the design doc accurate.

Suggested change
- **Zero Heavy Dependencies**: Use only `serde` + `serde_json` + standard library.
- **No Heavy OpenAPI Dependencies**: No external OpenAPI/schema generators or large meta-frameworks; the core schema engine and OpenAPI model types rely primarily on `serde` + `serde_json` + standard library.

Copilot uses AI. Check for mistakes.
- **OpenAPI 3.1**: Support full JSON Schema 2020-12 compatibility.
- **Determinism**: Output MUST be stable (sorted keys, stable component names).
- **Backwards Compatibility**: Maintain existing `RustApi::auto()` experience.
Expand Down
Loading