rust_webx_core/http.rs
1//! HTTP abstraction traits: IHttpContext, IHttpRequest, IHttpResponse.
2//!
3//! This module defines the core interfaces for HTTP request/response handling
4//! in the LRWF framework. All types are defined as traits to enable testability
5//! and middleware composition.
6//!
7//! # Traits
8//!
9//! - [`IHttpContext`] — The central context for a single HTTP request, providing
10//! access to request, response, and authentication claims.
11//! - [`IHttpRequest`] — Read-only access to the incoming HTTP request.
12//! - [`IHttpResponse`] — Mutable access to the outgoing HTTP response.
13//! - [`IClaimsExt`] — Extension for storing/retrieving authentication claims.
14//! - [`FromHttpContext`] — Build a request struct from HTTP request data.
15//!
16//! # Helpers
17//!
18//! - [`read_json_body`] — Deserialize JSON from the request body.
19//! - [`write_json_response`] — Serialize a value as JSON into the response.
20
21use crate::auth::IClaims;
22use crate::error::Result;
23use std::collections::HashMap;
24
25/// Common HTTP status codes.
26pub struct HttpStatus;
27
28impl HttpStatus {
29 pub const OK: u16 = 200;
30 pub const CREATED: u16 = 201;
31 pub const NO_CONTENT: u16 = 204;
32 pub const BAD_REQUEST: u16 = 400;
33 pub const UNAUTHORIZED: u16 = 401;
34 pub const FORBIDDEN: u16 = 403;
35 pub const NOT_FOUND: u16 = 404;
36 pub const INTERNAL_SERVER_ERROR: u16 = 500;
37}
38
39// ---------------------------------------------------------------------------
40// Claims extension for IHttpContext — MUST be defined before IHttpContext
41// ---------------------------------------------------------------------------
42
43/// Extension trait that adds claims storage to an `IHttpContext`.
44///
45/// Implemented by `HttpContext` in `lrwf-http`. This trait is a supertrait
46/// of `IHttpContext` so that authentication/authorization middleware can
47/// store and retrieve claims through `&mut dyn IHttpContext` directly.
48pub trait IClaimsExt {
49 /// Store authentication claims in the context.
50 fn set_claims(&mut self, claims: Box<dyn IClaims>);
51
52 /// Retrieve authentication claims from the context, if present.
53 fn claims(&self) -> Option<&dyn IClaims>;
54}
55
56/// HTTP context encapsulating the request, response, and service provider
57/// for the duration of a single HTTP request.
58///
59/// Extends `IClaimsExt` so middleware can store/retrieve auth claims.
60///
61/// Analogous to ASP.NET Core's HttpContext.
62pub trait IHttpContext: IClaimsExt + Send {
63 fn request(&self) -> &dyn IHttpRequest;
64 fn request_mut(&mut self) -> &mut dyn IHttpRequest;
65 fn response(&self) -> &dyn IHttpResponse;
66 fn response_mut(&mut self) -> &mut dyn IHttpResponse;
67}
68
69/// HTTP request abstraction.
70///
71/// Analogous to ASP.NET Core's HttpRequest.
72///
73/// Methods are non-generic to maintain dyn-compatibility.
74/// Use `serde_json::from_slice` on the raw body bytes for JSON deserialization.
75#[async_trait::async_trait]
76pub trait IHttpRequest: Send {
77 fn method(&self) -> &str;
78 fn path(&self) -> &str;
79 fn header(&self, name: &str) -> Option<&str>;
80 fn query(&self) -> &HashMap<String, String>;
81 fn route_params(&self) -> &HashMap<String, String>;
82 fn route_params_mut(&mut self) -> &mut HashMap<String, String>;
83
84 /// The original route pattern that was matched (e.g., `"/api/users/{id}"`).
85 /// Set by the router after successful route matching.
86 fn route_pattern(&self) -> Option<&str>;
87 fn route_pattern_mut(&mut self) -> &mut Option<String>;
88
89 /// Return the raw request body bytes.
90 async fn body_bytes(&self) -> Result<Vec<u8>>;
91
92 /// Return the request body as a UTF-8 string.
93 async fn body_text(&self) -> Result<String> {
94 let bytes = self.body_bytes().await?;
95 String::from_utf8(bytes).map_err(|e| crate::error::Error::Http(e.to_string()))
96 }
97}
98
99/// HTTP response abstraction.
100///
101/// Analogous to ASP.NET Core's HttpResponse.
102///
103/// Methods are non-generic to maintain dyn-compatibility.
104#[async_trait::async_trait]
105pub trait IHttpResponse: Send {
106 /// Get the current HTTP status code.
107 fn status(&self) -> u16;
108 fn set_status(&mut self, code: u16);
109 fn set_header(&mut self, key: &str, value: &str);
110
111 /// Whether a response body has been written.
112 fn has_body(&self) -> bool {
113 false
114 }
115
116 /// Write raw bytes as the response body.
117 async fn write_bytes(&mut self, data: Vec<u8>) -> Result<()>;
118
119 /// Write a UTF-8 string as the response body.
120 async fn write_text(&mut self, text: &str) -> Result<()> {
121 self.write_bytes(text.as_bytes().to_vec()).await
122 }
123
124 /// Read the current response body bytes.
125 ///
126 /// Returns an empty Vec if no body has been written.
127 /// Used by post-processing middleware (e.g. compression) in `after` hooks.
128 fn body_bytes(&self) -> Vec<u8> {
129 Vec::new()
130 }
131
132 /// Read a response header value by name.
133 ///
134 /// Returns None if the header is not set.
135 /// Used by post-processing middleware (e.g. compression) to inspect content-type.
136 fn header(&self, _key: &str) -> Option<&str> {
137 None
138 }
139}
140
141/// Helper: build a JSON response by serializing a value and writing it.
142pub async fn write_json_response<T: serde::Serialize + Send>(
143 resp: &mut dyn IHttpResponse,
144 value: &T,
145) -> Result<()> {
146 let json = serde_json::to_vec(value)?;
147 resp.set_header("content-type", "application/json");
148 resp.write_bytes(json).await
149}
150
151/// Helper: read JSON from the request body.
152pub async fn read_json_body<T: serde::de::DeserializeOwned>(req: &dyn IHttpRequest) -> Result<T> {
153 let bytes = req.body_bytes().await?;
154 serde_json::from_slice(&bytes).map_err(crate::error::Error::Serialization)
155}
156
157/// Type alias for JSON responses in controller methods.
158pub type Json<T> = T;
159
160/// Trait for constructing a request struct from the HTTP context.
161///
162/// Implemented automatically by the `#[get]`, `#[post]`, `#[put]`, `#[delete]`
163/// proc macros for request types. Custom implementations are also supported.
164///
165/// # Automatic implementation
166///
167/// For POST/PUT/PATCH routes, the macro will attempt `serde_json::from_slice`
168/// on the request body. For GET/DELETE routes with path parameters, the macro
169/// will extract parameters from `ctx.request().route_params()` by field name.
170///
171/// # Manual implementation
172///
173/// ```ignore
174/// #[async_trait::async_trait]
175/// impl FromHttpContext for MyRequest {
176/// async fn from_http_context(ctx: &dyn IHttpContext) -> Result<Self> {
177/// // custom construction logic
178/// }
179/// }
180/// ```
181#[async_trait::async_trait]
182pub trait FromHttpContext: Sized + Send + 'static {
183 async fn from_http_context(ctx: &dyn IHttpContext) -> Result<Self>;
184}