use std::str::FromStr;
use regex::{Captures, Regex};
use thiserror::Error;
use apx_core::url::http_uri::HttpUri;
#[derive(Debug, Error)]
#[error("{0}")]
pub struct PathError(&'static str);
pub trait FromCaptures {
fn from_captures(caps: Captures) -> Result<Self, PathError>
where Self: Sized;
}
fn extract(caps: Captures<'_>) -> Result<Vec<&str>, PathError> {
let substrings = caps.iter().skip(1)
.map(|maybe_match| {
maybe_match
.map(|match_| match_.as_str())
.ok_or(PathError("invalid path"))
})
.collect::<Result<_, _>>()?;
Ok(substrings)
}
impl<A: FromStr> FromCaptures for (A,) {
fn from_captures(caps: Captures) -> Result<Self, PathError> {
if let [a] = extract(caps)?[..] {
let a = a.parse()
.map_err(|_| PathError("invalid path segment"))?;
Ok((a,))
} else {
Err(PathError("unexpected number of groups"))
}
}
}
impl<A: FromStr, B: FromStr> FromCaptures for (A, B) {
fn from_captures(caps: Captures) -> Result<Self, PathError> {
if let [a, b] = extract(caps)?[..] {
let a = a.parse()
.map_err(|_| PathError("invalid path segment"))?;
let b = b.parse()
.map_err(|_| PathError("invalid path segment"))?;
Ok((a, b))
} else {
Err(PathError("unexpected number of groups"))
}
}
}
pub fn parse_object_id<T: FromCaptures>(
object_id: &str,
path_re: Regex,
) -> Result<(String, T), PathError> {
let uri = HttpUri::parse(object_id)
.map_err(|_| PathError("invalid URL"))?;
let base_uri = uri.base();
let path = uri.to_relative();
let path_caps = path_re.captures(&path)
.ok_or(PathError("invalid path"))?;
let value = T::from_captures(path_caps)?;
Ok((base_uri, value))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_object_id() {
let actor_id = "https://social.example/users/test";
let path_re = Regex::new(r"^/users/(?P<username>[0-9A-Za-z_\-]+)$").unwrap();
let (base_uri, (username,)) = parse_object_id::<(String,)>(
actor_id,
path_re,
).unwrap();
assert_eq!(base_uri, "https://social.example");
assert_eq!(username, "test");
}
}