Skip to main content

lrwf_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
125/// Helper: build a JSON response by serializing a value and writing it.
126pub async fn write_json_response<T: serde::Serialize + Send>(
127    resp: &mut dyn IHttpResponse,
128    value: &T,
129) -> Result<()> {
130    let json = serde_json::to_vec(value)?;
131    resp.set_header("content-type", "application/json");
132    resp.write_bytes(json).await
133}
134
135/// Helper: read JSON from the request body.
136pub async fn read_json_body<T: serde::de::DeserializeOwned>(req: &dyn IHttpRequest) -> Result<T> {
137    let bytes = req.body_bytes().await?;
138    serde_json::from_slice(&bytes).map_err(crate::error::Error::Serialization)
139}
140
141/// Type alias for JSON responses in controller methods.
142pub type Json<T> = T;
143
144/// Trait for constructing a request struct from the HTTP context.
145///
146/// Implemented automatically by the `#[get]`, `#[post]`, `#[put]`, `#[delete]`
147/// proc macros for request types. Custom implementations are also supported.
148///
149/// # Automatic implementation
150///
151/// For POST/PUT/PATCH routes, the macro will attempt `serde_json::from_slice`
152/// on the request body. For GET/DELETE routes with path parameters, the macro
153/// will extract parameters from `ctx.request().route_params()` by field name.
154///
155/// # Manual implementation
156///
157/// ```ignore
158/// #[async_trait::async_trait]
159/// impl FromHttpContext for MyRequest {
160///     async fn from_http_context(ctx: &dyn IHttpContext) -> Result<Self> {
161///         // custom construction logic
162///     }
163/// }
164/// ```
165#[async_trait::async_trait]
166pub trait FromHttpContext: Sized + Send + 'static {
167    async fn from_http_context(ctx: &dyn IHttpContext) -> Result<Self>;
168}