Skip to main content

tako_rs_extractors/
path.rs

1//! Path extraction from HTTP requests.
2//!
3//! This module provides the [`Path`](crate::path::Path) extractor for accessing the URI path from
4//! incoming HTTP requests. It wraps a reference to the path string, allowing
5//! efficient access to the request path without copying the underlying data.
6//!
7//! # Examples
8//!
9//! ```rust
10//! use tako::extractors::path::Path;
11//! use tako::types::Request;
12//!
13//! async fn handle_path(Path(path): Path<'_>) {
14//!     println!("Request path: {}", path);
15//!
16//!     // Check specific path patterns
17//!     if path.starts_with("/api/") {
18//!         println!("API endpoint");
19//!     }
20//!
21//!     // Extract path segments
22//!     let segments: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
23//!     println!("Path segments: {:?}", segments);
24//! }
25//! ```
26
27use std::convert::Infallible;
28
29use http::request::Parts;
30use serde::de::DeserializeOwned;
31use tako_rs_core::extractors::FromRequest;
32use tako_rs_core::extractors::FromRequestParts;
33use tako_rs_core::extractors::params::Params;
34use tako_rs_core::extractors::params::ParamsError;
35use tako_rs_core::types::Request;
36
37/// Owned URI-path extractor.
38///
39/// Returns the request path verbatim — no captures, no decoding. For typed
40/// path parameters use [`Path<T>`] (axum parity, generic over `T`).
41///
42/// # Examples
43///
44/// ```rust
45/// use tako::extractors::path::RawPath;
46/// use tako::types::Request;
47///
48/// async fn handler(RawPath(path): RawPath) {
49///     match path.as_str() {
50///         "/health" => println!("Health check endpoint"),
51///         "/api/users" => println!("Users API endpoint"),
52///         _ if path.starts_with("/static/") => println!("Static file request"),
53///         _ => println!("Other path: {}", path),
54///     }
55/// }
56/// ```
57#[doc(alias = "raw-path")]
58pub struct RawPath(pub String);
59
60impl<'a> FromRequest<'a> for RawPath {
61  type Error = Infallible;
62
63  fn from_request(
64    req: &'a mut Request,
65  ) -> impl core::future::Future<Output = core::result::Result<Self, Self::Error>> + Send + 'a {
66    futures_util::future::ready(Ok(RawPath(req.uri().path().to_string())))
67  }
68}
69
70impl<'a> FromRequestParts<'a> for RawPath {
71  type Error = Infallible;
72
73  fn from_request_parts(
74    parts: &'a mut Parts,
75  ) -> impl core::future::Future<Output = core::result::Result<Self, Self::Error>> + Send + 'a {
76    futures_util::future::ready(Ok(RawPath(parts.uri.path().to_string())))
77  }
78}
79
80/// Typed path-parameter extractor (axum parity).
81///
82/// `T` may be a single primitive (`Path<u64>`), a tuple (`Path<(u64, String)>`),
83/// a `Vec<T>` for repeated captures, an `Option<T>` (`None` when no captures
84/// matched), or a struct deriving `serde::Deserialize`.
85///
86/// Internally delegates to the path-params deserializer in `tako-core`, which
87/// has been extended in v2 to support tuples, sequences, and primitive
88/// destructuring on top of the original struct/map mode.
89///
90/// # Examples
91///
92/// ```rust,ignore
93/// use tako::extractors::path::Path;
94///
95/// // Single primitive
96/// async fn by_id(Path(id): Path<u64>) -> String { format!("id={id}") }
97///
98/// // Tuple
99/// async fn pair(Path((a, b)): Path<(String, u32)>) -> String { format!("{a}/{b}") }
100///
101/// // Struct
102/// #[derive(serde::Deserialize)]
103/// struct UserKey { tenant: String, user_id: u64 }
104/// async fn user(Path(key): Path<UserKey>) -> String {
105///   format!("{}:{}", key.tenant, key.user_id)
106/// }
107/// ```
108#[doc(alias = "path")]
109pub struct Path<T>(pub T);
110
111impl<'a, T> FromRequest<'a> for Path<T>
112where
113  T: DeserializeOwned + Send + 'a,
114{
115  type Error = ParamsError;
116
117  fn from_request(
118    req: &'a mut Request,
119  ) -> impl core::future::Future<Output = core::result::Result<Self, Self::Error>> + Send + 'a {
120    async move { Params::<T>::from_request(req).await.map(|p| Path(p.0)) }
121  }
122}
123
124impl<'a, T> FromRequestParts<'a> for Path<T>
125where
126  T: DeserializeOwned + Send + 'a,
127{
128  type Error = ParamsError;
129
130  fn from_request_parts(
131    parts: &'a mut Parts,
132  ) -> impl core::future::Future<Output = core::result::Result<Self, Self::Error>> + Send + 'a {
133    async move {
134      Params::<T>::from_request_parts(parts)
135        .await
136        .map(|p| Path(p.0))
137    }
138  }
139}