use std::collections::HashMap;
use std::sync::{OnceLock, RwLock};
use super::matcher::{parse_pattern, Segment};
fn registry() -> &'static RwLock<HashMap<String, String>> {
static NAMES: OnceLock<RwLock<HashMap<String, String>>> = OnceLock::new();
NAMES.get_or_init(|| RwLock::new(HashMap::new()))
}
pub fn name_route(name: &str, pattern: &str) {
if let Ok(mut names) = registry().write() {
names.insert(name.to_string(), pattern.to_string());
}
}
pub fn named_pattern(name: &str) -> Option<String> {
registry().read().ok()?.get(name).cloned()
}
pub fn named_routes() -> Vec<(String, String)> {
let mut all: Vec<(String, String)> = match registry().read() {
Ok(names) => names.iter().map(|(n, p)| (n.clone(), p.clone())).collect(),
Err(_) => Vec::new(),
};
all.sort();
all
}
pub fn url_for(name: &str, params: &[(&str, &str)]) -> Option<String> {
build_path(&named_pattern(name)?, params)
}
pub fn build_path(pattern: &str, params: &[(&str, &str)]) -> Option<String> {
let segments = parse_pattern(pattern);
let mut used = Vec::new();
let mut path = String::new();
for segment in &segments {
path.push('/');
match segment {
Segment::Literal(text) => path.push_str(text),
Segment::Param(name) => {
let value = params.iter().find(|(k, _)| k == name)?.1;
if value.is_empty() {
return None;
}
path.push_str(&encode(value, false));
used.push(name.as_str());
}
Segment::Wildcard(name) => {
let value = params.iter().find(|(k, _)| k == name)?.1;
path.push_str(&encode(value.trim_start_matches('/'), true));
used.push(name.as_str());
}
}
}
if path.is_empty() {
path.push('/');
}
let query: Vec<String> = params
.iter()
.filter(|(k, _)| !used.contains(k))
.map(|(k, v)| format!("{}={}", encode(k, false), encode(v, false)))
.collect();
if !query.is_empty() {
path.push('?');
path.push_str(&query.join("&"));
}
Some(path)
}
pub(crate) fn encode(value: &str, keep_slash: bool) -> String {
let mut out = String::with_capacity(value.len());
for byte in value.bytes() {
let unreserved = byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~') || (keep_slash && byte == b'/');
if unreserved {
out.push(byte as char);
} else {
out.push_str(&format!("%{byte:02X}"));
}
}
out
}