1use crate::r#match::{AllowedMethods, RouteLookup, RouteMatch};
47use fastapi_core::{Handler, Method};
48use std::collections::HashMap;
49use std::fmt;
50use std::sync::Arc;
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
54pub enum Converter {
55 #[default]
57 Str,
58 Int,
60 Float,
62 Uuid,
64 Path,
68}
69
70#[derive(Debug, Clone, PartialEq)]
72pub enum ParamValue {
73 Str(String),
75 Int(i64),
77 Float(f64),
79 Uuid(String),
81 Path(String),
83}
84
85impl ParamValue {
86 #[must_use]
88 pub fn as_str(&self) -> &str {
89 match self {
90 Self::Str(s) | Self::Uuid(s) | Self::Path(s) => s,
91 Self::Int(_) | Self::Float(_) => {
92 ""
95 }
96 }
97 }
98
99 #[must_use]
101 pub fn as_int(&self) -> Option<i64> {
102 match self {
103 Self::Int(n) => Some(*n),
104 _ => None,
105 }
106 }
107
108 #[must_use]
110 pub fn as_float(&self) -> Option<f64> {
111 match self {
112 Self::Float(n) => Some(*n),
113 _ => None,
114 }
115 }
116
117 #[must_use]
119 pub fn into_string(self) -> Option<String> {
120 match self {
121 Self::Str(s) | Self::Uuid(s) | Self::Path(s) => Some(s),
122 Self::Int(_) | Self::Float(_) => None,
123 }
124 }
125}
126
127#[derive(Debug, Clone, PartialEq, Eq)]
129pub enum ConversionError {
130 InvalidInt {
132 value: String,
134 param: String,
136 },
137 InvalidFloat {
139 value: String,
141 param: String,
143 },
144 InvalidUuid {
146 value: String,
148 param: String,
150 },
151}
152
153impl fmt::Display for ConversionError {
154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155 match self {
156 Self::InvalidInt { value, param } => {
157 write!(
158 f,
159 "path parameter '{param}': '{value}' is not a valid integer"
160 )
161 }
162 Self::InvalidFloat { value, param } => {
163 write!(
164 f,
165 "path parameter '{param}': '{value}' is not a valid float"
166 )
167 }
168 Self::InvalidUuid { value, param } => {
169 write!(f, "path parameter '{param}': '{value}' is not a valid UUID")
170 }
171 }
172 }
173}
174
175impl std::error::Error for ConversionError {}
176
177impl Converter {
178 #[must_use]
180 pub fn matches(&self, value: &str) -> bool {
181 match self {
182 Self::Str => true,
183 Self::Int => value.parse::<i64>().is_ok(),
184 Self::Float => value.parse::<f64>().is_ok(),
185 Self::Uuid => is_uuid(value),
186 Self::Path => true,
187 }
188 }
189
190 pub fn convert(&self, value: &str, param_name: &str) -> Result<ParamValue, ConversionError> {
196 match self {
197 Self::Str => Ok(ParamValue::Str(value.to_string())),
198 Self::Int => {
199 value
200 .parse::<i64>()
201 .map(ParamValue::Int)
202 .map_err(|_| ConversionError::InvalidInt {
203 value: value.to_string(),
204 param: param_name.to_string(),
205 })
206 }
207 Self::Float => value.parse::<f64>().map(ParamValue::Float).map_err(|_| {
208 ConversionError::InvalidFloat {
209 value: value.to_string(),
210 param: param_name.to_string(),
211 }
212 }),
213 Self::Uuid => {
214 if is_uuid(value) {
215 Ok(ParamValue::Uuid(value.to_string()))
216 } else {
217 Err(ConversionError::InvalidUuid {
218 value: value.to_string(),
219 param: param_name.to_string(),
220 })
221 }
222 }
223 Self::Path => Ok(ParamValue::Path(value.to_string())),
224 }
225 }
226
227 #[must_use]
229 pub fn type_name(&self) -> &'static str {
230 match self {
231 Self::Str => "string",
232 Self::Int => "integer",
233 Self::Float => "float",
234 Self::Uuid => "UUID",
235 Self::Path => "path",
236 }
237 }
238}
239
240fn is_uuid(s: &str) -> bool {
244 if s.len() != 36 {
246 return false;
247 }
248
249 let bytes = s.as_bytes();
250
251 if bytes[8] != b'-' || bytes[13] != b'-' || bytes[18] != b'-' || bytes[23] != b'-' {
253 return false;
254 }
255
256 bytes.iter().enumerate().all(|(i, &b)| {
258 if i == 8 || i == 13 || i == 18 || i == 23 {
259 true } else {
261 b.is_ascii_hexdigit()
262 }
263 })
264}
265
266#[derive(Debug, Clone, Default)]
268pub struct ParamInfo {
269 pub name: String,
271 pub converter: Converter,
273 pub title: Option<String>,
275 pub description: Option<String>,
277 pub deprecated: bool,
279 pub example: Option<serde_json::Value>,
281 pub examples: Vec<(String, serde_json::Value)>,
283}
284
285impl ParamInfo {
286 #[must_use]
288 pub fn new(name: impl Into<String>, converter: Converter) -> Self {
289 Self {
290 name: name.into(),
291 converter,
292 title: None,
293 description: None,
294 deprecated: false,
295 example: None,
296 examples: Vec::new(),
297 }
298 }
299
300 #[must_use]
302 pub fn with_title(mut self, title: impl Into<String>) -> Self {
303 self.title = Some(title.into());
304 self
305 }
306
307 #[must_use]
309 pub fn with_description(mut self, description: impl Into<String>) -> Self {
310 self.description = Some(description.into());
311 self
312 }
313
314 #[must_use]
316 pub fn deprecated(mut self) -> Self {
317 self.deprecated = true;
318 self
319 }
320
321 #[must_use]
323 pub fn with_example(mut self, example: serde_json::Value) -> Self {
324 self.example = Some(example);
325 self
326 }
327
328 #[must_use]
330 pub fn with_named_example(mut self, name: impl Into<String>, value: serde_json::Value) -> Self {
331 self.examples.push((name.into(), value));
332 self
333 }
334}
335
336#[must_use]
363pub fn extract_path_params(path: &str) -> Vec<ParamInfo> {
364 path.split('/')
365 .filter(|s| !s.is_empty())
366 .filter_map(|s| {
367 if s.starts_with('{') && s.ends_with('}') {
368 let inner = &s[1..s.len() - 1];
369 if let Some(name) = inner.strip_prefix('*') {
371 return Some(ParamInfo::new(name, Converter::Path));
372 }
373 let (name, converter) = if let Some(pos) = inner.find(':') {
374 let conv = match &inner[pos + 1..] {
375 "int" => Converter::Int,
376 "float" => Converter::Float,
377 "uuid" => Converter::Uuid,
378 "path" => Converter::Path,
379 _ => Converter::Str,
380 };
381 (&inner[..pos], conv)
382 } else {
383 (inner, Converter::Str)
384 };
385 Some(ParamInfo::new(name, converter))
386 } else {
387 None
388 }
389 })
390 .collect()
391}
392
393#[derive(Debug, Clone)]
398pub struct RouteResponse {
399 pub status: u16,
401 pub schema_name: String,
403 pub description: String,
405 pub content_type: String,
407}
408
409impl RouteResponse {
410 #[must_use]
412 pub fn new(
413 status: u16,
414 schema_name: impl Into<String>,
415 description: impl Into<String>,
416 ) -> Self {
417 Self {
418 status,
419 schema_name: schema_name.into(),
420 description: description.into(),
421 content_type: "application/json".to_string(),
422 }
423 }
424
425 #[must_use]
427 pub fn with_content_type(mut self, content_type: impl Into<String>) -> Self {
428 self.content_type = content_type.into();
429 self
430 }
431}
432
433#[derive(Debug, Clone, Default)]
437pub struct RouteSecurityRequirement {
438 pub scheme: String,
440 pub scopes: Vec<String>,
442}
443
444impl RouteSecurityRequirement {
445 #[must_use]
447 pub fn new(scheme: impl Into<String>) -> Self {
448 Self {
449 scheme: scheme.into(),
450 scopes: Vec::new(),
451 }
452 }
453
454 #[must_use]
456 pub fn with_scopes(
457 scheme: impl Into<String>,
458 scopes: impl IntoIterator<Item = impl Into<String>>,
459 ) -> Self {
460 Self {
461 scheme: scheme.into(),
462 scopes: scopes.into_iter().map(Into::into).collect(),
463 }
464 }
465}
466
467pub struct Route {
481 pub path: String,
483 pub method: Method,
485 pub operation_id: String,
487 pub summary: Option<String>,
489 pub description: Option<String>,
491 pub tags: Vec<String>,
493 pub deprecated: bool,
495 pub path_params: Vec<ParamInfo>,
497 pub request_body_schema: Option<String>,
499 pub request_body_content_type: Option<String>,
501 pub request_body_required: bool,
503 pub security: Vec<RouteSecurityRequirement>,
508 pub responses: Vec<RouteResponse>,
512 handler: Arc<dyn Handler>,
514}
515
516impl fmt::Debug for Route {
517 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
518 let mut s = f.debug_struct("Route");
519 s.field("path", &self.path)
520 .field("method", &self.method)
521 .field("operation_id", &self.operation_id);
522 if let Some(ref summary) = self.summary {
523 s.field("summary", summary);
524 }
525 if let Some(ref desc) = self.description {
526 s.field("description", desc);
527 }
528 if !self.tags.is_empty() {
529 s.field("tags", &self.tags);
530 }
531 if self.deprecated {
532 s.field("deprecated", &self.deprecated);
533 }
534 if !self.path_params.is_empty() {
535 s.field("path_params", &self.path_params);
536 }
537 if let Some(ref schema) = self.request_body_schema {
538 s.field("request_body_schema", schema);
539 }
540 if let Some(ref content_type) = self.request_body_content_type {
541 s.field("request_body_content_type", content_type);
542 }
543 if self.request_body_required {
544 s.field("request_body_required", &self.request_body_required);
545 }
546 if !self.security.is_empty() {
547 s.field("security", &self.security);
548 }
549 if !self.responses.is_empty() {
550 s.field("responses", &self.responses);
551 }
552 s.field("handler", &"<handler>").finish()
553 }
554}
555
556#[derive(Debug, Clone)]
558pub struct RouteConflictError {
559 pub method: Method,
561 pub new_path: String,
563 pub existing_path: String,
565}
566
567impl fmt::Display for RouteConflictError {
568 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
569 write!(
570 f,
571 "route conflict for {}: {} conflicts with {}",
572 self.method, self.new_path, self.existing_path
573 )
574 }
575}
576
577impl std::error::Error for RouteConflictError {}
578
579#[derive(Debug, Clone)]
581pub struct InvalidRouteError {
582 pub path: String,
584 pub message: String,
586}
587
588impl InvalidRouteError {
589 #[must_use]
591 pub fn new(path: impl Into<String>, message: impl Into<String>) -> Self {
592 Self {
593 path: path.into(),
594 message: message.into(),
595 }
596 }
597}
598
599impl fmt::Display for InvalidRouteError {
600 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
601 write!(f, "invalid route path '{}': {}", self.path, self.message)
602 }
603}
604
605impl std::error::Error for InvalidRouteError {}
606
607#[derive(Debug, Clone)]
609pub enum RouteAddError {
610 Conflict(RouteConflictError),
612 InvalidPath(InvalidRouteError),
614}
615
616impl fmt::Display for RouteAddError {
617 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
618 match self {
619 Self::Conflict(err) => err.fmt(f),
620 Self::InvalidPath(err) => err.fmt(f),
621 }
622 }
623}
624
625impl std::error::Error for RouteAddError {
626 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
627 match self {
628 Self::Conflict(err) => Some(err),
629 Self::InvalidPath(err) => Some(err),
630 }
631 }
632}
633
634impl From<RouteConflictError> for RouteAddError {
635 fn from(err: RouteConflictError) -> Self {
636 Self::Conflict(err)
637 }
638}
639
640impl From<InvalidRouteError> for RouteAddError {
641 fn from(err: InvalidRouteError) -> Self {
642 Self::InvalidPath(err)
643 }
644}
645
646impl Route {
647 pub fn new<H>(method: Method, path: impl Into<String>, handler: H) -> Self
664 where
665 H: Handler + 'static,
666 {
667 let path = path.into();
668 let operation_id = path.replace('/', "_").replace(['{', '}'], "");
669 let path_params = extract_path_params(&path);
670 Self {
671 path,
672 method,
673 operation_id,
674 summary: None,
675 description: None,
676 tags: Vec::new(),
677 deprecated: false,
678 path_params,
679 request_body_schema: None,
680 request_body_content_type: None,
681 request_body_required: false,
682 security: Vec::new(),
683 responses: Vec::new(),
684 handler: Arc::new(handler),
685 }
686 }
687
688 pub fn with_arc_handler(
692 method: Method,
693 path: impl Into<String>,
694 handler: Arc<dyn Handler>,
695 ) -> Self {
696 let path = path.into();
697 let operation_id = path.replace('/', "_").replace(['{', '}'], "");
698 let path_params = extract_path_params(&path);
699 Self {
700 path,
701 method,
702 operation_id,
703 summary: None,
704 description: None,
705 tags: Vec::new(),
706 deprecated: false,
707 path_params,
708 request_body_schema: None,
709 request_body_content_type: None,
710 request_body_required: false,
711 security: Vec::new(),
712 responses: Vec::new(),
713 handler,
714 }
715 }
716
717 #[must_use]
719 pub fn handler(&self) -> &Arc<dyn Handler> {
720 &self.handler
721 }
722
723 #[must_use]
731 pub fn with_placeholder_handler(method: Method, path: impl Into<String>) -> Self {
732 Self::new(method, path, PlaceholderHandler)
733 }
734
735 #[must_use]
737 pub fn summary(mut self, summary: impl Into<String>) -> Self {
738 self.summary = Some(summary.into());
739 self
740 }
741
742 #[must_use]
744 pub fn description(mut self, description: impl Into<String>) -> Self {
745 self.description = Some(description.into());
746 self
747 }
748
749 #[must_use]
751 pub fn operation_id(mut self, operation_id: impl Into<String>) -> Self {
752 self.operation_id = operation_id.into();
753 self
754 }
755
756 #[must_use]
758 pub fn tag(mut self, tag: impl Into<String>) -> Self {
759 self.tags.push(tag.into());
760 self
761 }
762
763 #[must_use]
765 pub fn tags(mut self, tags: impl IntoIterator<Item = impl Into<String>>) -> Self {
766 self.tags.extend(tags.into_iter().map(Into::into));
767 self
768 }
769
770 #[must_use]
772 pub fn deprecated(mut self) -> Self {
773 self.deprecated = true;
774 self
775 }
776
777 #[must_use]
782 pub fn request_body(
783 mut self,
784 schema: impl Into<String>,
785 content_type: impl Into<String>,
786 required: bool,
787 ) -> Self {
788 self.request_body_schema = Some(schema.into());
789 self.request_body_content_type = Some(content_type.into());
790 self.request_body_required = required;
791 self
792 }
793
794 #[must_use]
815 pub fn security(
816 mut self,
817 scheme: impl Into<String>,
818 scopes: impl IntoIterator<Item = impl Into<String>>,
819 ) -> Self {
820 self.security
821 .push(RouteSecurityRequirement::with_scopes(scheme, scopes));
822 self
823 }
824
825 #[must_use]
839 pub fn security_scheme(mut self, scheme: impl Into<String>) -> Self {
840 self.security.push(RouteSecurityRequirement::new(scheme));
841 self
842 }
843
844 #[must_use]
866 pub fn response(
867 mut self,
868 status: u16,
869 schema_name: impl Into<String>,
870 description: impl Into<String>,
871 ) -> Self {
872 self.responses
873 .push(RouteResponse::new(status, schema_name, description));
874 self
875 }
876
877 #[must_use]
879 pub fn has_responses(&self) -> bool {
880 !self.responses.is_empty()
881 }
882
883 #[must_use]
885 pub fn has_request_body(&self) -> bool {
886 self.request_body_schema.is_some()
887 }
888
889 #[must_use]
891 pub fn has_path_params(&self) -> bool {
892 !self.path_params.is_empty()
893 }
894
895 #[must_use]
897 pub fn has_security(&self) -> bool {
898 !self.security.is_empty()
899 }
900}
901
902struct PlaceholderHandler;
907
908impl Handler for PlaceholderHandler {
909 fn call<'a>(
910 &'a self,
911 _ctx: &'a fastapi_core::RequestContext,
912 _req: &'a mut fastapi_core::Request,
913 ) -> fastapi_core::BoxFuture<'a, fastapi_core::Response> {
914 Box::pin(async {
915 fastapi_core::Response::with_status(fastapi_core::StatusCode::from_u16(501))
917 })
918 }
919}
920
921struct Node {
923 segment: String,
924 children: Vec<Node>,
925 param: Option<ParamInfo>,
926 routes: HashMap<Method, usize>,
927}
928
929impl Node {
930 fn new(segment: impl Into<String>) -> Self {
931 Self {
932 segment: segment.into(),
933 children: Vec::new(),
934 param: None,
935 routes: HashMap::new(),
936 }
937 }
938
939 fn find_static(&self, segment: &str) -> Option<&Node> {
940 self.children
941 .iter()
942 .find(|c| c.param.is_none() && c.segment == segment)
943 }
944
945 fn find_param(&self) -> Option<&Node> {
946 self.children.iter().find(|c| c.param.is_some())
947 }
948}
949
950pub struct Router {
952 root: Node,
953 routes: Vec<Route>,
954}
955
956impl Router {
957 #[must_use]
959 pub fn new() -> Self {
960 Self {
961 root: Node::new(""),
962 routes: Vec::new(),
963 }
964 }
965
966 pub fn add(&mut self, route: Route) -> Result<(), RouteAddError> {
975 if let Some(conflict) = self.find_conflict(&route) {
976 return Err(RouteAddError::Conflict(conflict));
977 }
978
979 let route_idx = self.routes.len();
980 let path = route.path.clone();
981 let method = route.method;
982 self.routes.push(route);
983
984 let segments = parse_path(&path);
985 validate_path_segments(&path, &segments)?;
986 let mut node = &mut self.root;
987
988 for seg in segments {
989 let (segment, param) = match seg {
990 PathSegment::Static(s) => (s.to_string(), None),
991 PathSegment::Param { name, converter } => {
992 let info = ParamInfo::new(name, converter);
993 (format!("{{{name}}}"), Some(info))
994 }
995 };
996
997 let child_idx = node.children.iter().position(|c| c.segment == segment);
999
1000 if let Some(idx) = child_idx {
1001 node = &mut node.children[idx];
1002 } else {
1003 let mut new_node = Node::new(&segment);
1004 new_node.param = param;
1005 node.children.push(new_node);
1006 node = node.children.last_mut().unwrap();
1007 }
1008 }
1009
1010 node.routes.insert(method, route_idx);
1011 Ok(())
1012 }
1013
1014 #[must_use]
1016 pub fn lookup<'a>(&'a self, path: &'a str, method: Method) -> RouteLookup<'a> {
1017 let (node, params) = match self.match_node(path) {
1018 Some(found) => found,
1019 None => return RouteLookup::NotFound,
1020 };
1021
1022 if let Some(&idx) = node.routes.get(&method) {
1023 return RouteLookup::Match(RouteMatch {
1024 route: &self.routes[idx],
1025 params,
1026 });
1027 }
1028
1029 if method == Method::Head {
1031 if let Some(&idx) = node.routes.get(&Method::Get) {
1032 return RouteLookup::Match(RouteMatch {
1033 route: &self.routes[idx],
1034 params,
1035 });
1036 }
1037 }
1038
1039 if node.routes.is_empty() {
1040 return RouteLookup::NotFound;
1041 }
1042
1043 let allowed = AllowedMethods::new(node.routes.keys().copied().collect());
1044 RouteLookup::MethodNotAllowed { allowed }
1045 }
1046
1047 #[must_use]
1049 pub fn match_path<'a>(&'a self, path: &'a str, method: Method) -> Option<RouteMatch<'a>> {
1050 match self.lookup(path, method) {
1051 RouteLookup::Match(matched) => Some(matched),
1052 RouteLookup::MethodNotAllowed { .. } | RouteLookup::NotFound => None,
1053 }
1054 }
1055
1056 #[must_use]
1058 pub fn routes(&self) -> &[Route] {
1059 &self.routes
1060 }
1061
1062 pub fn mount(mut self, prefix: &str, child: Router) -> Result<Self, RouteAddError> {
1090 let prefix = prefix.trim_end_matches('/');
1091
1092 for route in child.routes {
1093 let child_path = if route.path == "/" {
1094 String::new()
1095 } else if route.path.starts_with('/') {
1096 route.path.clone()
1097 } else {
1098 format!("/{}", route.path)
1099 };
1100
1101 let full_path = if prefix.is_empty() {
1102 if child_path.is_empty() {
1103 "/".to_string()
1104 } else {
1105 child_path
1106 }
1107 } else if child_path.is_empty() {
1108 prefix.to_string()
1109 } else {
1110 format!("{}{}", prefix, child_path)
1111 };
1112
1113 let path_params = extract_path_params(&full_path);
1115 let mounted = Route {
1116 path: full_path,
1117 method: route.method,
1118 operation_id: route.operation_id,
1119 summary: route.summary,
1120 description: route.description,
1121 tags: route.tags,
1122 deprecated: route.deprecated,
1123 path_params,
1124 request_body_schema: route.request_body_schema,
1125 request_body_content_type: route.request_body_content_type,
1126 request_body_required: route.request_body_required,
1127 security: route.security,
1128 responses: route.responses,
1129 handler: route.handler,
1130 };
1131
1132 self.add(mounted)?;
1133 }
1134
1135 Ok(self)
1136 }
1137
1138 #[must_use]
1146 pub fn nest(self, prefix: &str, child: Router) -> Self {
1147 self.mount(prefix, child)
1148 .expect("route conflict when nesting router")
1149 }
1150
1151 fn find_conflict(&self, route: &Route) -> Option<RouteConflictError> {
1152 for existing in &self.routes {
1153 if existing.method != route.method {
1154 continue;
1155 }
1156
1157 if paths_conflict(&existing.path, &route.path) {
1158 return Some(RouteConflictError {
1159 method: route.method,
1160 new_path: route.path.clone(),
1161 existing_path: existing.path.clone(),
1162 });
1163 }
1164 }
1165
1166 None
1167 }
1168
1169 fn match_node<'a>(&'a self, path: &'a str) -> Option<(&'a Node, Vec<(&'a str, &'a str)>)> {
1170 let mut range_iter = SegmentRangeIter::new(path);
1172
1173 let mut ranges_buf: [(usize, usize); 16] = [(0, 0); 16];
1176 let mut ranges_vec: Vec<(usize, usize)> = Vec::new();
1177 let mut range_count = 0;
1178
1179 for range in &mut range_iter {
1180 match range_count.cmp(&16) {
1181 std::cmp::Ordering::Less => {
1182 ranges_buf[range_count] = range;
1183 }
1184 std::cmp::Ordering::Equal => {
1185 ranges_vec = ranges_buf.to_vec();
1187 ranges_vec.push(range);
1188 }
1189 std::cmp::Ordering::Greater => {
1190 ranges_vec.push(range);
1191 }
1192 }
1193 range_count += 1;
1194 }
1195
1196 let ranges: &[(usize, usize)] = if range_count <= 16 {
1197 &ranges_buf[..range_count]
1198 } else {
1199 &ranges_vec
1200 };
1201
1202 let last_end = ranges.last().map_or(0, |(_, end)| *end);
1203 let mut params = Vec::new();
1204 let mut node = &self.root;
1205
1206 for &(start, end) in ranges {
1207 let segment = &path[start..end];
1208
1209 if let Some(child) = node.find_static(segment) {
1211 node = child;
1212 continue;
1213 }
1214
1215 if let Some(child) = node.find_param() {
1217 if let Some(ref info) = child.param {
1218 if info.converter == Converter::Path {
1219 let value = &path[start..last_end];
1220 params.push((info.name.as_str(), value));
1221 node = child;
1222 return Some((node, params));
1224 }
1225 if info.converter.matches(segment) {
1226 params.push((info.name.as_str(), segment));
1227 node = child;
1228 continue;
1229 }
1230 }
1231 }
1232
1233 return None;
1234 }
1235
1236 Some((node, params))
1237 }
1238}
1239
1240impl Default for Router {
1241 fn default() -> Self {
1242 Self::new()
1243 }
1244}
1245
1246enum PathSegment<'a> {
1247 Static(&'a str),
1248 Param { name: &'a str, converter: Converter },
1249}
1250
1251fn parse_path(path: &str) -> Vec<PathSegment<'_>> {
1252 path.split('/')
1253 .filter(|s| !s.is_empty())
1254 .map(|s| {
1255 if s.starts_with('{') && s.ends_with('}') {
1256 let inner = &s[1..s.len() - 1];
1257 if let Some(name) = inner.strip_prefix('*') {
1259 return PathSegment::Param {
1260 name,
1261 converter: Converter::Path,
1262 };
1263 }
1264 let (name, converter) = if let Some(pos) = inner.find(':') {
1265 let conv = match &inner[pos + 1..] {
1266 "int" => Converter::Int,
1267 "float" => Converter::Float,
1268 "uuid" => Converter::Uuid,
1269 "path" => Converter::Path,
1270 _ => Converter::Str,
1271 };
1272 (&inner[..pos], conv)
1273 } else {
1274 (inner, Converter::Str)
1275 };
1276 PathSegment::Param { name, converter }
1277 } else {
1278 PathSegment::Static(s)
1279 }
1280 })
1281 .collect()
1282}
1283
1284fn validate_path_segments(
1285 path: &str,
1286 segments: &[PathSegment<'_>],
1287) -> Result<(), InvalidRouteError> {
1288 for (idx, segment) in segments.iter().enumerate() {
1289 if let PathSegment::Param {
1290 name,
1291 converter: Converter::Path,
1292 } = segment
1293 {
1294 if idx + 1 != segments.len() {
1295 return Err(InvalidRouteError::new(
1296 path,
1297 format!(
1298 "wildcard '{{*{name}}}' or '{{{name}:path}}' must be the final segment"
1299 ),
1300 ));
1301 }
1302 }
1303 }
1304 Ok(())
1305}
1306
1307struct SegmentRangeIter<'a> {
1311 bytes: &'a [u8],
1312 idx: usize,
1313}
1314
1315impl<'a> SegmentRangeIter<'a> {
1316 fn new(path: &'a str) -> Self {
1317 Self {
1318 bytes: path.as_bytes(),
1319 idx: 0,
1320 }
1321 }
1322}
1323
1324impl Iterator for SegmentRangeIter<'_> {
1325 type Item = (usize, usize);
1326
1327 #[inline]
1328 fn next(&mut self) -> Option<Self::Item> {
1329 while self.idx < self.bytes.len() && self.bytes[self.idx] == b'/' {
1331 self.idx += 1;
1332 }
1333 if self.idx >= self.bytes.len() {
1334 return None;
1335 }
1336 let start = self.idx;
1337 while self.idx < self.bytes.len() && self.bytes[self.idx] != b'/' {
1339 self.idx += 1;
1340 }
1341 Some((start, self.idx))
1342 }
1343
1344 fn size_hint(&self) -> (usize, Option<usize>) {
1345 let remaining = self.bytes.len().saturating_sub(self.idx);
1347 (0, Some(remaining / 2 + 1))
1348 }
1349}
1350
1351fn paths_conflict(a: &str, b: &str) -> bool {
1352 let a_segments = parse_path(a);
1353 let b_segments = parse_path(b);
1354
1355 let a_has_path = matches!(
1356 a_segments.last(),
1357 Some(PathSegment::Param {
1358 converter: Converter::Path,
1359 ..
1360 })
1361 );
1362 let b_has_path = matches!(
1363 b_segments.last(),
1364 Some(PathSegment::Param {
1365 converter: Converter::Path,
1366 ..
1367 })
1368 );
1369 let min_len = a_segments.len().min(b_segments.len());
1370 let mut param_mismatch = false;
1371
1372 for (left, right) in a_segments.iter().take(min_len).zip(b_segments.iter()) {
1373 match (left, right) {
1374 (PathSegment::Static(a), PathSegment::Static(b)) => {
1375 if a != b {
1376 return false;
1377 }
1378 }
1379 (PathSegment::Static(_), PathSegment::Param { .. })
1380 | (PathSegment::Param { .. }, PathSegment::Static(_)) => {
1381 return false;
1383 }
1384 (
1385 PathSegment::Param {
1386 name: left_name,
1387 converter: left_conv,
1388 },
1389 PathSegment::Param {
1390 name: right_name,
1391 converter: right_conv,
1392 },
1393 ) => {
1394 if left_name != right_name || left_conv != right_conv {
1395 param_mismatch = true;
1396 }
1397 }
1398 }
1399 }
1400
1401 if a_segments.len() == b_segments.len() {
1402 return true;
1403 }
1404
1405 if param_mismatch {
1406 return true;
1407 }
1408
1409 if a_has_path && a_segments.len() == min_len {
1410 return true;
1411 }
1412
1413 if b_has_path && b_segments.len() == min_len {
1414 return true;
1415 }
1416
1417 false
1418}
1419
1420#[cfg(test)]
1421mod tests {
1422 use super::*;
1423 use fastapi_core::{BoxFuture, Request, RequestContext, Response};
1424
1425 struct TestHandler;
1428
1429 impl Handler for TestHandler {
1430 fn call<'a>(
1431 &'a self,
1432 _ctx: &'a RequestContext,
1433 _req: &'a mut Request,
1434 ) -> BoxFuture<'a, Response> {
1435 Box::pin(async { Response::ok() })
1436 }
1437 }
1438
1439 fn route(method: Method, path: &str) -> Route {
1441 Route::new(method, path, TestHandler)
1442 }
1443
1444 #[test]
1445 fn static_route_match() {
1446 let mut router = Router::new();
1447 router.add(route(Method::Get, "/users")).unwrap();
1448 router.add(route(Method::Get, "/items")).unwrap();
1449
1450 let m = router.match_path("/users", Method::Get);
1451 assert!(m.is_some());
1452 assert_eq!(m.unwrap().route.path, "/users");
1453
1454 let m = router.match_path("/items", Method::Get);
1455 assert!(m.is_some());
1456 assert_eq!(m.unwrap().route.path, "/items");
1457
1458 assert!(router.match_path("/other", Method::Get).is_none());
1460 }
1461
1462 #[test]
1463 fn nested_static_routes() {
1464 let mut router = Router::new();
1465 router.add(route(Method::Get, "/api/v1/users")).unwrap();
1466 router.add(route(Method::Get, "/api/v2/users")).unwrap();
1467
1468 let m = router.match_path("/api/v1/users", Method::Get);
1469 assert!(m.is_some());
1470 assert_eq!(m.unwrap().route.path, "/api/v1/users");
1471
1472 let m = router.match_path("/api/v2/users", Method::Get);
1473 assert!(m.is_some());
1474 assert_eq!(m.unwrap().route.path, "/api/v2/users");
1475 }
1476
1477 #[test]
1478 fn parameter_extraction() {
1479 let mut router = Router::new();
1480 router.add(route(Method::Get, "/users/{user_id}")).unwrap();
1481
1482 let m = router.match_path("/users/123", Method::Get);
1483 assert!(m.is_some());
1484 let m = m.unwrap();
1485 assert_eq!(m.route.path, "/users/{user_id}");
1486 assert_eq!(m.params.len(), 1);
1487 assert_eq!(m.params[0], ("user_id", "123"));
1488 }
1489
1490 #[test]
1491 fn multiple_parameters() {
1492 let mut router = Router::new();
1493 router
1494 .add(route(Method::Get, "/users/{user_id}/posts/{post_id}"))
1495 .unwrap();
1496
1497 let m = router.match_path("/users/42/posts/99", Method::Get);
1498 assert!(m.is_some());
1499 let m = m.unwrap();
1500 assert_eq!(m.params.len(), 2);
1501 assert_eq!(m.params[0], ("user_id", "42"));
1502 assert_eq!(m.params[1], ("post_id", "99"));
1503 }
1504
1505 #[test]
1506 fn int_converter() {
1507 let mut router = Router::new();
1508 router.add(route(Method::Get, "/items/{id:int}")).unwrap();
1509
1510 let m = router.match_path("/items/123", Method::Get);
1512 assert!(m.is_some());
1513 assert_eq!(m.unwrap().params[0], ("id", "123"));
1514
1515 let m = router.match_path("/items/-456", Method::Get);
1517 assert!(m.is_some());
1518
1519 assert!(router.match_path("/items/abc", Method::Get).is_none());
1521 assert!(router.match_path("/items/12.34", Method::Get).is_none());
1522 }
1523
1524 #[test]
1525 fn float_converter() {
1526 let mut router = Router::new();
1527 router
1528 .add(route(Method::Get, "/values/{val:float}"))
1529 .unwrap();
1530
1531 let m = router.match_path("/values/3.14", Method::Get);
1533 assert!(m.is_some());
1534 assert_eq!(m.unwrap().params[0], ("val", "3.14"));
1535
1536 let m = router.match_path("/values/42", Method::Get);
1538 assert!(m.is_some());
1539
1540 assert!(router.match_path("/values/abc", Method::Get).is_none());
1542 }
1543
1544 #[test]
1545 fn uuid_converter() {
1546 let mut router = Router::new();
1547 router
1548 .add(route(Method::Get, "/objects/{id:uuid}"))
1549 .unwrap();
1550
1551 let m = router.match_path("/objects/550e8400-e29b-41d4-a716-446655440000", Method::Get);
1553 assert!(m.is_some());
1554 assert_eq!(
1555 m.unwrap().params[0],
1556 ("id", "550e8400-e29b-41d4-a716-446655440000")
1557 );
1558
1559 assert!(
1561 router
1562 .match_path("/objects/not-a-uuid", Method::Get)
1563 .is_none()
1564 );
1565 assert!(router.match_path("/objects/123", Method::Get).is_none());
1566 }
1567
1568 #[test]
1569 fn path_converter_captures_slashes() {
1570 let mut router = Router::new();
1571 router
1572 .add(route(Method::Get, "/files/{path:path}"))
1573 .unwrap();
1574
1575 let m = router.match_path("/files/a/b/c.txt", Method::Get).unwrap();
1576 assert_eq!(m.params[0], ("path", "a/b/c.txt"));
1577 }
1578
1579 #[test]
1580 fn path_converter_must_be_terminal() {
1581 let mut router = Router::new();
1582 let result = router.add(route(Method::Get, "/files/{path:path}/edit"));
1583 assert!(matches!(result, Err(RouteAddError::InvalidPath(_))));
1584 }
1585
1586 #[test]
1587 fn method_dispatch() {
1588 let mut router = Router::new();
1589 router.add(route(Method::Get, "/items")).unwrap();
1590 router.add(route(Method::Post, "/items")).unwrap();
1591 router.add(route(Method::Delete, "/items/{id}")).unwrap();
1592
1593 let m = router.match_path("/items", Method::Get);
1595 assert!(m.is_some());
1596 assert_eq!(m.unwrap().route.method, Method::Get);
1597
1598 let m = router.match_path("/items", Method::Post);
1600 assert!(m.is_some());
1601 assert_eq!(m.unwrap().route.method, Method::Post);
1602
1603 let m = router.match_path("/items/123", Method::Delete);
1605 assert!(m.is_some());
1606 assert_eq!(m.unwrap().route.method, Method::Delete);
1607
1608 assert!(router.match_path("/items", Method::Put).is_none());
1610 }
1611
1612 #[test]
1613 fn lookup_method_not_allowed_includes_head() {
1614 let mut router = Router::new();
1615 router.add(route(Method::Get, "/users")).unwrap();
1616
1617 let result = router.lookup("/users", Method::Post);
1618 match result {
1619 RouteLookup::MethodNotAllowed { allowed } => {
1620 assert!(allowed.contains(Method::Get));
1621 assert!(allowed.contains(Method::Head));
1622 assert_eq!(allowed.header_value(), "GET, HEAD");
1623 }
1624 _ => panic!("expected MethodNotAllowed"),
1625 }
1626 }
1627
1628 #[test]
1629 fn lookup_method_not_allowed_multiple_methods() {
1630 let mut router = Router::new();
1631 router.add(route(Method::Get, "/users")).unwrap();
1632 router.add(route(Method::Post, "/users")).unwrap();
1633 router.add(route(Method::Delete, "/users")).unwrap();
1634
1635 let result = router.lookup("/users", Method::Put);
1636 match result {
1637 RouteLookup::MethodNotAllowed { allowed } => {
1638 assert_eq!(allowed.header_value(), "GET, HEAD, POST, DELETE");
1639 }
1640 _ => panic!("expected MethodNotAllowed"),
1641 }
1642 }
1643
1644 #[test]
1645 fn lookup_not_found_when_path_missing() {
1646 let mut router = Router::new();
1647 router.add(route(Method::Get, "/users")).unwrap();
1648
1649 assert!(matches!(
1650 router.lookup("/missing", Method::Get),
1651 RouteLookup::NotFound
1652 ));
1653 }
1654
1655 #[test]
1656 fn lookup_not_found_when_converter_mismatch() {
1657 let mut router = Router::new();
1658 router.add(route(Method::Get, "/items/{id:int}")).unwrap();
1659
1660 assert!(matches!(
1661 router.lookup("/items/abc", Method::Get),
1662 RouteLookup::NotFound
1663 ));
1664 }
1665
1666 #[test]
1667 fn static_takes_priority_over_param() {
1668 let mut router = Router::new();
1669 router.add(route(Method::Get, "/users/me")).unwrap();
1671 router.add(route(Method::Get, "/users/{id}")).unwrap();
1672
1673 let m = router.match_path("/users/me", Method::Get);
1675 assert!(m.is_some());
1676 let m = m.unwrap();
1677 assert_eq!(m.route.path, "/users/me");
1678 assert!(m.params.is_empty());
1679
1680 let m = router.match_path("/users/123", Method::Get);
1682 assert!(m.is_some());
1683 let m = m.unwrap();
1684 assert_eq!(m.route.path, "/users/{id}");
1685 assert_eq!(m.params[0], ("id", "123"));
1686 }
1687
1688 #[test]
1689 fn route_match_get_param() {
1690 let mut router = Router::new();
1691 router
1692 .add(route(Method::Get, "/users/{user_id}/posts/{post_id}"))
1693 .unwrap();
1694
1695 let m = router
1696 .match_path("/users/42/posts/99", Method::Get)
1697 .unwrap();
1698
1699 assert_eq!(m.get_param("user_id"), Some("42"));
1700 assert_eq!(m.get_param("post_id"), Some("99"));
1701 assert_eq!(m.get_param("unknown"), None);
1702 }
1703
1704 #[test]
1705 fn converter_matches() {
1706 assert!(Converter::Str.matches("anything"));
1707 assert!(Converter::Str.matches("123"));
1708
1709 assert!(Converter::Int.matches("123"));
1710 assert!(Converter::Int.matches("-456"));
1711 assert!(!Converter::Int.matches("12.34"));
1712 assert!(!Converter::Int.matches("abc"));
1713
1714 assert!(Converter::Float.matches("3.14"));
1715 assert!(Converter::Float.matches("42"));
1716 assert!(!Converter::Float.matches("abc"));
1717
1718 assert!(Converter::Uuid.matches("550e8400-e29b-41d4-a716-446655440000"));
1719 assert!(!Converter::Uuid.matches("not-a-uuid"));
1720
1721 assert!(Converter::Path.matches("any/path/here"));
1722 }
1723
1724 #[test]
1725 fn parse_path_segments() {
1726 let segments = parse_path("/users/{id}/posts/{post_id:int}");
1727 assert_eq!(segments.len(), 4);
1728
1729 match &segments[0] {
1730 PathSegment::Static(s) => assert_eq!(*s, "users"),
1731 _ => panic!("Expected static segment"),
1732 }
1733
1734 match &segments[1] {
1735 PathSegment::Param { name, converter } => {
1736 assert_eq!(*name, "id");
1737 assert_eq!(*converter, Converter::Str);
1738 }
1739 _ => panic!("Expected param segment"),
1740 }
1741
1742 match &segments[2] {
1743 PathSegment::Static(s) => assert_eq!(*s, "posts"),
1744 _ => panic!("Expected static segment"),
1745 }
1746
1747 match &segments[3] {
1748 PathSegment::Param { name, converter } => {
1749 assert_eq!(*name, "post_id");
1750 assert_eq!(*converter, Converter::Int);
1751 }
1752 _ => panic!("Expected param segment"),
1753 }
1754 }
1755
1756 #[test]
1761 fn extract_path_params_simple() {
1762 let params = extract_path_params("/users/{id}");
1763 assert_eq!(params.len(), 1);
1764 assert_eq!(params[0].name, "id");
1765 assert_eq!(params[0].converter, Converter::Str);
1766 }
1767
1768 #[test]
1769 fn extract_path_params_multiple() {
1770 let params = extract_path_params("/users/{id}/posts/{post_id}");
1771 assert_eq!(params.len(), 2);
1772 assert_eq!(params[0].name, "id");
1773 assert_eq!(params[1].name, "post_id");
1774 }
1775
1776 #[test]
1777 fn extract_path_params_typed_int() {
1778 let params = extract_path_params("/items/{item_id:int}");
1779 assert_eq!(params.len(), 1);
1780 assert_eq!(params[0].name, "item_id");
1781 assert_eq!(params[0].converter, Converter::Int);
1782 }
1783
1784 #[test]
1785 fn extract_path_params_typed_float() {
1786 let params = extract_path_params("/prices/{value:float}");
1787 assert_eq!(params.len(), 1);
1788 assert_eq!(params[0].name, "value");
1789 assert_eq!(params[0].converter, Converter::Float);
1790 }
1791
1792 #[test]
1793 fn extract_path_params_typed_uuid() {
1794 let params = extract_path_params("/resources/{uuid:uuid}");
1795 assert_eq!(params.len(), 1);
1796 assert_eq!(params[0].name, "uuid");
1797 assert_eq!(params[0].converter, Converter::Uuid);
1798 }
1799
1800 #[test]
1801 fn extract_path_params_wildcard_asterisk() {
1802 let params = extract_path_params("/files/{*filepath}");
1803 assert_eq!(params.len(), 1);
1804 assert_eq!(params[0].name, "filepath");
1805 assert_eq!(params[0].converter, Converter::Path);
1806 }
1807
1808 #[test]
1809 fn extract_path_params_wildcard_path_converter() {
1810 let params = extract_path_params("/static/{path:path}");
1811 assert_eq!(params.len(), 1);
1812 assert_eq!(params[0].name, "path");
1813 assert_eq!(params[0].converter, Converter::Path);
1814 }
1815
1816 #[test]
1817 fn extract_path_params_mixed_types() {
1818 let params = extract_path_params("/api/{version}/items/{id:int}/details/{slug}");
1819 assert_eq!(params.len(), 3);
1820 assert_eq!(params[0].name, "version");
1821 assert_eq!(params[0].converter, Converter::Str);
1822 assert_eq!(params[1].name, "id");
1823 assert_eq!(params[1].converter, Converter::Int);
1824 assert_eq!(params[2].name, "slug");
1825 assert_eq!(params[2].converter, Converter::Str);
1826 }
1827
1828 #[test]
1829 fn extract_path_params_no_params() {
1830 let params = extract_path_params("/static/path/no/params");
1831 assert!(params.is_empty());
1832 }
1833
1834 #[test]
1835 fn extract_path_params_root() {
1836 let params = extract_path_params("/");
1837 assert!(params.is_empty());
1838 }
1839
1840 #[test]
1845 fn param_info_new() {
1846 let info = ParamInfo::new("id", Converter::Int);
1847 assert_eq!(info.name, "id");
1848 assert_eq!(info.converter, Converter::Int);
1849 assert!(info.title.is_none());
1850 assert!(info.description.is_none());
1851 assert!(!info.deprecated);
1852 assert!(info.example.is_none());
1853 assert!(info.examples.is_empty());
1854 }
1855
1856 #[test]
1857 fn param_info_with_title() {
1858 let info = ParamInfo::new("id", Converter::Str).with_title("User ID");
1859 assert_eq!(info.title.as_deref(), Some("User ID"));
1860 }
1861
1862 #[test]
1863 fn param_info_with_description() {
1864 let info =
1865 ParamInfo::new("page", Converter::Int).with_description("Page number for pagination");
1866 assert_eq!(
1867 info.description.as_deref(),
1868 Some("Page number for pagination")
1869 );
1870 }
1871
1872 #[test]
1873 fn param_info_deprecated() {
1874 let info = ParamInfo::new("old", Converter::Str).deprecated();
1875 assert!(info.deprecated);
1876 }
1877
1878 #[test]
1879 fn param_info_with_example() {
1880 let info = ParamInfo::new("id", Converter::Int).with_example(serde_json::json!(42));
1881 assert_eq!(info.example, Some(serde_json::json!(42)));
1882 }
1883
1884 #[test]
1885 fn param_info_with_named_examples() {
1886 let info = ParamInfo::new("status", Converter::Str)
1887 .with_named_example("active", serde_json::json!("active"))
1888 .with_named_example("inactive", serde_json::json!("inactive"));
1889 assert_eq!(info.examples.len(), 2);
1890 assert_eq!(info.examples[0].0, "active");
1891 assert_eq!(info.examples[1].0, "inactive");
1892 }
1893
1894 #[test]
1895 fn param_info_builder_chain() {
1896 let info = ParamInfo::new("item_id", Converter::Int)
1897 .with_title("Item ID")
1898 .with_description("The unique item identifier")
1899 .deprecated()
1900 .with_example(serde_json::json!(123));
1901
1902 assert_eq!(info.name, "item_id");
1903 assert_eq!(info.converter, Converter::Int);
1904 assert_eq!(info.title.as_deref(), Some("Item ID"));
1905 assert_eq!(
1906 info.description.as_deref(),
1907 Some("The unique item identifier")
1908 );
1909 assert!(info.deprecated);
1910 assert_eq!(info.example, Some(serde_json::json!(123)));
1911 }
1912
1913 #[test]
1914 fn empty_router() {
1915 let router = Router::new();
1916 assert!(router.match_path("/anything", Method::Get).is_none());
1917 assert!(router.routes().is_empty());
1918 }
1919
1920 #[test]
1921 fn routes_accessor() {
1922 let mut router = Router::new();
1923 let _ = router.add(route(Method::Get, "/a"));
1924 let _ = router.add(route(Method::Post, "/b"));
1925
1926 assert_eq!(router.routes().len(), 2);
1927 assert_eq!(router.routes()[0].path, "/a");
1928 assert_eq!(router.routes()[1].path, "/b");
1929 }
1930
1931 #[test]
1936 fn conflict_same_method_same_path() {
1937 let mut router = Router::new();
1938 router.add(route(Method::Get, "/users")).unwrap();
1939
1940 let result = router.add(route(Method::Get, "/users"));
1941 assert!(result.is_err());
1942 let err = match result.unwrap_err() {
1943 RouteAddError::Conflict(err) => err,
1944 RouteAddError::InvalidPath(err) => {
1945 panic!("unexpected invalid path error: {err}")
1946 }
1947 };
1948 assert_eq!(err.method, Method::Get);
1949 assert_eq!(err.new_path, "/users");
1950 assert_eq!(err.existing_path, "/users");
1951 }
1952
1953 #[test]
1954 fn conflict_same_method_same_param_pattern() {
1955 let mut router = Router::new();
1956 router.add(route(Method::Get, "/users/{id}")).unwrap();
1957
1958 let result = router.add(route(Method::Get, "/users/{user_id}"));
1960 assert!(result.is_err());
1961 let err = match result.unwrap_err() {
1962 RouteAddError::Conflict(err) => err,
1963 RouteAddError::InvalidPath(err) => {
1964 panic!("unexpected invalid path error: {err}")
1965 }
1966 };
1967 assert_eq!(err.existing_path, "/users/{id}");
1968 assert_eq!(err.new_path, "/users/{user_id}");
1969 }
1970
1971 #[test]
1972 fn conflict_param_name_mismatch_across_lengths() {
1973 let mut router = Router::new();
1974 router.add(route(Method::Get, "/users/{id}/posts")).unwrap();
1975
1976 let result = router.add(route(Method::Get, "/users/{user_id}"));
1977 assert!(matches!(result, Err(RouteAddError::Conflict(_))));
1978 }
1979
1980 #[test]
1981 fn conflict_different_converter_same_position() {
1982 let mut router = Router::new();
1983 router.add(route(Method::Get, "/items/{id:int}")).unwrap();
1984
1985 let result = router.add(route(Method::Get, "/items/{id:uuid}"));
1987 assert!(matches!(result, Err(RouteAddError::Conflict(_))));
1988 }
1989
1990 #[test]
1991 fn no_conflict_different_methods() {
1992 let mut router = Router::new();
1993 router.add(route(Method::Get, "/users")).unwrap();
1994 router.add(route(Method::Post, "/users")).unwrap();
1995 router.add(route(Method::Put, "/users")).unwrap();
1996 router.add(route(Method::Delete, "/users")).unwrap();
1997 router.add(route(Method::Patch, "/users")).unwrap();
1998
1999 assert_eq!(router.routes().len(), 5);
2000 }
2001
2002 #[test]
2003 fn no_conflict_static_vs_param() {
2004 let mut router = Router::new();
2005 router.add(route(Method::Get, "/users/me")).unwrap();
2006 router.add(route(Method::Get, "/users/{id}")).unwrap();
2007
2008 assert_eq!(router.routes().len(), 2);
2010 }
2011
2012 #[test]
2013 fn no_conflict_different_path_lengths() {
2014 let mut router = Router::new();
2015 router.add(route(Method::Get, "/users")).unwrap();
2016 router.add(route(Method::Get, "/users/{id}")).unwrap();
2017 router.add(route(Method::Get, "/users/{id}/posts")).unwrap();
2018
2019 assert_eq!(router.routes().len(), 3);
2020 }
2021
2022 #[test]
2023 fn conflict_error_display() {
2024 let err = RouteConflictError {
2025 method: Method::Get,
2026 new_path: "/new".to_string(),
2027 existing_path: "/existing".to_string(),
2028 };
2029 let msg = format!("{}", err);
2030 assert!(msg.contains("GET"));
2031 assert!(msg.contains("/new"));
2032 assert!(msg.contains("/existing"));
2033 }
2034
2035 #[test]
2040 fn root_path() {
2041 let mut router = Router::new();
2042 router.add(route(Method::Get, "/")).unwrap();
2043
2044 let m = router.match_path("/", Method::Get);
2045 assert!(m.is_some());
2046 assert_eq!(m.unwrap().route.path, "/");
2047 }
2048
2049 #[test]
2050 fn trailing_slash_handling() {
2051 let mut router = Router::new();
2052 router.add(route(Method::Get, "/users")).unwrap();
2053
2054 let m = router.match_path("/users/", Method::Get);
2057 assert!(m.is_none() || m.is_some());
2059 }
2060
2061 #[test]
2062 fn multiple_consecutive_slashes() {
2063 let mut router = Router::new();
2064 router.add(route(Method::Get, "/users")).unwrap();
2065
2066 let m = router.match_path("//users", Method::Get);
2069 assert!(m.is_some());
2070 assert_eq!(m.unwrap().route.path, "/users");
2071 }
2072
2073 #[test]
2074 fn unicode_in_static_path() {
2075 let mut router = Router::new();
2076 router.add(route(Method::Get, "/用户")).unwrap();
2077 router.add(route(Method::Get, "/données")).unwrap();
2078
2079 let m = router.match_path("/用户", Method::Get);
2080 assert!(m.is_some());
2081 assert_eq!(m.unwrap().route.path, "/用户");
2082
2083 let m = router.match_path("/données", Method::Get);
2084 assert!(m.is_some());
2085 assert_eq!(m.unwrap().route.path, "/données");
2086 }
2087
2088 #[test]
2089 fn unicode_in_param_value() {
2090 let mut router = Router::new();
2091 router.add(route(Method::Get, "/users/{name}")).unwrap();
2092
2093 let m = router.match_path("/users/田中", Method::Get);
2094 assert!(m.is_some());
2095 let m = m.unwrap();
2096 assert_eq!(m.params[0], ("name", "田中"));
2097 }
2098
2099 #[test]
2100 fn special_characters_in_param_value() {
2101 let mut router = Router::new();
2102 router.add(route(Method::Get, "/files/{name}")).unwrap();
2103
2104 let m = router.match_path("/files/my-file_v2", Method::Get);
2106 assert!(m.is_some());
2107 assert_eq!(m.unwrap().params[0], ("name", "my-file_v2"));
2108
2109 let m = router.match_path("/files/document.pdf", Method::Get);
2111 assert!(m.is_some());
2112 assert_eq!(m.unwrap().params[0], ("name", "document.pdf"));
2113 }
2114
2115 #[test]
2116 fn empty_param_value() {
2117 let mut router = Router::new();
2118 router.add(route(Method::Get, "/users/{id}/posts")).unwrap();
2119
2120 let m = router.match_path("/users//posts", Method::Get);
2122 assert!(m.is_none());
2124 }
2125
2126 #[test]
2127 fn very_long_path() {
2128 let mut router = Router::new();
2129 let long_path = "/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z";
2130 router.add(route(Method::Get, long_path)).unwrap();
2131
2132 let m = router.match_path(long_path, Method::Get);
2133 assert!(m.is_some());
2134 assert_eq!(m.unwrap().route.path, long_path);
2135 }
2136
2137 #[test]
2138 fn many_routes_same_prefix() {
2139 let mut router = Router::new();
2140 for i in 0..100 {
2141 router
2142 .add(route(Method::Get, &format!("/api/v{}", i)))
2143 .unwrap();
2144 }
2145
2146 assert_eq!(router.routes().len(), 100);
2147
2148 for i in 0..100 {
2150 let path = format!("/api/v{}", i);
2151 let m = router.match_path(&path, Method::Get);
2152 assert!(m.is_some());
2153 assert_eq!(m.unwrap().route.path, path);
2154 }
2155 }
2156
2157 #[test]
2162 fn head_matches_get_route() {
2163 let mut router = Router::new();
2164 router.add(route(Method::Get, "/users")).unwrap();
2165
2166 let m = router.match_path("/users", Method::Head);
2168 assert!(m.is_some());
2169 assert_eq!(m.unwrap().route.method, Method::Get);
2170 }
2171
2172 #[test]
2173 fn head_with_explicit_head_route() {
2174 let mut router = Router::new();
2175 router.add(route(Method::Get, "/users")).unwrap();
2176 router.add(route(Method::Head, "/users")).unwrap();
2177
2178 let m = router.match_path("/users", Method::Head);
2180 assert!(m.is_some());
2181 assert_eq!(m.unwrap().route.method, Method::Head);
2182 }
2183
2184 #[test]
2185 fn head_does_not_match_non_get() {
2186 let mut router = Router::new();
2187 router.add(route(Method::Post, "/users")).unwrap();
2188
2189 let result = router.lookup("/users", Method::Head);
2191 match result {
2192 RouteLookup::MethodNotAllowed { allowed } => {
2193 assert!(!allowed.contains(Method::Head));
2194 assert!(allowed.contains(Method::Post));
2195 }
2196 _ => panic!("expected MethodNotAllowed"),
2197 }
2198 }
2199
2200 #[test]
2205 fn int_converter_edge_cases() {
2206 let mut router = Router::new();
2207 router.add(route(Method::Get, "/items/{id:int}")).unwrap();
2208
2209 let m = router.match_path("/items/0", Method::Get);
2211 assert!(m.is_some());
2212
2213 let m = router.match_path("/items/9223372036854775807", Method::Get);
2215 assert!(m.is_some());
2216
2217 let m = router.match_path("/items/-9223372036854775808", Method::Get);
2219 assert!(m.is_some());
2220
2221 let m = router.match_path("/items/007", Method::Get);
2223 assert!(m.is_some());
2224
2225 let m = router.match_path("/items/+123", Method::Get);
2227 assert!(m.is_some());
2229 }
2230
2231 #[test]
2232 fn float_converter_edge_cases() {
2233 let mut router = Router::new();
2234 router
2235 .add(route(Method::Get, "/values/{val:float}"))
2236 .unwrap();
2237
2238 let m = router.match_path("/values/1e10", Method::Get);
2240 assert!(m.is_some());
2241
2242 let m = router.match_path("/values/1e-10", Method::Get);
2244 assert!(m.is_some());
2245
2246 let m = router.match_path("/values/inf", Method::Get);
2248 assert!(m.is_some());
2249
2250 let m = router.match_path("/values/NaN", Method::Get);
2252 assert!(m.is_some());
2253 }
2254
2255 #[test]
2256 fn uuid_converter_case_sensitivity() {
2257 let mut router = Router::new();
2258 router
2259 .add(route(Method::Get, "/objects/{id:uuid}"))
2260 .unwrap();
2261
2262 let m = router.match_path("/objects/550e8400-e29b-41d4-a716-446655440000", Method::Get);
2264 assert!(m.is_some());
2265
2266 let m = router.match_path("/objects/550E8400-E29B-41D4-A716-446655440000", Method::Get);
2268 assert!(m.is_some());
2269
2270 let m = router.match_path("/objects/550e8400-E29B-41d4-A716-446655440000", Method::Get);
2272 assert!(m.is_some());
2273 }
2274
2275 #[test]
2276 fn uuid_converter_invalid_formats() {
2277 let mut router = Router::new();
2278 router
2279 .add(route(Method::Get, "/objects/{id:uuid}"))
2280 .unwrap();
2281
2282 assert!(
2284 router
2285 .match_path("/objects/550e8400-e29b-41d4-a716-44665544000", Method::Get)
2286 .is_none()
2287 );
2288 assert!(
2289 router
2290 .match_path(
2291 "/objects/550e8400-e29b-41d4-a716-4466554400000",
2292 Method::Get
2293 )
2294 .is_none()
2295 );
2296
2297 assert!(
2299 router
2300 .match_path("/objects/550e8400e29b41d4a716446655440000", Method::Get)
2301 .is_none()
2302 );
2303
2304 assert!(
2306 router
2307 .match_path("/objects/550g8400-e29b-41d4-a716-446655440000", Method::Get)
2308 .is_none()
2309 );
2310 }
2311
2312 #[test]
2313 fn unknown_converter_defaults_to_str() {
2314 let segments = parse_path("/items/{id:custom}");
2315 assert_eq!(segments.len(), 2);
2316 match &segments[1] {
2317 PathSegment::Param { name, converter } => {
2318 assert_eq!(*name, "id");
2319 assert_eq!(*converter, Converter::Str);
2320 }
2321 _ => panic!("Expected param segment"),
2322 }
2323 }
2324
2325 #[test]
2330 fn parse_empty_path() {
2331 let segments = parse_path("");
2332 assert!(segments.is_empty());
2333 }
2334
2335 #[test]
2336 fn parse_root_only() {
2337 let segments = parse_path("/");
2338 assert!(segments.is_empty());
2339 }
2340
2341 #[test]
2342 fn parse_leading_trailing_slashes() {
2343 let segments = parse_path("///users///");
2344 assert_eq!(segments.len(), 1);
2345 match &segments[0] {
2346 PathSegment::Static(s) => assert_eq!(*s, "users"),
2347 _ => panic!("Expected static segment"),
2348 }
2349 }
2350
2351 #[test]
2352 fn parse_param_with_colon_no_type() {
2353 let segments = parse_path("/items/{id:}");
2355 assert_eq!(segments.len(), 2);
2356 match &segments[1] {
2357 PathSegment::Param { name, converter } => {
2358 assert_eq!(*name, "id");
2359 assert_eq!(*converter, Converter::Str);
2361 }
2362 _ => panic!("Expected param segment"),
2363 }
2364 }
2365
2366 #[test]
2371 fn lookup_404_empty_router() {
2372 let router = Router::new();
2373 assert!(matches!(
2374 router.lookup("/anything", Method::Get),
2375 RouteLookup::NotFound
2376 ));
2377 }
2378
2379 #[test]
2380 fn lookup_404_no_matching_path() {
2381 let mut router = Router::new();
2382 router.add(route(Method::Get, "/users")).unwrap();
2383 router.add(route(Method::Get, "/items")).unwrap();
2384
2385 assert!(matches!(
2386 router.lookup("/other", Method::Get),
2387 RouteLookup::NotFound
2388 ));
2389 assert!(matches!(
2390 router.lookup("/user", Method::Get),
2391 RouteLookup::NotFound
2392 )); }
2394
2395 #[test]
2396 fn lookup_404_partial_path_match() {
2397 let mut router = Router::new();
2398 router.add(route(Method::Get, "/api/v1/users")).unwrap();
2399
2400 assert!(matches!(
2402 router.lookup("/api", Method::Get),
2403 RouteLookup::NotFound
2404 ));
2405 assert!(matches!(
2406 router.lookup("/api/v1", Method::Get),
2407 RouteLookup::NotFound
2408 ));
2409 }
2410
2411 #[test]
2412 fn lookup_404_extra_path_segments() {
2413 let mut router = Router::new();
2414 router.add(route(Method::Get, "/users")).unwrap();
2415
2416 assert!(matches!(
2418 router.lookup("/users/extra", Method::Get),
2419 RouteLookup::NotFound
2420 ));
2421 }
2422
2423 #[test]
2424 fn lookup_405_single_method() {
2425 let mut router = Router::new();
2426 router.add(route(Method::Get, "/users")).unwrap();
2427
2428 let result = router.lookup("/users", Method::Post);
2429 match result {
2430 RouteLookup::MethodNotAllowed { allowed } => {
2431 assert_eq!(allowed.methods(), &[Method::Get, Method::Head]);
2432 }
2433 _ => panic!("expected MethodNotAllowed"),
2434 }
2435 }
2436
2437 #[test]
2438 fn lookup_405_all_methods() {
2439 let mut router = Router::new();
2440 router.add(route(Method::Get, "/resource")).unwrap();
2441 router.add(route(Method::Post, "/resource")).unwrap();
2442 router.add(route(Method::Put, "/resource")).unwrap();
2443 router.add(route(Method::Delete, "/resource")).unwrap();
2444 router.add(route(Method::Patch, "/resource")).unwrap();
2445 router.add(route(Method::Options, "/resource")).unwrap();
2446
2447 let result = router.lookup("/resource", Method::Trace);
2448 match result {
2449 RouteLookup::MethodNotAllowed { allowed } => {
2450 let header = allowed.header_value();
2451 assert!(header.contains("GET"));
2452 assert!(header.contains("HEAD"));
2453 assert!(header.contains("POST"));
2454 assert!(header.contains("PUT"));
2455 assert!(header.contains("DELETE"));
2456 assert!(header.contains("PATCH"));
2457 assert!(header.contains("OPTIONS"));
2458 }
2459 _ => panic!("expected MethodNotAllowed"),
2460 }
2461 }
2462
2463 #[test]
2468 fn allowed_methods_deduplication() {
2469 let allowed = AllowedMethods::new(vec![Method::Get, Method::Get, Method::Post]);
2471 assert_eq!(allowed.methods().len(), 3); }
2473
2474 #[test]
2475 fn allowed_methods_sorting() {
2476 let allowed = AllowedMethods::new(vec![Method::Delete, Method::Get, Method::Post]);
2478 assert_eq!(allowed.methods()[0], Method::Get);
2479 assert_eq!(allowed.methods()[1], Method::Head); assert_eq!(allowed.methods()[2], Method::Post);
2481 assert_eq!(allowed.methods()[3], Method::Delete);
2482 }
2483
2484 #[test]
2485 fn allowed_methods_head_not_duplicated() {
2486 let allowed = AllowedMethods::new(vec![Method::Get, Method::Head]);
2488 let count = allowed
2489 .methods()
2490 .iter()
2491 .filter(|&&m| m == Method::Head)
2492 .count();
2493 assert_eq!(count, 1);
2494 }
2495
2496 #[test]
2497 fn allowed_methods_empty() {
2498 let allowed = AllowedMethods::new(vec![]);
2499 assert!(allowed.methods().is_empty());
2500 assert_eq!(allowed.header_value(), "");
2501 }
2502
2503 #[test]
2508 fn wildcard_asterisk_syntax_basic() {
2509 let mut router = Router::new();
2510 router.add(route(Method::Get, "/files/{*path}")).unwrap();
2511
2512 let m = router.match_path("/files/a.txt", Method::Get).unwrap();
2513 assert_eq!(m.params[0], ("path", "a.txt"));
2514 }
2515
2516 #[test]
2517 fn wildcard_asterisk_captures_multiple_segments() {
2518 let mut router = Router::new();
2519 router
2520 .add(route(Method::Get, "/files/{*filepath}"))
2521 .unwrap();
2522
2523 let m = router
2524 .match_path("/files/css/styles/main.css", Method::Get)
2525 .unwrap();
2526 assert_eq!(m.params[0], ("filepath", "css/styles/main.css"));
2527 }
2528
2529 #[test]
2530 fn wildcard_asterisk_with_prefix() {
2531 let mut router = Router::new();
2532 router.add(route(Method::Get, "/api/v1/{*rest}")).unwrap();
2533
2534 let m = router
2535 .match_path("/api/v1/users/123/posts", Method::Get)
2536 .unwrap();
2537 assert_eq!(m.params[0], ("rest", "users/123/posts"));
2538 }
2539
2540 #[test]
2541 fn wildcard_asterisk_empty_capture() {
2542 let mut router = Router::new();
2543 router.add(route(Method::Get, "/files/{*path}")).unwrap();
2544
2545 let m = router.match_path("/files/x", Method::Get).unwrap();
2547 assert_eq!(m.params[0], ("path", "x"));
2548 }
2549
2550 #[test]
2551 fn wildcard_asterisk_must_be_terminal() {
2552 let mut router = Router::new();
2553 let result = router.add(route(Method::Get, "/files/{*path}/edit"));
2554 assert!(matches!(result, Err(RouteAddError::InvalidPath(_))));
2555 }
2556
2557 #[test]
2558 fn wildcard_asterisk_syntax_equivalent_to_path_converter() {
2559 let segments_asterisk = parse_path("/files/{*filepath}");
2561 let segments_converter = parse_path("/files/{filepath:path}");
2562
2563 assert_eq!(segments_asterisk.len(), 2);
2564 assert_eq!(segments_converter.len(), 2);
2565
2566 match (&segments_asterisk[1], &segments_converter[1]) {
2567 (
2568 PathSegment::Param {
2569 name: n1,
2570 converter: c1,
2571 },
2572 PathSegment::Param {
2573 name: n2,
2574 converter: c2,
2575 },
2576 ) => {
2577 assert_eq!(*n1, "filepath");
2578 assert_eq!(*n2, "filepath");
2579 assert_eq!(*c1, Converter::Path);
2580 assert_eq!(*c2, Converter::Path);
2581 }
2582 _ => panic!("Expected param segments"),
2583 }
2584 }
2585
2586 #[test]
2587 fn wildcard_asterisk_spa_routing() {
2588 let mut router = Router::new();
2589 router.add(route(Method::Get, "/api/users")).unwrap();
2591 router.add(route(Method::Get, "/api/posts")).unwrap();
2592 router.add(route(Method::Get, "/{*route}")).unwrap();
2594
2595 let m = router.match_path("/api/users", Method::Get).unwrap();
2597 assert_eq!(m.route.path, "/api/users");
2598
2599 let m = router.match_path("/dashboard", Method::Get).unwrap();
2601 assert_eq!(m.params[0], ("route", "dashboard"));
2602
2603 let m = router
2604 .match_path("/users/123/profile", Method::Get)
2605 .unwrap();
2606 assert_eq!(m.params[0], ("route", "users/123/profile"));
2607 }
2608
2609 #[test]
2610 fn wildcard_asterisk_file_serving() {
2611 let mut router = Router::new();
2612 router
2613 .add(route(Method::Get, "/static/{*filepath}"))
2614 .unwrap();
2615
2616 let m = router
2617 .match_path("/static/js/app.bundle.js", Method::Get)
2618 .unwrap();
2619 assert_eq!(m.params[0], ("filepath", "js/app.bundle.js"));
2620
2621 let m = router
2622 .match_path("/static/images/logo.png", Method::Get)
2623 .unwrap();
2624 assert_eq!(m.params[0], ("filepath", "images/logo.png"));
2625 }
2626
2627 #[test]
2628 fn wildcard_asterisk_priority_lowest() {
2629 let mut router = Router::new();
2630 router.add(route(Method::Get, "/users/me")).unwrap();
2632 router.add(route(Method::Get, "/users/{id}")).unwrap();
2634 router.add(route(Method::Get, "/{*path}")).unwrap();
2636
2637 let m = router.match_path("/users/me", Method::Get).unwrap();
2639 assert_eq!(m.route.path, "/users/me");
2640 assert!(m.params.is_empty());
2641
2642 let m = router.match_path("/users/123", Method::Get).unwrap();
2644 assert_eq!(m.route.path, "/users/{id}");
2645 assert_eq!(m.params[0], ("id", "123"));
2646
2647 let m = router.match_path("/other/deep/path", Method::Get).unwrap();
2649 assert_eq!(m.route.path, "/{*path}");
2650 assert_eq!(m.params[0], ("path", "other/deep/path"));
2651 }
2652
2653 #[test]
2654 fn parse_wildcard_asterisk_syntax() {
2655 let segments = parse_path("/files/{*path}");
2656 assert_eq!(segments.len(), 2);
2657
2658 match &segments[0] {
2659 PathSegment::Static(s) => assert_eq!(*s, "files"),
2660 _ => panic!("Expected static segment"),
2661 }
2662
2663 match &segments[1] {
2664 PathSegment::Param { name, converter } => {
2665 assert_eq!(*name, "path");
2666 assert_eq!(*converter, Converter::Path);
2667 }
2668 _ => panic!("Expected param segment"),
2669 }
2670 }
2671
2672 #[test]
2673 fn wildcard_asterisk_conflict_with_path_converter() {
2674 let mut router = Router::new();
2675 router.add(route(Method::Get, "/files/{*path}")).unwrap();
2676
2677 let result = router.add(route(Method::Get, "/files/{filepath:path}"));
2679 assert!(matches!(result, Err(RouteAddError::Conflict(_))));
2680 }
2681
2682 #[test]
2683 fn wildcard_asterisk_different_methods_no_conflict() {
2684 let mut router = Router::new();
2685 router.add(route(Method::Get, "/files/{*path}")).unwrap();
2686 router.add(route(Method::Post, "/files/{*path}")).unwrap();
2687 router.add(route(Method::Delete, "/files/{*path}")).unwrap();
2688
2689 assert_eq!(router.routes().len(), 3);
2690
2691 let m = router.match_path("/files/a/b/c", Method::Get).unwrap();
2693 assert_eq!(m.route.method, Method::Get);
2694
2695 let m = router.match_path("/files/a/b/c", Method::Post).unwrap();
2696 assert_eq!(m.route.method, Method::Post);
2697
2698 let m = router.match_path("/files/a/b/c", Method::Delete).unwrap();
2699 assert_eq!(m.route.method, Method::Delete);
2700 }
2701
2702 #[test]
2715 fn priority_static_before_param() {
2716 let mut router = Router::new();
2718 router.add(route(Method::Get, "/users/{id}")).unwrap();
2719 router.add(route(Method::Get, "/users/me")).unwrap();
2720
2721 let m = router.match_path("/users/me", Method::Get).unwrap();
2723 assert_eq!(m.route.path, "/users/me");
2724 assert!(m.params.is_empty());
2725
2726 let m = router.match_path("/users/123", Method::Get).unwrap();
2728 assert_eq!(m.route.path, "/users/{id}");
2729 assert_eq!(m.params[0], ("id", "123"));
2730 }
2731
2732 #[test]
2733 fn priority_named_param_vs_wildcard_conflict() {
2734 let mut router = Router::new();
2737 router.add(route(Method::Get, "/files/{name}")).unwrap();
2738
2739 let result = router.add(route(Method::Get, "/files/{*path}"));
2741 assert!(
2742 matches!(result, Err(RouteAddError::Conflict(_))),
2743 "Named param and wildcard at same position should conflict"
2744 );
2745 }
2746
2747 #[test]
2748 fn priority_different_prefixes_no_conflict() {
2749 let mut router = Router::new();
2751 router.add(route(Method::Get, "/files/{name}")).unwrap();
2752 router.add(route(Method::Get, "/static/{*path}")).unwrap();
2753
2754 let m = router.match_path("/files/foo.txt", Method::Get).unwrap();
2756 assert_eq!(m.route.path, "/files/{name}");
2757
2758 let m = router
2760 .match_path("/static/css/main.css", Method::Get)
2761 .unwrap();
2762 assert_eq!(m.route.path, "/static/{*path}");
2763 }
2764
2765 #[test]
2766 fn priority_nested_param_before_shallow_wildcard() {
2767 let mut router = Router::new();
2769 router.add(route(Method::Get, "/{*path}")).unwrap();
2770 router.add(route(Method::Get, "/api/users")).unwrap();
2771
2772 let m = router.match_path("/api/users", Method::Get).unwrap();
2774 assert_eq!(m.route.path, "/api/users");
2775
2776 let m = router.match_path("/other/path", Method::Get).unwrap();
2778 assert_eq!(m.route.path, "/{*path}");
2779 }
2780
2781 #[test]
2782 fn priority_multiple_static_depths() {
2783 let mut router = Router::new();
2785 router.add(route(Method::Get, "/api/{*rest}")).unwrap();
2786 router.add(route(Method::Get, "/api/v1/users")).unwrap();
2787 router
2788 .add(route(Method::Get, "/api/v1/{resource}"))
2789 .unwrap();
2790
2791 let m = router.match_path("/api/v1/users", Method::Get).unwrap();
2793 assert_eq!(m.route.path, "/api/v1/users");
2794
2795 let m = router.match_path("/api/v1/items", Method::Get).unwrap();
2797 assert_eq!(m.route.path, "/api/v1/{resource}");
2798
2799 let m = router
2801 .match_path("/api/v2/anything/deep", Method::Get)
2802 .unwrap();
2803 assert_eq!(m.route.path, "/api/{*rest}");
2804 }
2805
2806 #[test]
2807 fn priority_complex_route_set() {
2808 let mut router = Router::new();
2810
2811 router.add(route(Method::Get, "/users/me")).unwrap();
2813 router
2814 .add(route(Method::Get, "/users/{user_id}/profile"))
2815 .unwrap();
2816 router.add(route(Method::Get, "/users/{user_id}")).unwrap();
2817 router.add(route(Method::Get, "/{*path}")).unwrap();
2818
2819 let m = router.match_path("/users/me", Method::Get).unwrap();
2821 assert_eq!(m.route.path, "/users/me");
2822
2823 let m = router.match_path("/users/123", Method::Get).unwrap();
2825 assert_eq!(m.route.path, "/users/{user_id}");
2826 assert_eq!(m.params[0], ("user_id", "123"));
2827
2828 let m = router
2830 .match_path("/users/123/profile", Method::Get)
2831 .unwrap();
2832 assert_eq!(m.route.path, "/users/{user_id}/profile");
2833 assert_eq!(m.params[0], ("user_id", "123"));
2834
2835 let m = router.match_path("/anything/else", Method::Get).unwrap();
2837 assert_eq!(m.route.path, "/{*path}");
2838 assert_eq!(m.params[0], ("path", "anything/else"));
2839 }
2840
2841 #[test]
2846 fn converter_convert_str() {
2847 let result = Converter::Str.convert("hello", "param");
2848 assert!(result.is_ok());
2849 assert_eq!(result.unwrap(), ParamValue::Str("hello".to_string()));
2850 }
2851
2852 #[test]
2853 fn converter_convert_int_valid() {
2854 let result = Converter::Int.convert("42", "id");
2855 assert!(result.is_ok());
2856 assert_eq!(result.unwrap(), ParamValue::Int(42));
2857 }
2858
2859 #[test]
2860 fn converter_convert_int_negative() {
2861 let result = Converter::Int.convert("-123", "id");
2862 assert!(result.is_ok());
2863 assert_eq!(result.unwrap(), ParamValue::Int(-123));
2864 }
2865
2866 #[test]
2867 fn converter_convert_int_invalid() {
2868 let result = Converter::Int.convert("abc", "id");
2869 assert!(result.is_err());
2870 match result.unwrap_err() {
2871 ConversionError::InvalidInt { value, param } => {
2872 assert_eq!(value, "abc");
2873 assert_eq!(param, "id");
2874 }
2875 _ => panic!("Expected InvalidInt error"),
2876 }
2877 }
2878
2879 #[test]
2880 #[allow(clippy::approx_constant)]
2881 fn converter_convert_float_valid() {
2882 let result = Converter::Float.convert("3.14", "val");
2883 assert!(result.is_ok());
2884 assert_eq!(result.unwrap(), ParamValue::Float(3.14));
2885 }
2886
2887 #[test]
2888 fn converter_convert_float_integer() {
2889 let result = Converter::Float.convert("42", "val");
2890 assert!(result.is_ok());
2891 assert_eq!(result.unwrap(), ParamValue::Float(42.0));
2892 }
2893
2894 #[test]
2895 fn converter_convert_float_scientific() {
2896 let result = Converter::Float.convert("1e10", "val");
2897 assert!(result.is_ok());
2898 assert_eq!(result.unwrap(), ParamValue::Float(1e10));
2899 }
2900
2901 #[test]
2902 fn converter_convert_float_invalid() {
2903 let result = Converter::Float.convert("not-a-float", "val");
2904 assert!(result.is_err());
2905 match result.unwrap_err() {
2906 ConversionError::InvalidFloat { value, param } => {
2907 assert_eq!(value, "not-a-float");
2908 assert_eq!(param, "val");
2909 }
2910 _ => panic!("Expected InvalidFloat error"),
2911 }
2912 }
2913
2914 #[test]
2915 fn converter_convert_uuid_valid() {
2916 let result = Converter::Uuid.convert("550e8400-e29b-41d4-a716-446655440000", "id");
2917 assert!(result.is_ok());
2918 assert_eq!(
2919 result.unwrap(),
2920 ParamValue::Uuid("550e8400-e29b-41d4-a716-446655440000".to_string())
2921 );
2922 }
2923
2924 #[test]
2925 fn converter_convert_uuid_invalid() {
2926 let result = Converter::Uuid.convert("not-a-uuid", "id");
2927 assert!(result.is_err());
2928 match result.unwrap_err() {
2929 ConversionError::InvalidUuid { value, param } => {
2930 assert_eq!(value, "not-a-uuid");
2931 assert_eq!(param, "id");
2932 }
2933 _ => panic!("Expected InvalidUuid error"),
2934 }
2935 }
2936
2937 #[test]
2938 fn converter_convert_path() {
2939 let result = Converter::Path.convert("a/b/c.txt", "filepath");
2940 assert!(result.is_ok());
2941 assert_eq!(result.unwrap(), ParamValue::Path("a/b/c.txt".to_string()));
2942 }
2943
2944 #[test]
2945 fn param_value_accessors() {
2946 let val = ParamValue::Str("hello".to_string());
2948 assert_eq!(val.as_str(), "hello");
2949 assert_eq!(val.as_int(), None);
2950 assert_eq!(val.as_float(), None);
2951 assert_eq!(val.into_string(), Some("hello".to_string()));
2952
2953 let val = ParamValue::Int(42);
2955 assert_eq!(val.as_int(), Some(42));
2956 assert_eq!(val.as_float(), None);
2957 assert_eq!(val.into_string(), None);
2958
2959 #[allow(clippy::approx_constant)]
2961 let val = ParamValue::Float(3.14);
2962 #[allow(clippy::approx_constant)]
2963 let expected_pi = Some(3.14);
2964 assert_eq!(val.as_float(), expected_pi);
2965 assert_eq!(val.as_int(), None);
2966 assert_eq!(val.into_string(), None);
2967
2968 let val = ParamValue::Uuid("550e8400-e29b-41d4-a716-446655440000".to_string());
2970 assert_eq!(val.as_str(), "550e8400-e29b-41d4-a716-446655440000");
2971 assert_eq!(
2972 val.into_string(),
2973 Some("550e8400-e29b-41d4-a716-446655440000".to_string())
2974 );
2975
2976 let val = ParamValue::Path("a/b/c".to_string());
2978 assert_eq!(val.as_str(), "a/b/c");
2979 assert_eq!(val.into_string(), Some("a/b/c".to_string()));
2980 }
2981
2982 #[test]
2983 fn conversion_error_display() {
2984 let err = ConversionError::InvalidInt {
2985 value: "abc".to_string(),
2986 param: "id".to_string(),
2987 };
2988 let msg = format!("{}", err);
2989 assert!(msg.contains("id"));
2990 assert!(msg.contains("abc"));
2991 assert!(msg.contains("integer"));
2992
2993 let err = ConversionError::InvalidFloat {
2994 value: "xyz".to_string(),
2995 param: "val".to_string(),
2996 };
2997 let msg = format!("{}", err);
2998 assert!(msg.contains("val"));
2999 assert!(msg.contains("xyz"));
3000 assert!(msg.contains("float"));
3001
3002 let err = ConversionError::InvalidUuid {
3003 value: "bad".to_string(),
3004 param: "uuid".to_string(),
3005 };
3006 let msg = format!("{}", err);
3007 assert!(msg.contains("uuid"));
3008 assert!(msg.contains("bad"));
3009 assert!(msg.contains("UUID"));
3010 }
3011
3012 #[test]
3013 fn converter_type_name() {
3014 assert_eq!(Converter::Str.type_name(), "string");
3015 assert_eq!(Converter::Int.type_name(), "integer");
3016 assert_eq!(Converter::Float.type_name(), "float");
3017 assert_eq!(Converter::Uuid.type_name(), "UUID");
3018 assert_eq!(Converter::Path.type_name(), "path");
3019 }
3020
3021 #[test]
3022 fn route_match_typed_getters() {
3023 let mut router = Router::new();
3024 router
3025 .add(route(Method::Get, "/items/{id:int}/price/{val:float}"))
3026 .unwrap();
3027
3028 let m = router
3029 .match_path("/items/42/price/99.99", Method::Get)
3030 .unwrap();
3031
3032 assert_eq!(m.get_param("id"), Some("42"));
3034 assert_eq!(m.get_param("val"), Some("99.99"));
3035
3036 assert_eq!(m.get_param_int("id"), Some(Ok(42)));
3038 assert_eq!(m.get_param_float("val"), Some(Ok(99.99)));
3039
3040 assert!(m.get_param_int("missing").is_none());
3042
3043 let result = m.get_param_int("val");
3045 assert!(result.is_some());
3048 assert!(result.unwrap().is_err());
3049 }
3050
3051 #[test]
3052 fn route_match_param_count() {
3053 let mut router = Router::new();
3054 router
3055 .add(route(Method::Get, "/users/{user_id}/posts/{post_id}"))
3056 .unwrap();
3057
3058 let m = router.match_path("/users/1/posts/2", Method::Get).unwrap();
3059
3060 assert_eq!(m.param_count(), 2);
3061 assert!(!m.is_empty());
3062
3063 let mut router2 = Router::new();
3065 router2.add(route(Method::Get, "/static")).unwrap();
3066 let m2 = router2.match_path("/static", Method::Get).unwrap();
3067 assert_eq!(m2.param_count(), 0);
3068 assert!(m2.is_empty());
3069 }
3070
3071 #[test]
3072 fn route_match_iter() {
3073 let mut router = Router::new();
3074 router
3075 .add(route(Method::Get, "/a/{x}/b/{y}/c/{z}"))
3076 .unwrap();
3077
3078 let m = router.match_path("/a/1/b/2/c/3", Method::Get).unwrap();
3079
3080 let params: Vec<_> = m.iter().collect();
3081 assert_eq!(params.len(), 3);
3082 assert_eq!(params[0], ("x", "1"));
3083 assert_eq!(params[1], ("y", "2"));
3084 assert_eq!(params[2], ("z", "3"));
3085 }
3086
3087 #[test]
3088 fn route_match_is_param_uuid() {
3089 let mut router = Router::new();
3090 router
3091 .add(route(Method::Get, "/objects/{id:uuid}"))
3092 .unwrap();
3093
3094 let m = router
3095 .match_path("/objects/550e8400-e29b-41d4-a716-446655440000", Method::Get)
3096 .unwrap();
3097
3098 assert_eq!(m.is_param_uuid("id"), Some(true));
3099 assert_eq!(m.is_param_uuid("missing"), None);
3100 }
3101
3102 #[test]
3103 fn route_match_integer_variants() {
3104 let mut router = Router::new();
3105 router.add(route(Method::Get, "/items/{id}")).unwrap();
3106
3107 let m = router.match_path("/items/12345", Method::Get).unwrap();
3108
3109 assert_eq!(m.get_param_int("id"), Some(Ok(12345i64)));
3111 assert_eq!(m.get_param_i32("id"), Some(Ok(12345i32)));
3112 assert_eq!(m.get_param_u64("id"), Some(Ok(12345u64)));
3113 assert_eq!(m.get_param_u32("id"), Some(Ok(12345u32)));
3114
3115 assert_eq!(m.get_param_float("id"), Some(Ok(12345.0f64)));
3117 assert_eq!(m.get_param_f32("id"), Some(Ok(12345.0f32)));
3118 }
3119
3120 #[test]
3125 fn mount_basic() {
3126 let mut child = Router::new();
3127 child.add(route(Method::Get, "/users")).unwrap();
3128 child.add(route(Method::Get, "/items")).unwrap();
3129
3130 let parent = Router::new().mount("/api/v1", child).unwrap();
3131
3132 let m = parent.match_path("/api/v1/users", Method::Get).unwrap();
3134 assert_eq!(m.route.path, "/api/v1/users");
3135
3136 let m = parent.match_path("/api/v1/items", Method::Get).unwrap();
3137 assert_eq!(m.route.path, "/api/v1/items");
3138 }
3139
3140 #[test]
3141 fn mount_with_params() {
3142 let mut child = Router::new();
3143 child.add(route(Method::Get, "/users/{id}")).unwrap();
3144 child
3145 .add(route(Method::Get, "/users/{id}/posts/{post_id}"))
3146 .unwrap();
3147
3148 let parent = Router::new().mount("/api", child).unwrap();
3149
3150 let m = parent.match_path("/api/users/42", Method::Get).unwrap();
3152 assert_eq!(m.route.path, "/api/users/{id}");
3153 assert_eq!(m.params[0], ("id", "42"));
3154
3155 let m = parent
3156 .match_path("/api/users/1/posts/99", Method::Get)
3157 .unwrap();
3158 assert_eq!(m.params.len(), 2);
3159 assert_eq!(m.params[0], ("id", "1"));
3160 assert_eq!(m.params[1], ("post_id", "99"));
3161 }
3162
3163 #[test]
3164 fn mount_preserves_methods() {
3165 let mut child = Router::new();
3166 child.add(route(Method::Get, "/resource")).unwrap();
3167 child.add(route(Method::Post, "/resource")).unwrap();
3168 child.add(route(Method::Delete, "/resource")).unwrap();
3169
3170 let parent = Router::new().mount("/api", child).unwrap();
3171
3172 let m = parent.match_path("/api/resource", Method::Get).unwrap();
3174 assert_eq!(m.route.method, Method::Get);
3175
3176 let m = parent.match_path("/api/resource", Method::Post).unwrap();
3177 assert_eq!(m.route.method, Method::Post);
3178
3179 let m = parent.match_path("/api/resource", Method::Delete).unwrap();
3180 assert_eq!(m.route.method, Method::Delete);
3181 }
3182
3183 #[test]
3184 fn mount_root_route() {
3185 let mut child = Router::new();
3186 child.add(route(Method::Get, "/")).unwrap();
3187
3188 let parent = Router::new().mount("/api", child).unwrap();
3189
3190 let m = parent.match_path("/api", Method::Get).unwrap();
3192 assert_eq!(m.route.path, "/api");
3193 }
3194
3195 #[test]
3196 fn mount_trailing_slash_prefix() {
3197 let mut child = Router::new();
3198 child.add(route(Method::Get, "/users")).unwrap();
3199
3200 let parent = Router::new().mount("/api/", child).unwrap();
3202
3203 let m = parent.match_path("/api/users", Method::Get).unwrap();
3204 assert_eq!(m.route.path, "/api/users");
3205 }
3206
3207 #[test]
3208 fn mount_empty_prefix() {
3209 let mut child = Router::new();
3210 child.add(route(Method::Get, "/users")).unwrap();
3211
3212 let parent = Router::new().mount("", child).unwrap();
3213
3214 let m = parent.match_path("/users", Method::Get).unwrap();
3215 assert_eq!(m.route.path, "/users");
3216 }
3217
3218 #[test]
3219 fn mount_nested() {
3220 let mut inner = Router::new();
3222 inner.add(route(Method::Get, "/items")).unwrap();
3223
3224 let middle = Router::new().mount("/v1", inner).unwrap();
3226
3227 let outer = Router::new().mount("/api", middle).unwrap();
3229
3230 let m = outer.match_path("/api/v1/items", Method::Get).unwrap();
3232 assert_eq!(m.route.path, "/api/v1/items");
3233 }
3234
3235 #[test]
3236 fn mount_conflict_detection() {
3237 let mut child1 = Router::new();
3238 child1.add(route(Method::Get, "/users")).unwrap();
3239
3240 let mut child2 = Router::new();
3241 child2.add(route(Method::Get, "/users")).unwrap();
3242
3243 let parent = Router::new().mount("/api", child1).unwrap();
3244
3245 let result = parent.mount("/api", child2);
3247 assert!(matches!(result, Err(RouteAddError::Conflict(_))));
3248 }
3249
3250 #[test]
3251 fn mount_no_conflict_different_prefixes() {
3252 let mut child1 = Router::new();
3253 child1.add(route(Method::Get, "/users")).unwrap();
3254
3255 let mut child2 = Router::new();
3256 child2.add(route(Method::Get, "/users")).unwrap();
3257
3258 let parent = Router::new()
3259 .mount("/api/v1", child1)
3260 .unwrap()
3261 .mount("/api/v2", child2)
3262 .unwrap();
3263
3264 let m = parent.match_path("/api/v1/users", Method::Get).unwrap();
3266 assert_eq!(m.route.path, "/api/v1/users");
3267
3268 let m = parent.match_path("/api/v2/users", Method::Get).unwrap();
3269 assert_eq!(m.route.path, "/api/v2/users");
3270 }
3271
3272 #[test]
3273 #[should_panic(expected = "route conflict when nesting router")]
3274 fn nest_panics_on_conflict() {
3275 let mut child1 = Router::new();
3276 child1.add(route(Method::Get, "/users")).unwrap();
3277
3278 let mut child2 = Router::new();
3279 child2.add(route(Method::Get, "/users")).unwrap();
3280
3281 let parent = Router::new().nest("/api", child1);
3282
3283 let _ = parent.nest("/api", child2);
3285 }
3286
3287 #[test]
3288 fn mount_with_wildcard() {
3289 let mut child = Router::new();
3290 child.add(route(Method::Get, "/files/{*path}")).unwrap();
3291
3292 let parent = Router::new().mount("/static", child).unwrap();
3293
3294 let m = parent
3296 .match_path("/static/files/css/style.css", Method::Get)
3297 .unwrap();
3298 assert_eq!(m.route.path, "/static/files/{*path}");
3299 assert_eq!(m.params[0], ("path", "css/style.css"));
3300 }
3301
3302 #[test]
3303 fn mount_parent_and_child_routes() {
3304 let mut parent = Router::new();
3305 parent.add(route(Method::Get, "/health")).unwrap();
3306
3307 let mut child = Router::new();
3308 child.add(route(Method::Get, "/users")).unwrap();
3309
3310 let app = parent.mount("/api", child).unwrap();
3311
3312 let m = app.match_path("/health", Method::Get).unwrap();
3314 assert_eq!(m.route.path, "/health");
3315
3316 let m = app.match_path("/api/users", Method::Get).unwrap();
3317 assert_eq!(m.route.path, "/api/users");
3318 }
3319
3320 #[test]
3337 fn percent_encoded_space_in_static_path() {
3338 let mut router = Router::new();
3339 router.add(route(Method::Get, "/hello%20world")).unwrap();
3340
3341 let m = router.match_path("/hello%20world", Method::Get);
3343 assert!(m.is_some());
3344 assert_eq!(m.unwrap().route.path, "/hello%20world");
3345
3346 let m = router.match_path("/hello world", Method::Get);
3348 assert!(m.is_none());
3349 }
3350
3351 #[test]
3352 fn percent_encoded_slash_in_param() {
3353 let mut router = Router::new();
3354 router.add(route(Method::Get, "/files/{name}")).unwrap();
3355
3356 let m = router.match_path("/files/a%2Fb.txt", Method::Get);
3358 assert!(m.is_some());
3359 assert_eq!(m.unwrap().params[0], ("name", "a%2Fb.txt"));
3360 }
3361
3362 #[test]
3363 fn percent_encoded_special_chars_in_param() {
3364 let mut router = Router::new();
3365 router.add(route(Method::Get, "/search/{query}")).unwrap();
3366
3367 let test_cases = vec![
3369 ("/search/hello%20world", ("query", "hello%20world")),
3370 ("/search/foo%26bar", ("query", "foo%26bar")), ("/search/a%3Db", ("query", "a%3Db")), ("/search/%23hash", ("query", "%23hash")), ("/search/100%25", ("query", "100%25")), ];
3375
3376 for (path, expected) in test_cases {
3377 let m = router.match_path(path, Method::Get);
3378 assert!(m.is_some(), "Failed to match: {}", path);
3379 assert_eq!(m.unwrap().params[0], expected);
3380 }
3381 }
3382
3383 #[test]
3384 fn percent_encoded_unicode_in_param() {
3385 let mut router = Router::new();
3386 router.add(route(Method::Get, "/users/{name}")).unwrap();
3387
3388 let m = router.match_path("/users/%E6%97%A5%E6%9C%AC", Method::Get);
3390 assert!(m.is_some());
3391 assert_eq!(m.unwrap().params[0], ("name", "%E6%97%A5%E6%9C%AC"));
3392 }
3393
3394 #[test]
3395 fn percent_encoded_in_wildcard() {
3396 let mut router = Router::new();
3397 router.add(route(Method::Get, "/files/{*path}")).unwrap();
3398
3399 let m = router.match_path("/files/dir%20name/file%20name.txt", Method::Get);
3401 assert!(m.is_some());
3402 assert_eq!(m.unwrap().params[0], ("path", "dir%20name/file%20name.txt"));
3403 }
3404
3405 #[test]
3406 fn double_percent_encoding() {
3407 let mut router = Router::new();
3408 router.add(route(Method::Get, "/data/{value}")).unwrap();
3409
3410 let m = router.match_path("/data/%2520", Method::Get);
3412 assert!(m.is_some());
3413 assert_eq!(m.unwrap().params[0], ("value", "%2520"));
3414 }
3415
3416 #[test]
3421 fn trailing_slash_strict_mode_static() {
3422 let mut router = Router::new();
3423 router.add(route(Method::Get, "/users")).unwrap();
3424 router.add(route(Method::Get, "/items/")).unwrap();
3425
3426 let m = router.match_path("/users", Method::Get);
3428 assert!(m.is_some());
3429 assert_eq!(m.unwrap().route.path, "/users");
3430
3431 let m = router.match_path("/users/", Method::Get);
3435 if let Some(m) = m {
3436 assert!(m.route.path == "/users" || m.route.path == "/users/");
3438 }
3439
3440 let m = router.match_path("/items/", Method::Get);
3442 assert!(m.is_some());
3443 }
3444
3445 #[test]
3446 fn trailing_slash_on_param_routes() {
3447 let mut router = Router::new();
3448 router.add(route(Method::Get, "/users/{id}")).unwrap();
3449
3450 let m = router.match_path("/users/123", Method::Get);
3452 assert!(m.is_some());
3453 assert_eq!(m.unwrap().params[0], ("id", "123"));
3454
3455 let m = router.match_path("/users/123/", Method::Get);
3457 if let Some(m) = m {
3459 assert_eq!(m.params[0].0, "id");
3460 }
3461 }
3462
3463 #[test]
3464 fn trailing_slash_on_nested_routes() {
3465 let mut router = Router::new();
3466 router.add(route(Method::Get, "/api/v1/users")).unwrap();
3467
3468 let result = router.add(route(Method::Get, "/api/v1/users/"));
3472 assert!(
3473 matches!(result, Err(RouteAddError::Conflict(_))),
3474 "Routes with and without trailing slash should conflict"
3475 );
3476
3477 assert_eq!(router.routes().len(), 1);
3479 }
3480
3481 #[test]
3482 fn multiple_trailing_slashes() {
3483 let mut router = Router::new();
3484 router.add(route(Method::Get, "/data")).unwrap();
3485
3486 let m = router.match_path("/data//", Method::Get);
3488 assert!(m.is_some()); let m = router.match_path("/data///", Method::Get);
3491 assert!(m.is_some()); }
3493
3494 #[test]
3499 fn empty_segment_normalization() {
3500 let mut router = Router::new();
3501 router.add(route(Method::Get, "/a/b/c")).unwrap();
3502
3503 let paths = vec!["/a//b/c", "/a/b//c", "//a/b/c", "/a/b/c//", "//a//b//c//"];
3505
3506 for path in paths {
3507 let m = router.match_path(path, Method::Get);
3508 assert!(m.is_some(), "Failed to match normalized path: {}", path);
3509 assert_eq!(m.unwrap().route.path, "/a/b/c");
3510 }
3511 }
3512
3513 #[test]
3514 fn empty_segment_in_middle_of_params() {
3515 let mut router = Router::new();
3516 router.add(route(Method::Get, "/a/{x}/b/{y}")).unwrap();
3517
3518 let m = router.match_path("/a//1/b/2", Method::Get);
3520 if let Some(m) = m {
3524 assert!(!m.params.is_empty());
3525 }
3526 }
3527
3528 #[test]
3529 fn only_slashes_path() {
3530 let mut router = Router::new();
3531 router.add(route(Method::Get, "/")).unwrap();
3532
3533 let paths = vec!["/", "//", "///", "////"];
3535 for path in paths {
3536 let m = router.match_path(path, Method::Get);
3537 assert!(m.is_some(), "Failed to match root with: {}", path);
3538 }
3539 }
3540
3541 #[test]
3542 fn empty_path_handling() {
3543 let mut router = Router::new();
3544 router.add(route(Method::Get, "/")).unwrap();
3545
3546 let m = router.match_path("", Method::Get);
3548 let _matched = m.is_some();
3551 }
3552
3553 #[test]
3558 fn deep_nesting_50_levels() {
3559 let mut router = Router::new();
3560
3561 let path = format!(
3563 "/{}",
3564 (0..50)
3565 .map(|i| format!("l{}", i))
3566 .collect::<Vec<_>>()
3567 .join("/")
3568 );
3569 router.add(route(Method::Get, &path)).unwrap();
3570
3571 let m = router.match_path(&path, Method::Get);
3573 assert!(m.is_some());
3574 assert_eq!(m.unwrap().route.path, path);
3575 }
3576
3577 #[test]
3578 fn deep_nesting_100_levels() {
3579 let mut router = Router::new();
3580
3581 let path = format!(
3583 "/{}",
3584 (0..100)
3585 .map(|i| format!("d{}", i))
3586 .collect::<Vec<_>>()
3587 .join("/")
3588 );
3589 router.add(route(Method::Get, &path)).unwrap();
3590
3591 let m = router.match_path(&path, Method::Get);
3592 assert!(m.is_some());
3593 assert_eq!(m.unwrap().route.path, path);
3594 }
3595
3596 #[test]
3597 fn deep_nesting_with_params_at_various_depths() {
3598 let mut router = Router::new();
3599
3600 let mut segments = vec![];
3602 for i in 0..20 {
3603 if i == 5 || i == 10 || i == 15 {
3604 segments.push(format!("{{p{}}}", i));
3605 } else {
3606 segments.push(format!("s{}", i));
3607 }
3608 }
3609 let path = format!("/{}", segments.join("/"));
3610 router.add(route(Method::Get, &path)).unwrap();
3611
3612 let mut request_segments = vec![];
3614 for i in 0..20 {
3615 if i == 5 || i == 10 || i == 15 {
3616 request_segments.push(format!("val{}", i));
3617 } else {
3618 request_segments.push(format!("s{}", i));
3619 }
3620 }
3621 let request_path = format!("/{}", request_segments.join("/"));
3622
3623 let m = router.match_path(&request_path, Method::Get);
3624 assert!(m.is_some());
3625 let m = m.unwrap();
3626 assert_eq!(m.params.len(), 3);
3627 assert_eq!(m.params[0], ("p5", "val5"));
3628 assert_eq!(m.params[1], ("p10", "val10"));
3629 assert_eq!(m.params[2], ("p15", "val15"));
3630 }
3631
3632 #[test]
3633 fn deep_nesting_with_wildcard_at_end() {
3634 let mut router = Router::new();
3635
3636 let segments: Vec<_> = (0..30).map(|i| format!("x{}", i)).collect();
3638 let prefix = format!("/{}", segments.join("/"));
3639 let path = format!("{}/{{*rest}}", prefix);
3640 router.add(route(Method::Get, &path)).unwrap();
3641
3642 let request_path = format!("{}/a/b/c/d/e", prefix);
3644 let m = router.match_path(&request_path, Method::Get);
3645 assert!(m.is_some());
3646 assert_eq!(m.unwrap().params[0], ("rest", "a/b/c/d/e"));
3647 }
3648
3649 #[test]
3654 fn many_siblings_500_routes() {
3655 let mut router = Router::new();
3656
3657 for i in 0..500 {
3659 router
3660 .add(route(Method::Get, &format!("/api/endpoint{}", i)))
3661 .unwrap();
3662 }
3663
3664 assert_eq!(router.routes().len(), 500);
3665
3666 for i in [0, 50, 100, 250, 499] {
3668 let path = format!("/api/endpoint{}", i);
3669 let m = router.match_path(&path, Method::Get);
3670 assert!(m.is_some(), "Failed to match: {}", path);
3671 assert_eq!(m.unwrap().route.path, path);
3672 }
3673 }
3674
3675 #[test]
3676 fn many_siblings_with_shared_prefix() {
3677 let mut router = Router::new();
3678
3679 for i in 0..200 {
3681 router
3682 .add(route(Method::Get, &format!("/users/user{:04}", i)))
3683 .unwrap();
3684 }
3685
3686 assert_eq!(router.routes().len(), 200);
3687
3688 for i in [0, 50, 100, 150, 199] {
3690 let path = format!("/users/user{:04}", i);
3691 let m = router.match_path(&path, Method::Get);
3692 assert!(m.is_some());
3693 assert_eq!(m.unwrap().route.path, path);
3694 }
3695 }
3696
3697 #[test]
3698 fn many_siblings_mixed_static_and_param() {
3699 let mut router = Router::new();
3700
3701 for i in 0..100 {
3703 router
3704 .add(route(Method::Get, &format!("/items/item{}", i)))
3705 .unwrap();
3706 }
3707
3708 router.add(route(Method::Get, "/items/{id}")).unwrap();
3710
3711 assert_eq!(router.routes().len(), 101);
3712
3713 let m = router.match_path("/items/item50", Method::Get).unwrap();
3715 assert_eq!(m.route.path, "/items/item50");
3716
3717 let m = router.match_path("/items/other", Method::Get).unwrap();
3719 assert_eq!(m.route.path, "/items/{id}");
3720 assert_eq!(m.params[0], ("id", "other"));
3721 }
3722
3723 #[test]
3724 fn many_siblings_different_methods() {
3725 let mut router = Router::new();
3726
3727 let methods = vec![
3729 Method::Get,
3730 Method::Post,
3731 Method::Put,
3732 Method::Delete,
3733 Method::Patch,
3734 ];
3735
3736 for i in 0..50 {
3737 for method in &methods {
3738 router
3739 .add(Route::new(*method, &format!("/resource{}", i), TestHandler))
3740 .unwrap();
3741 }
3742 }
3743
3744 assert_eq!(router.routes().len(), 250);
3745
3746 let m = router.match_path("/resource25", Method::Get).unwrap();
3748 assert_eq!(m.route.method, Method::Get);
3749
3750 let m = router.match_path("/resource25", Method::Post).unwrap();
3751 assert_eq!(m.route.method, Method::Post);
3752
3753 let m = router.match_path("/resource25", Method::Delete).unwrap();
3754 assert_eq!(m.route.method, Method::Delete);
3755 }
3756
3757 #[test]
3758 fn stress_wide_and_deep() {
3759 let mut router = Router::new();
3760
3761 for a in 0..10 {
3764 for b in 0..10 {
3765 for c in 0..10 {
3766 let path = format!("/a{}/b{}/c{}", a, b, c);
3767 router.add(route(Method::Get, &path)).unwrap();
3768 }
3769 }
3770 }
3771
3772 assert_eq!(router.routes().len(), 1000);
3773
3774 let m = router.match_path("/a0/b0/c0", Method::Get).unwrap();
3776 assert_eq!(m.route.path, "/a0/b0/c0");
3777
3778 let m = router.match_path("/a5/b5/c5", Method::Get).unwrap();
3779 assert_eq!(m.route.path, "/a5/b5/c5");
3780
3781 let m = router.match_path("/a9/b9/c9", Method::Get).unwrap();
3782 assert_eq!(m.route.path, "/a9/b9/c9");
3783
3784 assert!(router.match_path("/a10/b0/c0", Method::Get).is_none());
3786 assert!(router.match_path("/a0/b10/c0", Method::Get).is_none());
3787 }
3788
3789 #[test]
3794 fn unicode_emoji_in_path() {
3795 let mut router = Router::new();
3796 router.add(route(Method::Get, "/emoji/🎉")).unwrap();
3797
3798 let m = router.match_path("/emoji/🎉", Method::Get);
3799 assert!(m.is_some());
3800 assert_eq!(m.unwrap().route.path, "/emoji/🎉");
3801 }
3802
3803 #[test]
3804 fn unicode_rtl_characters() {
3805 let mut router = Router::new();
3806 router.add(route(Method::Get, "/greet/مرحبا")).unwrap();
3808
3809 let m = router.match_path("/greet/مرحبا", Method::Get);
3810 assert!(m.is_some());
3811 assert_eq!(m.unwrap().route.path, "/greet/مرحبا");
3812 }
3813
3814 #[test]
3815 fn unicode_mixed_scripts() {
3816 let mut router = Router::new();
3817 router
3819 .add(route(Method::Get, "/mix/hello世界Привет"))
3820 .unwrap();
3821
3822 let m = router.match_path("/mix/hello世界Привет", Method::Get);
3823 assert!(m.is_some());
3824 }
3825
3826 #[test]
3827 fn unicode_normalization_awareness() {
3828 let mut router = Router::new();
3829 router.add(route(Method::Get, "/café")).unwrap();
3831
3832 let m = router.match_path("/café", Method::Get);
3834 assert!(m.is_some());
3835
3836 }
3839
3840 #[test]
3841 fn unicode_in_param_with_converter() {
3842 let mut router = Router::new();
3843 router.add(route(Method::Get, "/data/{value:str}")).unwrap();
3844
3845 let m = router.match_path("/data/日本語テスト", Method::Get);
3847 assert!(m.is_some());
3848 assert_eq!(m.unwrap().params[0], ("value", "日本語テスト"));
3849 }
3850
3851 #[test]
3856 fn int_converter_overflow() {
3857 let mut router = Router::new();
3858 router.add(route(Method::Get, "/id/{num:int}")).unwrap();
3859
3860 let overflow = "99999999999999999999999999999";
3862 let path = format!("/id/{}", overflow);
3863 let m = router.match_path(&path, Method::Get);
3864 assert!(m.is_none());
3865 }
3866
3867 #[test]
3868 fn float_converter_very_small() {
3869 let mut router = Router::new();
3870 router.add(route(Method::Get, "/val/{v:float}")).unwrap();
3871
3872 let m = router.match_path("/val/1e-308", Method::Get);
3874 assert!(m.is_some());
3875 }
3876
3877 #[test]
3878 fn float_converter_very_large() {
3879 let mut router = Router::new();
3880 router.add(route(Method::Get, "/val/{v:float}")).unwrap();
3881
3882 let m = router.match_path("/val/1e308", Method::Get);
3884 assert!(m.is_some());
3885 }
3886
3887 #[test]
3888 fn uuid_converter_nil_uuid() {
3889 let mut router = Router::new();
3890 router.add(route(Method::Get, "/obj/{id:uuid}")).unwrap();
3891
3892 let m = router.match_path("/obj/00000000-0000-0000-0000-000000000000", Method::Get);
3894 assert!(m.is_some());
3895 }
3896
3897 #[test]
3898 fn uuid_converter_max_uuid() {
3899 let mut router = Router::new();
3900 router.add(route(Method::Get, "/obj/{id:uuid}")).unwrap();
3901
3902 let m = router.match_path("/obj/ffffffff-ffff-ffff-ffff-ffffffffffff", Method::Get);
3904 assert!(m.is_some());
3905 }
3906
3907 #[test]
3912 fn path_with_dots() {
3913 let mut router = Router::new();
3914 router.add(route(Method::Get, "/files/{name}")).unwrap();
3915
3916 let m = router.match_path("/files/file.name.ext", Method::Get);
3918 assert!(m.is_some());
3919 assert_eq!(m.unwrap().params[0], ("name", "file.name.ext"));
3920 }
3921
3922 #[test]
3923 fn path_with_only_special_chars() {
3924 let mut router = Router::new();
3925 router.add(route(Method::Get, "/data/{val}")).unwrap();
3926
3927 let m = router.match_path("/data/-._~", Method::Get);
3929 assert!(m.is_some());
3930 assert_eq!(m.unwrap().params[0], ("val", "-._~"));
3931 }
3932
3933 #[test]
3934 fn path_segment_with_colon() {
3935 let mut router = Router::new();
3936 router.add(route(Method::Get, "/time/{val}")).unwrap();
3937
3938 let m = router.match_path("/time/12:30:45", Method::Get);
3940 assert!(m.is_some());
3941 assert_eq!(m.unwrap().params[0], ("val", "12:30:45"));
3942 }
3943
3944 #[test]
3945 fn path_segment_with_at_sign() {
3946 let mut router = Router::new();
3947 router.add(route(Method::Get, "/user/{handle}")).unwrap();
3948
3949 let m = router.match_path("/user/@username", Method::Get);
3951 assert!(m.is_some());
3952 assert_eq!(m.unwrap().params[0], ("handle", "@username"));
3953 }
3954
3955 #[test]
3956 fn very_long_segment() {
3957 let mut router = Router::new();
3958 router.add(route(Method::Get, "/data/{val}")).unwrap();
3959
3960 let long_val: String = (0..1000).map(|_| 'x').collect();
3962 let path = format!("/data/{}", long_val);
3963
3964 let m = router.match_path(&path, Method::Get);
3965 assert!(m.is_some());
3966 assert_eq!(m.unwrap().params[0].1.len(), 1000);
3967 }
3968
3969 #[test]
3970 fn very_long_path_total() {
3971 let mut router = Router::new();
3972
3973 let segments: Vec<_> = (0..500).map(|i| format!("s{}", i)).collect();
3975 let path = format!("/{}", segments.join("/"));
3976 router.add(route(Method::Get, &path)).unwrap();
3977
3978 let m = router.match_path(&path, Method::Get);
3979 assert!(m.is_some());
3980 }
3981}