1use anyhow::bail;
4
5fn is_id(s: &str) -> bool {
6 s.len() == 10 && s.bytes().all(|b| b.is_ascii_alphanumeric())
7}
8
9pub fn parse(s: &str) -> anyhow::Result<String> {
11 let s = s.trim();
12 if is_id(s) {
13 return Ok(s.to_string());
14 }
15 if let Ok(u) = url::Url::parse(s) {
16 let segs: Vec<&str> = u.path_segments().map(|p| p.collect()).unwrap_or_default();
17 for w in segs.windows(2) {
18 if w[0] == "d" && is_id(w[1]) {
19 return Ok(w[1].to_string());
20 }
21 }
22 }
23 bail!("`{s}` is not a document id or Semora URL")
24}
25
26#[cfg(test)]
27mod tests {
28 use super::parse;
29
30 #[test]
31 fn parses_ids_and_urls() {
32 assert_eq!(parse("k3F9xQ2mLp").unwrap(), "k3F9xQ2mLp");
33 assert_eq!(
34 parse("https://semora.example.com/d/k3F9xQ2mLp/").unwrap(),
35 "k3F9xQ2mLp"
36 );
37 assert_eq!(
38 parse("https://semora.example.com/d/k3F9xQ2mLp/guide/a.md").unwrap(),
39 "k3F9xQ2mLp"
40 );
41 assert_eq!(
42 parse("wss://semora.example.com/ws/d/k3F9xQ2mLp").unwrap(),
43 "k3F9xQ2mLp"
44 );
45 assert!(parse("nope").is_err());
46 assert!(parse("https://x/d/short/").is_err());
47 }
48}