rws_server/handler/
path.rs1use std::fmt::Display;
4use std::str::FromStr;
5
6use super::{rejection, FromRequestParts, RequestParts};
7use crate::response::{Response, STATUS_CODE_REASON_PHRASE};
8use crate::router::PathParams;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct Path<T>(pub T);
19
20impl<T> std::ops::Deref for Path<T> {
21 type Target = T;
22 fn deref(&self) -> &T {
23 &self.0
24 }
25}
26
27impl<S, T: FromPath> FromRequestParts<S> for Path<T> {
28 fn from_request_parts(parts: &RequestParts<'_, S>) -> Result<Self, Response> {
29 T::from_path(parts.params, parts.path_names)
30 .map(Path)
31 .map_err(|detail| rejection(parts.request, STATUS_CODE_REASON_PHRASE.n400_bad_request, &detail))
32 }
33}
34
35pub trait FromPath: Sized {
42 const ARITY: usize;
44 const NAMES: &'static [&'static str] = &[];
47 fn from_path(params: &PathParams, names: &[&str]) -> Result<Self, String>;
50}
51
52pub fn parse_param<T>(params: &PathParams, name: &str) -> Result<T, String>
54where
55 T: FromStr,
56 T::Err: Display,
57{
58 let raw = params.get(name).ok_or_else(|| format!("the route has no path parameter `{name}`"))?;
59 let decoded = percent_decode(raw);
60 decoded.parse::<T>().map_err(|e| format!("path parameter `{name}`: {e}"))
61}
62
63fn percent_decode(raw: &str) -> String {
66 if !raw.contains('%') {
67 return raw.to_string();
68 }
69 let b = raw.as_bytes();
70 let mut out = Vec::with_capacity(b.len());
71 let mut i = 0;
72 let hex = |c: u8| (c as char).to_digit(16);
73 while i < b.len() {
74 if b[i] == b'%' {
75 if let (Some(h), Some(l)) = (b.get(i + 1).and_then(|c| hex(*c)), b.get(i + 2).and_then(|c| hex(*c))) {
76 out.push((h * 16 + l) as u8);
77 i += 3;
78 continue;
79 }
80 }
81 out.push(b[i]);
82 i += 1;
83 }
84 String::from_utf8_lossy(&out).into_owned()
85}
86
87pub const fn path_fits<T: FromPath>(route: &[&str]) -> bool {
90 if T::NAMES.is_empty() {
91 return T::ARITY == route.len();
92 }
93 if T::NAMES.len() != route.len() {
94 return false;
95 }
96 let mut i = 0;
97 while i < T::NAMES.len() {
98 if !contains(route, T::NAMES[i]) {
99 return false;
100 }
101 i += 1;
102 }
103 true
104}
105
106const fn contains(list: &[&str], name: &str) -> bool {
107 let mut i = 0;
108 while i < list.len() {
109 if str_eq(list[i], name) {
110 return true;
111 }
112 i += 1;
113 }
114 false
115}
116
117const fn str_eq(a: &str, b: &str) -> bool {
118 let (a, b) = (a.as_bytes(), b.as_bytes());
119 if a.len() != b.len() {
120 return false;
121 }
122 let mut i = 0;
123 while i < a.len() {
124 if a[i] != b[i] {
125 return false;
126 }
127 i += 1;
128 }
129 true
130}
131
132fn one<T>(params: &PathParams, names: &[&str]) -> Result<T, String>
133where
134 T: FromStr,
135 T::Err: Display,
136{
137 match names {
138 [name] => parse_param(params, name),
139 _ => Err(format!("expected 1 path parameter, the route passes {}", names.len())),
140 }
141}
142
143macro_rules! scalar_from_path {
144 ($($t:ty),*) => {$(
145 impl FromPath for $t {
146 const ARITY: usize = 1;
147 fn from_path(params: &PathParams, names: &[&str]) -> Result<Self, String> {
148 one(params, names)
149 }
150 }
151 )*};
152}
153
154scalar_from_path!(
155 u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64, bool, char, String,
156 std::net::IpAddr, std::net::Ipv4Addr, std::net::Ipv6Addr
157);
158
159macro_rules! tuple_from_path {
160 ($n:expr; $($t:ident $i:tt),+) => {
161 impl<$($t),+> FromPath for ($($t,)+)
162 where $($t: FromStr, $t::Err: Display),+
163 {
164 const ARITY: usize = $n;
165 fn from_path(params: &PathParams, names: &[&str]) -> Result<Self, String> {
166 if names.len() != $n {
167 return Err(format!("expected {} path parameters, the route passes {}", $n, names.len()));
168 }
169 Ok(($(parse_param::<$t>(params, names[$i])?,)+))
170 }
171 }
172 };
173}
174
175tuple_from_path!(1; A 0);
176tuple_from_path!(2; A 0, B 1);
177tuple_from_path!(3; A 0, B 1, C 2);
178tuple_from_path!(4; A 0, B 1, C 2, D 3);
179tuple_from_path!(5; A 0, B 1, C 2, D 3, E 4);
180tuple_from_path!(6; A 0, B 1, C 2, D 3, E 4, F 5);