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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Embedded dashboard snapshots now include request-stage counters, route topology groups, health endpoint summary, and replay admin API discovery metadata.
- Dashboard UI adds route group/method/tag filters plus a replay browser that reuses the existing `ReplayLayer` admin API for list, detail, and diff workflows.
- Replay admin list endpoint now accepts UI-friendly pagination and filters: `offset`, `status_max`, `from`, `to`, `tag`, and `order`.
- `mcp_tools` example under `crates/rustapi-rs/examples/` showing concurrent RustAPI HTTP + MCP sidecar with tag-based tool exposure (run with `--features protocol-mcp`).
- Comprehensive e2e tests for the MCP transport (initialize, tag-filtered `tools/list`, real proxied `tools/call` for GET+body routes, error handling for hidden tools).
- Cookbook recipe "MCP Integration (Agent Tools)" plus `rustapi_mcp.md` crate deep-dive page.

### Documentation

Expand Down
23 changes: 23 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ members = [
"crates/rustapi-view",
"crates/rustapi-testing",
"crates/rustapi-grpc",
"crates/rustapi-mcp",
"crates/cargo-rustapi",
]

Expand Down Expand Up @@ -103,6 +104,7 @@ rustapi-ws = { path = "crates/rustapi-ws", version = "0.1.335" }
rustapi-view = { path = "crates/rustapi-view", version = "0.1.335" }
rustapi-testing = { path = "crates/rustapi-testing", version = "0.1.335" }
rustapi-grpc = { path = "crates/rustapi-grpc", version = "0.1.335" }
rustapi-mcp = { path = "crates/rustapi-mcp", version = "0.1.335" }

# HTTP/3 (QUIC)
quinn = "0.11"
Expand Down
42 changes: 31 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,31 +148,40 @@ Current benchmark methodology and canonical published performance claims live in

## Quick Start

**Recommended usage** (short and clean macro paths):

```toml
[dependencies]
api = { package = "rustapi-rs", version = "0.1.478" }
```

```rust
use rustapi_rs::prelude::*;
use api::prelude::*;

#[derive(Serialize, Schema)]
struct Message { text: String }

#[rustapi_rs::get("/hello/{name}")]
#[api::get("/hello/{name}")]
async fn hello(Path(name): Path<String>) -> Json<Message> {
Json(Message { text: format!("Hello, {}!", name) })
}

#[rustapi_rs::main]
#[api::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
RustApi::auto().run("127.0.0.1:8080").await
}
```

`RustApi::auto()` collects all macro-annotated handlers, generates OpenAPI documentation (served at `/docs`), and starts a multi-threaded tokio runtime.

> **Tip:** Crate'i `api` (veya `myapi`, `server` vs.) diye alias'lamak en temiz ve FastAPI benzeri deneyimi verir. Makrolar otomatik olarak `#[api::get]`, `#[api::post]`, `#[api::main]` şeklinde çalışır.

For production deployments, you can enable standard probe endpoints without writing handlers manually:

```rust
use rustapi_rs::prelude::*;
use api::prelude::*;

#[rustapi_rs::main]
#[api::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
let health = HealthCheckBuilder::new(true)
.add_check("database", || async { HealthStatus::healthy() })
Expand All @@ -193,9 +202,9 @@ This registers:
Or use a single production baseline preset:

```rust
use rustapi_rs::prelude::*;
use api::prelude::*;

#[rustapi_rs::main]
#[api::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
RustApi::auto()
.production_defaults("users-api")
Expand All @@ -206,20 +215,29 @@ async fn main() -> std::result::Result<(), Box<dyn std::error::Error + Send + Sy

`production_defaults()` enables request IDs, tracing spans, and standard probe endpoints in one call.

You can shorten the macro prefix by renaming the crate:
### Using a shorter macro prefix (recommended)

`rustapi-rs` makrolarını `api::get`, `api::post`, `api::main` gibi kısa ve güzel isimlerle kullanmak için crate'i alias'layabilirsiniz:

```toml
[dependencies]
api = { package = "rustapi-rs", version = "0.1.335" }
api = { package = "rustapi-rs", version = "0.1.478" }
```

```rust
use api::prelude::*;

#[api::get("/users")]
async fn list_users() -> &'static str { "ok" }

#[api::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
RustApi::auto().run("127.0.0.1:8080").await
}
```

Bu, hem daha okunabilir hem de FastAPI benzeri bir deneyim sağlar. Makro genişletme sırasında crate ismi otomatik olarak algılanır (`proc-macro-crate` sayesinde).

## Feature Flags

Features are organized into three namespaces:
Expand All @@ -236,6 +254,7 @@ Meta features: `core` (default), `protocol-all`, `extras-all`, `full`.

