1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
//! Navigation stack with parameters.
//!
//! A simple client-side routing stack. Each entry has a path
//! string and optional parameters. Push to navigate forward,
//! pop to go back.
use std::collections::HashMap;
/// A single entry in the navigation stack.
#[derive(Debug, Clone)]
pub struct RouteEntry {
/// Path string identifying the route (e.g. `"/notes/42"`).
pub path: String,
/// Arbitrary JSON parameters associated with the entry.
pub params: HashMap<String, serde_json::Value>,
}
/// A navigation stack tracking the current route and history.
///
/// The root route (first push) cannot be popped.
#[derive(Debug, Clone)]
pub struct Route {
stack: Vec<RouteEntry>,
}
impl Route {
/// Create a new route starting at the given path with no parameters.
pub fn new(path: &str) -> Self {
Self {
stack: vec![RouteEntry {
path: path.to_string(),
params: HashMap::new(),
}],
}
}
/// Create a new route at the given path with initial parameters.
pub fn new_with_params(path: &str, params: HashMap<String, serde_json::Value>) -> Self {
Self {
stack: vec![RouteEntry {
path: path.to_string(),
params,
}],
}
}
/// Navigate to a new path, pushing it onto the stack.
pub fn push(&mut self, path: &str) {
self.push_with_params(path, HashMap::new());
}
/// Navigate to a new path with parameters.
pub fn push_with_params(&mut self, path: &str, params: HashMap<String, serde_json::Value>) {
self.stack.push(RouteEntry {
path: path.to_string(),
params,
});
}
/// Go back one level. Returns `false` if already at the root.
pub fn pop(&mut self) -> bool {
if self.stack.len() <= 1 {
return false;
}
self.stack.pop();
true
}
/// Replace the current (top) route without changing history depth.
pub fn replace_top(&mut self, path: &str) {
self.replace_top_with_params(path, HashMap::new());
}
/// Replace the current route with a new path and parameters.
pub fn replace_top_with_params(
&mut self,
path: &str,
params: HashMap<String, serde_json::Value>,
) {
if let Some(top) = self.stack.last_mut() {
top.path = path.to_string();
top.params = params;
}
}
/// The current path (top of the stack).
///
/// # Panics
///
/// Never in practice: [`Route::new`] always pushes a root entry,
/// and no public API allows the stack to become empty. The
/// internal `expect` guards against the private invariant being
/// violated.
pub fn current(&self) -> &str {
&self.stack.last().expect("route stack is never empty").path
}
/// Parameters of the current route.
///
/// # Panics
///
/// See [`Route::current`] for the stack-never-empty invariant.
pub fn params(&self) -> &HashMap<String, serde_json::Value> {
&self
.stack
.last()
.expect("route stack is never empty")
.params
}
/// Whether there is a previous route to go back to.
pub fn can_go_back(&self) -> bool {
self.stack.len() > 1
}
/// How many entries are in the stack (including root).
pub fn depth(&self) -> usize {
self.stack.len()
}
/// The full navigation history as path strings (most recent first).
pub fn history(&self) -> Vec<&str> {
self.stack.iter().rev().map(|e| e.path.as_str()).collect()
}
}