route.rs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. use either::Either;
  2. use urlencoding::decode;
  3. use crate::{
  4. navigation::NavigationTarget,
  5. routes::{ParameterRoute, Route, RouteContent, Segment},
  6. RouterState,
  7. };
  8. pub fn route_segment<T: Clone>(
  9. segment: &Segment<T>,
  10. values: &[&str],
  11. state: RouterState<T>,
  12. ) -> Either<RouterState<T>, NavigationTarget> {
  13. route_segment_internal(segment, values, state, None, false)
  14. }
  15. fn route_segment_internal<T: Clone>(
  16. segment: &Segment<T>,
  17. values: &[&str],
  18. state: RouterState<T>,
  19. mut fallback: Option<RouteContent<T>>,
  20. mut clear_fallback: bool,
  21. ) -> Either<RouterState<T>, NavigationTarget> {
  22. // fallback
  23. if let Some(fb) = &segment.fallback {
  24. fallback = Some(fb.clone());
  25. }
  26. if let Some(clear) = &segment.clear_fallback {
  27. clear_fallback = *clear;
  28. }
  29. // index route
  30. if values.is_empty() {
  31. if let Some(c) = &segment.index {
  32. return merge(state, c.clone());
  33. }
  34. return Either::Left(state);
  35. }
  36. // fixed route
  37. if let Some(r) = segment.fixed.get(values[0]) {
  38. return merge_route(values, r, state, fallback, clear_fallback);
  39. }
  40. // matching routes
  41. for (m, r) in &segment.matching {
  42. if m.matches(values[0]) {
  43. return merge_parameter_route(values, r, state, fallback, clear_fallback);
  44. }
  45. }
  46. // catchall
  47. if let Some(c) = &segment.catch_all {
  48. return merge_parameter_route(values, c.as_ref(), state, fallback, clear_fallback);
  49. }
  50. merge_fallback(state, fallback, clear_fallback)
  51. }
  52. fn merge<T: Clone>(
  53. mut state: RouterState<T>,
  54. content: RouteContent<T>,
  55. ) -> Either<RouterState<T>, NavigationTarget> {
  56. match content {
  57. RouteContent::Content(c) => state.content.push(c),
  58. RouteContent::Redirect(t) => return Either::Right(t),
  59. RouteContent::MultiContent { main, named } => {
  60. if let Some(main) = main {
  61. state.content.push(main);
  62. }
  63. for (name, content) in named {
  64. state.named_content.entry(name).or_default().push(content);
  65. }
  66. }
  67. }
  68. Either::Left(state)
  69. }
  70. fn merge_route<T: Clone>(
  71. values: &[&str],
  72. route: &Route<T>,
  73. mut state: RouterState<T>,
  74. fallback: Option<RouteContent<T>>,
  75. clear_fallback: bool,
  76. ) -> Either<RouterState<T>, NavigationTarget> {
  77. // merge content
  78. if let Some(c) = &route.content {
  79. match merge(state, c.clone()) {
  80. Either::Left(s) => state = s,
  81. Either::Right(t) => return Either::Right(t),
  82. }
  83. }
  84. if let Some(n) = &route.name {
  85. state.names.insert(n.clone());
  86. }
  87. match (&route.nested, values.is_empty()) {
  88. (Some(n), _) => route_segment_internal(n, &values[1..], state, fallback, clear_fallback),
  89. (None, false) => merge_fallback(state, fallback, clear_fallback),
  90. _ => Either::Left(state),
  91. }
  92. }
  93. fn merge_parameter_route<T: Clone>(
  94. values: &[&str],
  95. route: &ParameterRoute<T>,
  96. mut state: RouterState<T>,
  97. fallback: Option<RouteContent<T>>,
  98. clear_fallback: bool,
  99. ) -> Either<RouterState<T>, NavigationTarget> {
  100. // merge content
  101. if let Some(c) = &route.content {
  102. match merge(state, c.clone()) {
  103. Either::Left(s) => state = s,
  104. Either::Right(t) => return Either::Right(t),
  105. }
  106. }
  107. if let Some(n) = &route.name {
  108. state.names.insert(n.clone());
  109. }
  110. state.parameters.insert(
  111. route.key.clone(),
  112. decode(values[0]).unwrap(/* string already is UTF-8 */).into_owned(),
  113. );
  114. match (&route.nested, values.is_empty()) {
  115. (Some(n), _) => route_segment_internal(n, &values[1..], state, fallback, clear_fallback),
  116. (None, false) => merge_fallback(state, fallback, clear_fallback),
  117. _ => Either::Left(state),
  118. }
  119. }
  120. fn merge_fallback<T: Clone>(
  121. mut state: RouterState<T>,
  122. fallback: Option<RouteContent<T>>,
  123. clear_fallback: bool,
  124. ) -> Either<RouterState<T>, NavigationTarget> {
  125. // fallback clearing
  126. if clear_fallback {
  127. state.content.clear();
  128. state.names.clear();
  129. state.parameters.clear();
  130. }
  131. // fallback content
  132. match fallback {
  133. Some(fallback) => merge(state, fallback),
  134. None => Either::Left(state),
  135. }
  136. }
  137. #[cfg(test)]
  138. mod tests {
  139. use std::collections::{BTreeMap, HashMap, HashSet};
  140. use crate::{
  141. routes::{multi, ContentAtom},
  142. Name,
  143. };
  144. use super::*;
  145. fn test_segment() -> Segment<&'static str> {
  146. Segment::content(ContentAtom("index"))
  147. .fixed("fixed", Route::content(ContentAtom("fixed")).name::<bool>())
  148. .matching(
  149. String::from("matching"),
  150. ParameterRoute::content::<u8>(ContentAtom("matching"))
  151. .nested(Segment::empty().fixed("nested", ContentAtom("matching nested"))),
  152. )
  153. .catch_all(
  154. ParameterRoute::content::<u16>(ContentAtom("catch all"))
  155. .nested(Segment::empty().fixed("nested", ContentAtom("catch all nested"))),
  156. )
  157. .fixed(
  158. "nested",
  159. Route::content(ContentAtom("nested")).name::<u32>().nested(
  160. Segment::content(ContentAtom("nested index"))
  161. .fixed("again", ContentAtom("nested again")),
  162. ),
  163. )
  164. .fixed("redirect", "/redirect")
  165. .fixed(
  166. "fallback",
  167. Route::content(ContentAtom("fallback")).nested(
  168. Segment::empty()
  169. .fixed(
  170. "keep",
  171. Route::content(ContentAtom("keep route")).nested(
  172. Segment::content(ContentAtom("keep index"))
  173. .fallback(ContentAtom("keep")),
  174. ),
  175. )
  176. .fixed(
  177. "clear",
  178. Route::content(ContentAtom("clear route")).nested(
  179. Segment::empty()
  180. .fallback(ContentAtom("clear"))
  181. .clear_fallback(true),
  182. ),
  183. ),
  184. ),
  185. )
  186. .fixed(
  187. "no_fallback",
  188. Route::content(ContentAtom("no fallback")).nested(
  189. Segment::empty()
  190. .fixed(
  191. "keep",
  192. Route::content(ContentAtom("keep route"))
  193. .nested(Segment::empty().clear_fallback(false)),
  194. )
  195. .fixed(
  196. "clear",
  197. Route::content(ContentAtom("clear route"))
  198. .nested(Segment::empty().clear_fallback(true)),
  199. ),
  200. ),
  201. )
  202. .fixed(
  203. "named_content",
  204. Route::content(
  205. multi(None)
  206. .add_named::<i8>(ContentAtom("1"))
  207. .add_named::<i16>(ContentAtom("2")),
  208. )
  209. .nested(Segment::content(multi(Some(ContentAtom("3"))))),
  210. )
  211. }
  212. #[test]
  213. fn route_index() {
  214. let state = route_segment(
  215. &test_segment(),
  216. &[],
  217. RouterState {
  218. path: String::from("/"),
  219. can_go_back: false,
  220. can_go_forward: true,
  221. ..Default::default()
  222. },
  223. );
  224. assert!(state.is_left());
  225. let state = state.unwrap_left();
  226. assert_eq!(state.content, vec![ContentAtom("index")]);
  227. assert!(state.names.is_empty());
  228. assert!(state.parameters.is_empty());
  229. assert_eq!(state.path, String::from("/"));
  230. assert_eq!(state.can_go_back, false);
  231. assert_eq!(state.can_go_forward, true);
  232. }
  233. #[test]
  234. fn route_fixed() {
  235. let state = route_segment(&test_segment(), &["fixed"], Default::default());
  236. assert!(state.is_left());
  237. let state = state.unwrap_left();
  238. assert_eq!(state.content, vec![ContentAtom("fixed")]);
  239. assert_eq!(state.names, {
  240. let mut r = HashSet::new();
  241. r.insert(Name::of::<bool>());
  242. r
  243. });
  244. assert!(state.parameters.is_empty());
  245. }
  246. #[test]
  247. fn route_matching() {
  248. let state = route_segment(&test_segment(), &["matching"], Default::default());
  249. assert!(state.is_left());
  250. let state = state.unwrap_left();
  251. assert_eq!(state.content, vec![ContentAtom("matching")]);
  252. assert!(state.names.is_empty());
  253. assert_eq!(state.parameters, {
  254. let mut r = HashMap::new();
  255. r.insert(Name::of::<u8>(), String::from("matching"));
  256. r
  257. });
  258. }
  259. #[test]
  260. fn route_matching_nested() {
  261. let state = route_segment(&test_segment(), &["matching", "nested"], Default::default());
  262. assert!(state.is_left());
  263. let state = state.unwrap_left();
  264. assert_eq!(
  265. state.content,
  266. vec![ContentAtom("matching"), ContentAtom("matching nested")]
  267. );
  268. assert!(state.names.is_empty());
  269. assert_eq!(state.parameters, {
  270. let mut r = HashMap::new();
  271. r.insert(Name::of::<u8>(), String::from("matching"));
  272. r
  273. });
  274. }
  275. #[test]
  276. fn route_catch_all() {
  277. let state = route_segment(&test_segment(), &["invalid"], Default::default());
  278. assert!(state.is_left());
  279. let state = state.unwrap_left();
  280. assert_eq!(state.content, vec![ContentAtom("catch all")]);
  281. assert!(state.names.is_empty());
  282. assert_eq!(state.parameters, {
  283. let mut r = HashMap::new();
  284. r.insert(Name::of::<u16>(), String::from("invalid"));
  285. r
  286. });
  287. }
  288. #[test]
  289. fn route_catch_all_nested() {
  290. let state = route_segment(&test_segment(), &["invalid", "nested"], Default::default());
  291. assert!(state.is_left());
  292. let state = state.unwrap_left();
  293. assert_eq!(
  294. state.content,
  295. vec![ContentAtom("catch all"), ContentAtom("catch all nested")]
  296. );
  297. assert!(state.names.is_empty());
  298. assert_eq!(state.parameters, {
  299. let mut r = HashMap::new();
  300. r.insert(Name::of::<u16>(), String::from("invalid"));
  301. r
  302. });
  303. }
  304. #[test]
  305. fn route_nested_index() {
  306. let state = route_segment(&test_segment(), &["nested"], Default::default());
  307. assert!(state.is_left());
  308. let state = state.unwrap_left();
  309. assert_eq!(
  310. state.content,
  311. vec![ContentAtom("nested"), ContentAtom("nested index")]
  312. );
  313. assert_eq!(state.names, {
  314. let mut r = HashSet::new();
  315. r.insert(Name::of::<u32>());
  316. r
  317. });
  318. assert!(state.parameters.is_empty());
  319. }
  320. #[test]
  321. fn route_nested_again() {
  322. let state = route_segment(&test_segment(), &["nested", "again"], Default::default());
  323. assert!(state.is_left());
  324. let state = state.unwrap_left();
  325. assert_eq!(
  326. state.content,
  327. vec![ContentAtom("nested"), ContentAtom("nested again")]
  328. );
  329. assert_eq!(state.names, {
  330. let mut r = HashSet::new();
  331. r.insert(Name::of::<u32>());
  332. r
  333. });
  334. assert!(state.parameters.is_empty());
  335. }
  336. #[test]
  337. fn route_redirect() {
  338. let state = route_segment(&test_segment(), &["redirect"], Default::default());
  339. assert_eq!(state.unwrap_right(), "/redirect".into());
  340. }
  341. #[test]
  342. fn route_fallback_keep() {
  343. let state = route_segment(
  344. &test_segment(),
  345. &["fallback", "keep", "invalid"],
  346. Default::default(),
  347. );
  348. assert!(state.is_left());
  349. let state = state.unwrap_left();
  350. assert_eq!(
  351. state.content,
  352. vec![
  353. ContentAtom("fallback"),
  354. ContentAtom("keep route"),
  355. ContentAtom("keep")
  356. ]
  357. );
  358. assert!(state.names.is_empty());
  359. assert!(state.parameters.is_empty());
  360. }
  361. #[test]
  362. fn route_fallback_clear() {
  363. let state = route_segment(
  364. &test_segment(),
  365. &["fallback", "clear", "invalid"],
  366. Default::default(),
  367. );
  368. assert!(state.is_left());
  369. let state = state.unwrap_left();
  370. assert_eq!(state.content, vec![ContentAtom("clear")]);
  371. assert!(state.names.is_empty());
  372. assert!(state.parameters.is_empty());
  373. }
  374. #[test]
  375. fn route_named_content() {
  376. let state = route_segment(&test_segment(), &["named_content"], Default::default());
  377. assert!(state.is_left());
  378. let state = state.unwrap_left();
  379. assert_eq!(state.content, vec![ContentAtom("3")]);
  380. assert_eq!(state.named_content, {
  381. let mut r = BTreeMap::new();
  382. r.insert(Name::of::<i8>(), vec![ContentAtom("1")]);
  383. r.insert(Name::of::<i16>(), vec![ContentAtom("2")]);
  384. r
  385. });
  386. assert!(state.names.is_empty());
  387. assert!(state.parameters.is_empty());
  388. }
  389. #[test]
  390. #[ignore = "not yet implemented"]
  391. fn no_fallback() {
  392. let state = route_segment(
  393. &test_segment(),
  394. &["no_fallback", "keep", "invalid"],
  395. Default::default(),
  396. );
  397. assert!(state.is_left());
  398. let state = state.unwrap_left();
  399. assert_eq!(
  400. state.content,
  401. vec![
  402. ContentAtom("fallback"),
  403. ContentAtom("keep route"),
  404. ContentAtom("keep")
  405. ]
  406. );
  407. assert!(state.names.is_empty());
  408. assert!(state.parameters.is_empty());
  409. }
  410. #[test]
  411. #[ignore = "not yet implemented"]
  412. fn no_fallback_with_clearing() {
  413. let state = route_segment(
  414. &test_segment(),
  415. &["fallback", "clear", "invalid"],
  416. Default::default(),
  417. );
  418. assert!(state.is_left());
  419. let state = state.unwrap_left();
  420. assert!(state.content.is_empty());
  421. assert!(state.names.is_empty());
  422. assert!(state.parameters.is_empty());
  423. }
  424. #[test]
  425. fn url_encoding() {
  426. let state = route_segment(&test_segment(), &["%F0%9F%A5%B3"], Default::default());
  427. assert!(state.is_left());
  428. let state = state.unwrap_left();
  429. assert_eq!(state.content, vec![ContentAtom("catch all")]);
  430. assert!(state.names.is_empty());
  431. assert_eq!(state.parameters, {
  432. let mut r = HashMap::new();
  433. r.insert(Name::of::<u16>(), "🥳".to_string());
  434. r
  435. });
  436. }
  437. }