- **Crate consolidation (13 → 9):** `rustapi-testing`, `rustapi-jobs`, `rustapi-view`, and `rustapi-toon` merged into `rustapi-core` and `rustapi-extras` as feature-gated modules.
- **Embedded Isometric System Dashboard:** Live `/dashboard` with bento-grid layout, execution-flow visualization, and time-travel replay browser.
- **Native MCP progress:** Cookbook recipe, `mcp_tools` runnable example, and end-to-end tests for tool discovery + real proxied invocation.
- Dual-stack runtime: simultaneous HTTP/1.1 (TCP) and HTTP/3 (QUIC/UDP)
- WebSocket permessage-deflate compression
- `rustapi-grpc` crate: optional Tonic/Prost-based gRPC alongside HTTP (`run_rustapi_and_grpc`)
Expand All @@ -247,10 +266,11 @@ Meta features: `core` (default), `protocol-all`, `extras-all`, `full`.
- Live architectural view of request routing across the **Ultra Fast**, **Fast**, and **Full** execution paths.
- Interactive endpoint visualization for topology inspection, route grouping, and runtime status awareness.
- Time-travel replay UI for browsing recorded HTTP traffic, selecting a historical request, and inspecting replay state directly from the dashboard.
- [ ] Native MCP (Model Context Protocol) Orchestration
- [~] Native MCP (Model Context Protocol) Orchestration
- Embedded MCP server that exposes RustAPI endpoints as discoverable tools for LLMs and external AI agents.
- Automatic capability discovery and direct tool-style invocation for compatible clients such as Claude and other multi-agent runtimes.
- Framework-level orchestration layer for agent-to-endpoint communication, authorization, and operational policy enforcement.
- Framework-level orchestration layer for agent-to-endpoint communication (core + examples + cookbook + e2e tests done: discovery + proxied invocation through full pipeline + sidecar runner + runnable `mcp_tools` example).
- Remaining polish: admin token enforcement in transport, optional zero-copy in-process invoker (current design uses correct localhost proxy), more client conformance tests.


