rws_server/router/
names.rs1use std::collections::HashMap;
8use std::sync::{OnceLock, RwLock};
9
10use super::matcher::{parse_pattern, Segment};
11
12fn registry() -> &'static RwLock<HashMap<String, String>> {
13 static NAMES: OnceLock<RwLock<HashMap<String, String>>> = OnceLock::new();
14 NAMES.get_or_init(|| RwLock::new(HashMap::new()))
15}
16
17pub fn name_route(name: &str, pattern: &str) {
21 if let Ok(mut names) = registry().write() {
22 names.insert(name.to_string(), pattern.to_string());
23 }
24}
25
26pub fn named_pattern(name: &str) -> Option<String> {
28 registry().read().ok()?.get(name).cloned()
29}
30
31pub fn named_routes() -> Vec<(String, String)> {
33 let mut all: Vec<(String, String)> = match registry().read() {
34 Ok(names) => names.iter().map(|(n, p)| (n.clone(), p.clone())).collect(),
35 Err(_) => Vec::new(),
36 };
37 all.sort();
38 all
39}
40
41pub fn url_for(name: &str, params: &[(&str, &str)]) -> Option<String> {
56 build_path(&named_pattern(name)?, params)
57}
58
59pub fn build_path(pattern: &str, params: &[(&str, &str)]) -> Option<String> {
61 let segments = parse_pattern(pattern);
62 let mut used = Vec::new();
63 let mut path = String::new();
64 for segment in &segments {
65 path.push('/');
66 match segment {
67 Segment::Literal(text) => path.push_str(text),
68 Segment::Param(name) => {
69 let value = params.iter().find(|(k, _)| k == name)?.1;
70 if value.is_empty() {
71 return None;
72 }
73 path.push_str(&encode(value, false));
74 used.push(name.as_str());
75 }
76 Segment::Wildcard(name) => {
77 let value = params.iter().find(|(k, _)| k == name)?.1;
78 path.push_str(&encode(value.trim_start_matches('/'), true));
79 used.push(name.as_str());
80 }
81 }
82 }
83 if path.is_empty() {
84 path.push('/');
85 }
86 let query: Vec<String> = params
87 .iter()
88 .filter(|(k, _)| !used.contains(k))
89 .map(|(k, v)| format!("{}={}", encode(k, false), encode(v, false)))
90 .collect();
91 if !query.is_empty() {
92 path.push('?');
93 path.push_str(&query.join("&"));
94 }
95 Some(path)
96}
97
98pub(crate) fn encode(value: &str, keep_slash: bool) -> String {
101 let mut out = String::with_capacity(value.len());
102 for byte in value.bytes() {
103 let unreserved = byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~') || (keep_slash && byte == b'/');
104 if unreserved {
105 out.push(byte as char);
106 } else {
107 out.push_str(&format!("%{byte:02X}"));
108 }
109 }
110 out
111}