use serde::{Deserialize, Deserializer};
use thiserror::Error;
#[derive(Debug, Clone, PartialEq)]
pub enum Method {
Get,
Post,
Put,
Delete,
}
impl<'de> Deserialize<'de> for Method {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?.to_uppercase();
match s.as_str() {
"GET" => Ok(Method::Get),
"POST" => Ok(Method::Post),
"PUT" => Ok(Method::Put),
"DELETE" => Ok(Method::Delete),
_ => Err(serde::de::Error::custom(format!("Invalid method: {}", s))),
}
}
}
impl std::fmt::Display for Method {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Method::Get => "GET".to_string(),
Method::Post => "POST".to_string(),
Method::Put => "PUT".to_string(),
Method::Delete => "DELETE".to_string(),
}
)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Urn {
pub method: Method,
pub url: String,
}
#[derive(Debug, Error)]
pub enum UrnError {
#[error("Fail to parse Urn string: {0}")]
Parse(String),
}
impl<'de> Deserialize<'de> for Urn {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Urn::from_str(&s).map_err(|e| serde::de::Error::custom(format!("{e}")))
}
}
impl std::fmt::Display for Urn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.method.to_string(), self.url)
}
}
impl Urn {
pub fn from_str(urn: &str) -> Result<Self, UrnError> {
if urn.starts_with("http:") || urn.starts_with("https:") {
return Ok(Self {
method: Method::Get,
url: urn.to_string(),
});
}
let parts: Vec<&str> = urn.splitn(2, ':').collect();
let (method, url) = match parts.len() {
1 => ("GET", parts[0].trim()),
2 => (parts[0].trim(), parts[1].trim()),
_ => Err(UrnError::Parse(format!("Invalid URN \"{urn}\"")))?,
};
if url.is_empty() {
Err(UrnError::Parse(format!("Invalid URN \"{urn}\"")))?
}
Ok(Self {
method: match method.to_uppercase().as_str() {
"GET" => Method::Get,
"POST" => Method::Post,
"PUT" => Method::Put,
"DELETE" => Method::Delete,
_ => panic!("Invalid method: {}", method),
},
url: url.to_string(),
})
}
pub fn matches(&self, method: &str, url: &str) -> bool {
self.method.to_string() == method.to_uppercase() && url.starts_with(&self.url)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_urn_new() {
let urn = Urn::from_str("GET:example.com").unwrap();
assert!(matches!(urn.method, Method::Get));
assert_eq!(urn.url, "example.com");
}
#[test]
fn test_urn_with_complex_url() {
let urn = Urn::from_str("POST:api.example.com/v1/users").unwrap();
assert!(matches!(urn.method, Method::Post));
assert_eq!(urn.url, "api.example.com/v1/users");
}
#[test]
#[should_panic(expected = "Invalid URN:")]
fn test_urn_with_empty_url() {
let _urn = Urn::from_str("PUT:");
}
#[test]
fn test_http_prefix_urls() {
let urn = Urn::from_str("http:example.com").unwrap();
assert!(matches!(urn.method, Method::Get));
assert_eq!(urn.url, "http:example.com");
let urn = Urn::from_str("https:example.com").unwrap();
assert!(matches!(urn.method, Method::Get));
assert_eq!(urn.url, "https:example.com");
}
}