use serde::{Deserialize, Deserializer, Serialize};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum MethodError {
#[error("Fail to parse method string: {0}")]
Parse(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
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(),
}
)
}
}
impl Method {
pub fn from_str(method: &str) -> Result<Self, MethodError> {
match method.to_uppercase().as_str() {
"GET" => Ok(Self::Get),
"POST" => Ok(Self::Post),
"PUT" => Ok(Self::Put),
"DELETE" => Ok(Self::Delete),
_ => Err(MethodError::Parse(format!("Invalid method: {}", method))),
}
}
}
#[derive(Debug, Error)]
pub enum UrnError {
#[error("Fail to parse Urn string: {0}")]
Parse(String),
#[error("{0}")]
InvalidMethod(#[from] MethodError),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Urn {
pub method: Option<Method>,
pub url: String,
}
impl Serialize for Urn {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_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 {
if let Some(method) = &self.method {
write!(f, "{}:{}", method.to_string(), self.url)
} else {
write!(f, "{}", self.url)
}
}
}
impl Urn {
pub fn new(method: Option<String>, url: String) -> Result<Self, UrnError> {
let method = method.map(|m| Method::from_str(&m)).transpose()?;
Ok(Self { method, url })
}
pub fn from_str(urn: &str) -> Result<Self, UrnError> {
if urn.starts_with("http:") || urn.starts_with("https:") {
return Ok(Self {
method: Some(Method::Get),
url: urn.to_string(),
});
}
let parts: Vec<&str> = urn.splitn(2, ':').collect();
let (method, url) = match parts.len() {
1 => (None, parts[0].trim()),
2 => (Some(parts[0].trim()), parts[1].trim()),
_ => Err(UrnError::Parse(format!("Invalid URN \"{urn}\"")))?,
};
if url.is_empty() {
Err(UrnError::Parse(format!("Invalid URN \"{urn}\"")))?
}
let method = method.map(|m| Method::from_str(&m)).transpose()?;
Ok(Self {
method,
url: url.to_string(),
})
}
pub fn matches(&self, method: &str, url: &str) -> bool {
if let Some(self_method) = &self.method {
if self_method.to_string() != method.to_uppercase() {
return false;
}
}
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, Some(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, Some(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, Some(Method::Get)));
assert_eq!(urn.url, "http:example.com");
let urn = Urn::from_str("https:example.com").unwrap();
assert!(matches!(urn.method, Some(Method::Get)));
assert_eq!(urn.url, "https:example.com");
}
}