fastapi_rust/lib.rs
1//! Ultra-optimized Rust web framework inspired by FastAPI.
2//!
3//! fastapi_rust provides a type-safe, high-performance web framework with:
4//!
5//! - **Type-driven API design** — Route handlers declare types, framework extracts/validates automatically
6//! - **Dependency injection** — Composable, testable request handling
7//! - **Automatic OpenAPI** — Schema generation from type definitions
8//! - **First-class async** — Built on asupersync for structured concurrency
9//! - **Dependency discipline** — No Tokio/Hyper/Tower/Axum; direct deps kept small
10//!
11//! # Role In The System
12//!
13//! `fastapi_rust` is the user-facing facade crate. It re-exports the framework's
14//! core types, macros, and utilities from the sub-crates so applications only
15//! need a single dependency. All real behavior lives in the sub-crates listed
16//! below; this crate exists to provide a cohesive, ergonomic API surface.
17//!
18//! # Quick Start
19//!
20//! ```ignore
21//! use fastapi_rust::prelude::*;
22//!
23//! #[derive(Serialize, Deserialize, JsonSchema)]
24//! struct Item {
25//! id: i64,
26//! name: String,
27//! }
28//!
29//! #[get("/items/{id}")]
30//! async fn get_item(cx: &Cx, id: Path<i64>) -> Json<Item> {
31//! Json(Item { id: id.0, name: "Example".into() })
32//! }
33//!
34//! fn main() {
35//! let app = App::builder()
36//! .title("My API")
37//! .route_entry(get_item_route())
38//! .build();
39//!
40//! let rt = asupersync::runtime::RuntimeBuilder::current_thread()
41//! .build()
42//! .expect("runtime must build");
43//! rt.block_on(async move {
44//! serve(app, "0.0.0.0:8000").await.expect("server must start");
45//! });
46//! }
47//! ```
48//!
49//! # Design Philosophy
50//!
51//! This framework is built with the following principles:
52//!
53//! 1. **Zero-cost abstractions** — No runtime reflection, everything at compile time
54//! 2. **Cancel-correct** — Leverages asupersync's structured concurrency
55//! 3. **Minimal allocations** — Zero-copy parsing where possible
56//! 4. **Familiar API** — FastAPI users will recognize the patterns
57//!
58//! # Crate Structure
59//!
60//! | Crate | Purpose |
61//! |-------|---------|
62//! | `fastapi_core` | Core types (Request, Response, Error), extractors, middleware, DI |
63//! | `fastapi_http` | Zero-copy HTTP/1.1 parser, TCP server, chunked encoding |
64//! | `fastapi_router` | Trie-based router with O(log n) lookups |
65//! | `fastapi_macros` | Procedural macros (`#[get]`, `#[derive(Validate)]`, `#[derive(JsonSchema)]`) |
66//! | `fastapi_openapi` | OpenAPI 3.1 schema types and generation |
67//! | `fastapi_output` | Agent-aware rich console output (optional) |
68//!
69//! # Feature Flags
70//!
71//! | Feature | Default | Description |
72//! |---------|---------|-------------|
73//! | `output` | **yes** | Rich console output with agent detection (includes `fastapi-output/rich`) |
74//! | `testing` | **yes** | TestClient, assertion macros, and deterministic in-process testing helpers |
75//! | `output-plain` | no | Plain-text-only output (smaller binary, no ANSI codes) |
76//! | `full` | no | All output features including every theme and component |
77//!
78//! ## Sub-crate Feature Flags
79//!
80//! **`fastapi-core`:**
81//!
82//! | Feature | Description |
83//! |---------|-------------|
84//! | `regex` | Regex support in testing assertions |
85//! | `testing` | TestClient and assertion helpers backed by asupersync test internals |
86//! | `compression` | Response compression middleware (gzip via flate2) |
87//! | `proptest` | Property-based testing support |
88
89//!
90//! # Cookbook
91//!
92//! Common patterns for building APIs with fastapi_rust.
93//!
94//! ## JSON CRUD Handler
95//!
96//! ```ignore
97//! use fastapi_rust::prelude::*;
98//!
99//! #[get("/items/{id}")]
100//! async fn get_item(cx: &Cx, id: Path<i64>, state: State<AppState>) -> Result<Json<Item>, HttpError> {
101//! let item = state.db.find(id.0).await?;
102//! Ok(Json(item))
103//! }
104//! ```
105//!
106//! ## Pagination
107//!
108//! ```ignore
109//! use fastapi_rust::prelude::*;
110//!
111//! #[get("/items")]
112//! async fn list_items(cx: &Cx, page: Pagination) -> Json<Page<Item>> {
113//! // page.page() returns current page (default: 1)
114//! // page.per_page() returns items per page (default: 20, max: 100)
115//! let items = db.list(page.offset(), page.limit()).await;
116//! Json(Page::new(items, total_count, page.page(), page.per_page()))
117//! }
118//! ```
119//!
120//! ## Bearer Token Authentication
121//!
122//! ```ignore
123//! use fastapi_rust::prelude::*;
124//!
125//! #[get("/protected")]
126//! async fn protected(cx: &Cx, token: BearerToken) -> Json<UserInfo> {
127//! let user = verify_jwt(token.token()).await?;
128//! Json(user)
129//! }
130//! ```
131//!
132//! ## Background Tasks
133//!
134//! ```ignore
135//! use fastapi_rust::prelude::*;
136//!
137//! #[post("/send-email")]
138//! async fn send_email(cx: &Cx, body: Json<EmailRequest>, tasks: BackgroundTasks) -> StatusCode {
139//! tasks.add(move || {
140//! // Runs after response is sent
141//! email_service::send(&body.to, &body.subject, &body.body);
142//! });
143//! StatusCode::ACCEPTED
144//! }
145//! ```
146//!
147//! ## CORS + Rate Limiting Middleware
148//!
149//! ```ignore
150//! use fastapi_rust::prelude::*;
151//!
152//! let app = App::new()
153//! .middleware(Cors::new().allow_any_origin(true).allow_credentials(true))
154//! .middleware(RateLimitBuilder::new().max_requests(100).window_secs(60).build());
155//! ```
156//!
157//! ## Error Handling
158//!
159//! ```ignore
160//! use fastapi_rust::prelude::*;
161//!
162//! // Custom errors implement IntoResponse automatically via HttpError
163//! fn not_found(resource: &str, id: u64) -> HttpError {
164//! HttpError::not_found(format!("{} {} not found", resource, id))
165//! }
166//! ```
167//!
168//! # Migrating from Python FastAPI
169//!
170//! ## Key Differences
171//!
172//! | Python FastAPI | fastapi_rust | Notes |
173//! |----------------|--------------|-------|
174//! | `@app.get("/")` | `#[get("/")]` | Proc macro instead of decorator |
175//! | `async def handler(item: Item)` | `async fn handler(cx: &Cx, item: Json<Item>)` | Explicit `Cx` context + typed extractors |
176//! | `Depends(get_db)` | `Depends<DbPool>` | Type-based DI, not function-based |
177//! | `HTTPException(404)` | `HttpError::not_found(msg)` | Typed error constructors |
178//! | `BackgroundTasks` | `BackgroundTasks` | Same concept, different API |
179//! | `Query(q: str)` | `Query<SearchParams>` | Struct-based query extraction |
180//! | `Path(item_id: int)` | `Path<i64>` | Type-safe path parameters |
181//! | `Body(...)` | `Json<T>` | Explicit JSON extraction |
182//! | `Response(status_code=201)` | `StatusCode::CREATED` | Type-safe status codes |
183//!
184//! ## Async Runtime
185//!
186//! Python FastAPI uses `asyncio`. fastapi_rust uses `asupersync`, which provides:
187//! - **Structured concurrency**: Request handlers run in regions
188//! - **Cancel-correctness**: Graceful cancellation via checkpoints
189//! - **Budgeted timeouts**: Request timeouts via budget exhaustion
190//!
191//! Every handler receives `&Cx` as its first parameter for async context.
192//!
193//! ## Dependency Injection
194//!
195//! Python uses function-based DI with `Depends(func)`. Rust uses trait-based DI:
196//!
197//! ```ignore
198//! // Python:
199//! // async def get_db():
200//! // yield db_session
201//! //
202//! // @app.get("/")
203//! // async def handler(db: Session = Depends(get_db)):
204//!
205//! // Rust:
206//! impl FromDependency for DbPool {
207//! async fn from_dependency(cx: &Cx, cache: &DependencyCache) -> Result<Self, HttpError> {
208//! Ok(DbPool::acquire(cx).await?)
209//! }
210//! }
211//!
212//! #[get("/")]
213//! async fn handler(cx: &Cx, db: Depends<DbPool>) -> Json<Data> { ... }
214//! ```
215//!
216//! ## Validation
217//!
218//! Python uses Pydantic models. Rust uses `#[derive(Validate)]`:
219//!
220//! ```ignore
221//! // Python:
222//! // class Item(BaseModel):
223//! // name: str = Field(..., min_length=1, max_length=100)
224//! // price: float = Field(..., gt=0)
225//!
226//! // Rust:
227//! #[derive(Validate)]
228//! struct Item {
229//! #[validate(min_length = 1, max_length = 100)]
230//! name: String,
231//! #[validate(range(min = 0.01))]
232//! price: f64,
233//! }
234//! ```
235
236#![forbid(unsafe_code)]
237// Design doc at PROPOSED_RUST_ARCHITECTURE.md (not embedded - too many conceptual code examples)
238
239// Re-export crates
240pub use fastapi_core as core;
241pub use fastapi_http as http;
242pub use fastapi_macros as macros;
243pub use fastapi_openapi as openapi;
244pub use fastapi_router as router;
245
246// Re-export commonly used types
247pub use fastapi_core::{
248 App, AppBuilder, AppConfig, Cors, CorsConfig, Cx, DefaultConfig, DefaultDependencyConfig,
249 DependencyOverrides, DependencyScope, Depends, DependsConfig, FromDependency, FromRequest,
250 HttpError, IntoResponse, Method, NoCache, Request, RequestId, RequestIdConfig,
251 RequestIdMiddleware, Response, ResponseBody, StateContainer, StatusCode, ValidationError,
252 ValidationErrors,
253};
254
255// Re-export extractors
256pub use fastapi_core::{
257 // Common header types
258 Accept,
259 AddResponseHeader,
260 AppState,
261 Authorization,
262 // Background tasks
263 BackgroundTasks,
264 // Auth extractors
265 BasicAuth,
266 BasicAuthError,
267 BearerToken,
268 BearerTokenError,
269 ContentType,
270 // Cookies
271 Cookie,
272 DEFAULT_PAGE,
273 DEFAULT_PER_PAGE,
274 // Headers
275 Header,
276 HeaderExtractError,
277 HeaderValues,
278 Host,
279 // Body extractors
280 Json,
281 JsonConfig,
282 JsonExtractError,
283 MAX_PER_PAGE,
284 NamedHeader,
285 OAuth2BearerError,
286 OAuth2PasswordBearer,
287 OAuth2PasswordBearerConfig,
288 Page,
289 // Pagination
290 Pagination,
291 PaginationConfig,
292 // Path parameters
293 Path,
294 PathExtractError,
295 PathParams,
296 // Query string
297 Query,
298 QueryExtractError,
299 QueryParams,
300 RequestContext,
301 SameSite,
302 // State
303 State,
304 UserAgent,
305 XRequestId,
306};
307
308// Re-export testing utilities
309#[cfg(feature = "testing")]
310pub use fastapi_core::{CookieJar, RequestBuilder, TestClient, TestResponse};
311pub use fastapi_macros::{JsonSchema, Validate, delete, get, head, options, patch, post, put};
312pub use fastapi_openapi::{OpenApi, OpenApiBuilder, SchemaRegistry};
313pub use fastapi_router::{
314 // Route matching
315 AllowedMethods,
316 ConversionError,
317 // Path parameter types
318 Converter,
319 // Error types
320 InvalidRouteError,
321 ParamInfo,
322 ParamValue,
323 // Core router types
324 Route,
325 RouteAddError,
326 RouteConflictError,
327 RouteLookup,
328 RouteMatch,
329 Router,
330};
331
332// Re-export HTTP server types
333pub use fastapi_http::{
334 GracefulOutcome, ServeError, Server, ServerConfig, ServerError, ShutdownController,
335 ShutdownReceiver, TcpServer, serve, serve_with_config,
336};
337
338/// Prelude module for convenient imports.
339pub mod prelude {
340 pub use crate::{
341 // Core types
342 App,
343 AppBuilder,
344 AppConfig,
345 // Auth
346 BasicAuth,
347 BearerToken,
348 Cookie,
349 Cors,
350 CorsConfig,
351 // asupersync context
352 Cx,
353 DefaultConfig,
354 DefaultDependencyConfig,
355 DependencyOverrides,
356 DependencyScope,
357 Depends,
358 DependsConfig,
359 FromDependency,
360 FromRequest,
361 Header,
362 HttpError,
363 IntoResponse,
364 // Extractors
365 Json,
366 // Macros
367 JsonSchema,
368 Method,
369 NoCache,
370 OAuth2PasswordBearer,
371 // OpenAPI
372 OpenApi,
373 OpenApiBuilder,
374 Page,
375 // Pagination
376 Pagination,
377 Path,
378 Query,
379 Request,
380 RequestContext,
381 RequestId,
382 RequestIdMiddleware,
383 Response,
384 Route,
385 Router,
386 // Server
387 Server,
388 ServerConfig,
389 State,
390 StatusCode,
391 Validate,
392 ValidationError,
393 ValidationErrors,
394 delete,
395 get,
396 head,
397 options,
398 patch,
399 post,
400 put,
401 serve,
402 };
403 pub use serde::{Deserialize, Serialize};
404}
405
406/// Testing utilities module.
407#[cfg(feature = "testing")]
408pub mod testing {
409 pub use fastapi_core::testing::{CookieJar, RequestBuilder, TestClient, TestResponse};
410}
411
412/// Extractors module for type-safe request data extraction.
413pub mod extractors {
414 pub use fastapi_core::{
415 Accept, AppState, Authorization, BackgroundTasks, BasicAuth, BearerToken, ContentType,
416 Cookie, Header, HeaderValues, Host, Json, JsonConfig, NamedHeader, OAuth2PasswordBearer,
417 Page, Pagination, PaginationConfig, Path, PathParams, Query, QueryParams, State, UserAgent,
418 XRequestId,
419 };
420}
421
422/// Extractors module for request data extraction (extended).
423pub mod extract {
424 pub use fastapi_core::{
425 Accept, AppState, Authorization, ContentType, FromHeaderValue, Header, HeaderExtractError,
426 HeaderName, HeaderValues, Host, Json, JsonConfig, JsonExtractError, NamedHeader,
427 OAuth2BearerError, OAuth2BearerErrorKind, OAuth2PasswordBearer, OAuth2PasswordBearerConfig,
428 Path, PathExtractError, PathParams, Query, QueryExtractError, QueryParams, State,
429 StateExtractError, UserAgent, XRequestId,
430 };
431}
432
433/// HTTP server module with server types and configuration.
434pub mod server {
435 pub use fastapi_http::{
436 // Configuration constants
437 DEFAULT_DRAIN_TIMEOUT_SECS,
438 DEFAULT_KEEP_ALIVE_TIMEOUT_SECS,
439 DEFAULT_MAX_CONNECTIONS,
440 DEFAULT_MAX_REQUESTS_PER_CONNECTION,
441 DEFAULT_READ_BUFFER_SIZE,
442 DEFAULT_REQUEST_TIMEOUT_SECS,
443 // Shutdown coordination
444 GracefulOutcome,
445 // Error types
446 ServeError,
447 // Server types
448 Server,
449 ServerConfig,
450 ServerError,
451 ShutdownController,
452 ShutdownReceiver,
453 TcpServer,
454 // Server functions
455 serve,
456 serve_with_config,
457 };
458}
459
460/// Extension trait for generating OpenAPI specifications from applications.
461pub trait OpenApiExt {
462 /// Generate an OpenAPI specification from the application.
463 ///
464 /// This creates an OpenAPI 3.1 document based on the application's
465 /// configuration and registered routes.
466 ///
467 /// # Example
468 ///
469 /// ```ignore
470 /// use fastapi::prelude::*;
471 /// use fastapi::OpenApiExt;
472 ///
473 /// let app = App::builder()
474 /// .config(AppConfig::new().name("My API").version("1.0.0"))
475 /// .build();
476 ///
477 /// let spec = app.openapi();
478 /// println!("{}", serde_json::to_string_pretty(&spec).unwrap());
479 /// ```
480 fn openapi(&self) -> OpenApi;
481
482 /// Generate an OpenAPI specification with custom configuration.
483 fn openapi_with<F>(&self, configure: F) -> OpenApi
484 where
485 F: FnOnce(OpenApiBuilder) -> OpenApiBuilder;
486}
487
488impl OpenApiExt for App {
489 fn openapi(&self) -> OpenApi {
490 self.openapi_with(|b| b)
491 }
492
493 fn openapi_with<F>(&self, configure: F) -> OpenApi
494 where
495 F: FnOnce(OpenApiBuilder) -> OpenApiBuilder,
496 {
497 let mut builder = OpenApiBuilder::new(&self.config().name, &self.config().version);
498 builder = configure(builder);
499 builder.build()
500 }
501}
502
503// Note: operation_id generation lives in `fastapi-router::Route` and in the
504// OpenAPI builder layer; keep the facade crate lean.