## Documentation
Expand Down
4 changes: 4 additions & 0 deletions api/public/rustapi-rs.all-features.txt
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,8 @@ pub use rustapi_rs::prelude::route
pub use rustapi_rs::prelude::run_concurrently
pub use rustapi_rs::prelude::run_rustapi_and_grpc
pub use rustapi_rs::prelude::run_rustapi_and_grpc_with_shutdown
pub use rustapi_rs::prelude::run_rustapi_and_mcp
pub use rustapi_rs::prelude::run_rustapi_and_mcp_with_shutdown
pub use rustapi_rs::prelude::serve_dir
pub use rustapi_rs::prelude::sse_from_iter
pub use rustapi_rs::prelude::sse_response
Expand All @@ -529,6 +531,8 @@ pub use rustapi_rs::protocol::grpc::<<rustapi_grpc::*>>
pub mod rustapi_rs::protocol::http3
pub use rustapi_rs::protocol::http3::Http3Config
pub use rustapi_rs::protocol::http3::Http3Server
pub mod rustapi_rs::protocol::mcp
pub use rustapi_rs::protocol::mcp::<<rustapi_mcp::*>>
pub mod rustapi_rs::protocol::toon
pub use rustapi_rs::protocol::toon::<<rustapi_extras::toon::*>>
pub mod rustapi_rs::protocol::view
Expand Down
23 changes: 23 additions & 0 deletions crates/rustapi-core/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,29 @@ impl RustApi {

fn mount_auto_routes_grouped(mut self) -> Self {
let routes = crate::auto_route::collect_auto_routes();

if routes.is_empty() {
// This is a common source of confusion with linkme-based auto registration.
// We emit a clear warning so users know their annotated handlers were not linked.
tracing::warn!(
target: "rustapi::auto",
count = 0,
"RustApi::auto() collected 0 routes. \
This usually means either:\n\
- No handlers were annotated with #[rustapi_rs::get], #[post], etc.\n\
- The binary/test was not linked with the annotated modules (common in some test setups).\n\
- You are building a library (cdylib/rlib) where linkme distributed slices may not be populated.\n\n\
You can still register routes manually with .route() or check with rustapi_rs::auto_route_count()."
);
} else {
#[cfg(feature = "tracing")]
tracing::debug!(
target: "rustapi::auto",
count = routes.len(),
"Auto route collection found handlers"
);
}

// Use BTreeMap for deterministic route registration order
let mut by_path: BTreeMap<String, MethodRouter> = BTreeMap::new();

Expand Down
121 changes: 106 additions & 15 deletions crates/rustapi-core/src/auto_route.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,108 @@
//! Auto-route registration using linkme distributed slices
//!
//! This module enables zero-config route registration. Routes decorated with
//! `#[rustapi::get]`, `#[rustapi::post]`, etc. are automatically collected at link-time.
//! **Implementation detail**: This module provides the current backend for
//! automatic, zero-boilerplate route collection using the `linkme` crate's
//! distributed slice mechanism.
//!
//! This is an internal implementation detail of `RustApi::auto()`.
//! The public contract is only:
//! - Handlers annotated with the route macros are discovered automatically.
//! - `collect_auto_routes()` and `auto_route_count()` for introspection.
//!
//! We may change the underlying mechanism in the future (while keeping the
//! observable behavior stable).
//!
//! This module enables **zero-config route registration**. Routes decorated with
//! `#[rustapi_rs::get]`, `#[rustapi_rs::post]`, etc. are automatically collected at
//! **link time** using the [`linkme`](https://docs.rs/linkme) crate.
//!
//! `RustApi::auto()` (and `RustApi::config()`) rely on this mechanism.
//!
//! # How It Works
//!
//! When you use `#[rustapi::get("/path")]` or similar macros, they generate a
//! static registration that adds the route factory function to a distributed slice.
//! At runtime, `RustApi::auto()` collects all these routes and registers them.
//! The attribute macros emit a small static initializer that appends a factory
//! function into a `linkme::distributed_slice`. At runtime we simply iterate the
//! slice and build the router.
//!
//! After collection we sort routes into a `BTreeMap` so registration order is
//! deterministic regardless of link order.
//!
//! # Public API
//!
//! - [`collect_auto_routes`] – returns all discovered routes as `Vec<Route>`
//! - [`auto_route_count`] – cheap way to check how many handlers were linked in
//!
//! # Example
//!
//! ```rust,ignore
//! use rustapi_rs::prelude::*;
//!
//! #[rustapi::get("/users")]
//! #[rustapi_rs::get("/users")]
//! async fn list_users() -> Json<Vec<User>> {
//! Json(vec![])
//! }
//!
//! #[rustapi::post("/users")]
//! #[rustapi_rs::post("/users")]
//! async fn create_user(Json(body): Json<CreateUser>) -> Created<User> {
//! // ...
//! }
//!
//! #[rustapi::main]
//! #[rustapi_rs::main]
//! async fn main() -> Result<()> {
//! // Routes are auto-registered, no need for manual .mount() calls!
//! // No manual .route() calls needed!
//! RustApi::auto()
//! .run("0.0.0.0:8080")
//! .await
//! }
//! ```
//!
//! # Limitations & Known Gotchas
//!
//! Link-time registration is powerful but comes with trade-offs:
//!
//! - **Tests can be flaky** — the test binary sometimes links differently than the
//! main binary. You may see `auto_route_count() == 0` inside `#[test]` even if
//! your annotated functions exist. Use `collect_auto_routes()` + filtering or
//! fall back to manual `.route()` in tests when necessary.
//!
//! - **Non-executable artifacts** (cdylib, rlib, staticlib, wasm32, etc.) often do
//! **not** populate distributed slices reliably because the linker may discard
//! the registration code.
//!
//! - **Multiple separate binaries** in the same workspace each get their own
//! independent slice. Routes defined in one binary are invisible to another.
//!
//! - There is **no runtime unregistration**. Once linked, the routes are there for
//! the lifetime of the process.
//!
//! - If you see zero routes at runtime with `RustApi::auto()`, the most common
//! causes are:
//! 1. No handlers were annotated with the route attributes.
//! 2. The module containing the handler was not linked into this binary.
//! 3. You are inside a test, library, or cdylib target.
//!
//! A clear warning is now emitted automatically when `RustApi::auto()` collects
//! zero routes (see `mount_auto_routes_grouped`).
//!
//! You can always inspect the situation with:
//!
//! ```rust,ignore
//! println!("Auto routes discovered: {}", rustapi_rs::auto_route_count());
//! ```
//!
//! # Manual Registration (always available)
//!
//! If auto-registration causes problems in your environment, you can ignore the
//! attribute macros entirely and use the classic builder:
//!
//! ```rust,ignore
//! RustApi::new()
//! .route("/users", get(list_users).post(create_user))
//! .run("0.0.0.0:8080")
//! .await
//! ```
//!
//! Both styles can be mixed freely.

use crate::handler::Route;
use linkme::distributed_slice;
Expand Down Expand Up @@ -66,8 +137,18 @@ pub fn collect_auto_routes() -> Vec<Route> {

/// Get the count of auto-registered routes without collecting them.
///
/// Useful for debugging and logging.
#[allow(dead_code)]
/// This is useful for:
/// - Debugging (e.g. in tests or startup logs)
/// - Asserting that your annotated handlers were actually linked in
///
/// # Example
///
/// ```rust,ignore
/// let count = rustapi_core::auto_route_count();
/// if count == 0 {
/// eprintln!("Warning: No auto-routes were discovered!");
/// }
/// ```
pub fn auto_route_count() -> usize {
AUTO_ROUTES.len()
}
Expand All @@ -78,15 +159,25 @@ mod tests {

#[test]
fn test_auto_routes_slice_exists() {
// The slice should exist, even if empty initially
// The slice always exists (even if empty). This test mainly ensures
// the linkme static was emitted correctly by the build.
let _count = auto_route_count();
}

#[test]
fn test_collect_auto_routes() {
// Should not panic, returns empty vec if no routes registered
fn test_collect_auto_routes_does_not_panic() {
// Collection must be safe even when no annotated handlers are present
// in the current test binary (very common situation).
let routes = collect_auto_routes();
// In tests, we may or may not have routes depending on what's linked
// We don't assert a specific count here because linkme behavior in
// test binaries is not guaranteed to be the same as in the final binary.
let _ = routes;
}

#[test]
fn test_auto_route_count_is_accessible() {
// Public API smoke test – users and integration tests should be able
// to call this without importing internal modules.
let _ = auto_route_count();
}
}
Loading
Loading