plexus_core/request.rs
1//! Raw HTTP request context and extraction trait for Plexus request extraction.
2//!
3//! This module defines:
4//! - `RawRequestContext` — the raw HTTP view passed through the dispatch chain
5//! - `PlexusRequest` — trait for typed field extraction from `RawRequestContext`
6//!
7//! Defined here (in plexus-core) so that:
8//! - The `Activation` trait can reference `RawRequestContext` without a circular dep
9//! - The generated dispatch code can call `PlexusRequest::extract` via `#crate_path::request`
10
11use std::net::SocketAddr;
12use crate::plexus::{AuthContext, PlexusError};
13
14/// The raw HTTP context available when extracting a typed request struct.
15///
16/// This is populated from the HTTP upgrade/connection phase and made available
17/// to every `#[derive(PlexusRequest)]` extraction.
18pub struct RawRequestContext {
19 /// HTTP headers from the request.
20 pub headers: http::HeaderMap,
21
22 /// The request URI (used for query-parameter extraction).
23 pub uri: http::Uri,
24
25 /// Authenticated user context, if authentication succeeded.
26 pub auth: Option<AuthContext>,
27
28 /// Remote peer socket address, if available.
29 pub peer: Option<SocketAddr>,
30}
31
32/// Trait implemented by `#[derive(PlexusRequest)]` structs.
33///
34/// A `PlexusRequest` struct is a typed view of an inbound HTTP request, where
35/// each field is extracted from a specific part of the raw context (cookie,
36/// header, query string, peer address, or auth context).
37///
38/// # Deriving
39///
40/// Use the `PlexusRequest` derive macro from `plexus_macros`:
41///
42/// ```ignore
43/// use plexus_macros::PlexusRequest;
44///
45/// #[derive(PlexusRequest)]
46/// struct MyRequest {
47/// #[from_cookie("access_token")]
48/// auth_token: String,
49///
50/// #[from_header("origin")]
51/// origin: Option<String>,
52///
53/// #[from_peer]
54/// peer_addr: Option<std::net::SocketAddr>,
55/// }
56/// ```
57pub trait PlexusRequest: Sized {
58 /// Extract a typed request from the raw HTTP context.
59 fn extract(ctx: &RawRequestContext) -> Result<Self, PlexusError>;
60
61 /// Return the JSON Schema for this request type as a `serde_json::Value`.
62 ///
63 /// The schema includes `x-plexus-source` metadata on each field describing
64 /// where the field value is extracted from (cookie, header, peer, etc.).
65 ///
66 /// The default implementation returns `None`; the `#[derive(PlexusRequest)]`
67 /// macro generates a concrete implementation with the full schema.
68 fn request_schema() -> Option<serde_json::Value> {
69 None
70 }
71}
72
73/// Trait for newtype field types that carry their own extraction and validation logic.
74///
75/// Implement this trait on a newtype wrapper to enable `#[derive(PlexusRequest)]`
76/// to extract it without an explicit source annotation. The macro generates:
77///
78/// ```ignore
79/// let field_name = FieldType::extract_from_raw(ctx)?;
80/// ```
81///
82/// for fields of any type that implements `PlexusRequestField`.
83pub trait PlexusRequestField: Sized {
84 /// Extract and validate `Self` from the raw HTTP request context.
85 ///
86 /// Return `Ok(Self)` on success, or a `PlexusError` (typically
87 /// `PlexusError::Unauthenticated`) on validation failure.
88 fn extract_from_raw(ctx: &RawRequestContext) -> Result<Self, PlexusError>;
89}
90
91/// Parse a named cookie from a raw `Cookie` header value.
92///
93/// The cookie header value is like `"session=abc; access_token=tok123; other=xyz"`.
94/// Returns the value for the first matching key, or `None` if not found.
95///
96/// This is exported so that the `#[derive(PlexusRequest)]` generated code can call it.
97pub fn parse_cookie<'a>(cookie_str: &'a str, name: &str) -> Option<&'a str> {
98 for part in cookie_str.split(';') {
99 let part = part.trim();
100 if let Some(rest) = part.strip_prefix(name) {
101 if let Some(value) = rest.strip_prefix('=') {
102 return Some(value);
103 }
104 }
105 }
106 None
107}