use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use url::Url;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Issuer(Url);
impl Issuer {
pub(crate) const fn new(url: Url) -> Self {
Self(url)
}
#[must_use]
pub const fn as_url(&self) -> &Url {
&self.0
}
#[must_use]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl AsRef<Url> for Issuer {
fn as_ref(&self) -> &Url {
self.as_url()
}
}
impl AsRef<str> for Issuer {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for Issuer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.0.as_str())
}
}
impl PartialEq<Url> for Issuer {
fn eq(&self, other: &Url) -> bool {
&self.0 == other
}
}
impl PartialEq<Issuer> for Url {
fn eq(&self, other: &Issuer) -> bool {
self == &other.0
}
}
impl Serialize for Issuer {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.0.as_str())
}
}
impl<'de> Deserialize<'de> for Issuer {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Url::parse(&s).map(Self).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
#![expect(
clippy::unwrap_used,
reason = "tests do not need to meet production lint standards"
)]
use super::Issuer;
use url::Url;
fn example_url() -> Url {
Url::parse("https://accounts.example.com").unwrap()
}
fn example_issuer() -> Issuer {
Issuer::new(example_url())
}
#[test]
fn issuer_as_url_returns_inner_url() {
assert_eq!(example_issuer().as_url(), &example_url());
}
#[test]
fn issuer_as_str_returns_url_string() {
assert_eq!(example_issuer().as_str(), "https://accounts.example.com/");
}
#[test]
fn issuer_display_matches_url_string() {
assert_eq!(
example_issuer().to_string(),
"https://accounts.example.com/"
);
}
#[test]
fn issuer_partial_eq_url() {
let issuer = example_issuer();
let url = example_url();
assert_eq!(issuer, url);
assert_eq!(url, issuer);
}
#[test]
fn issuer_serde_roundtrip() {
let issuer = example_issuer();
let json = serde_json::to_string(&issuer).unwrap();
let roundtripped: Issuer = serde_json::from_str(&json).unwrap();
assert_eq!(issuer, roundtripped);
}
#[test]
fn issuer_deserialize_rejects_invalid_url() {
let result = serde_json::from_value::<Issuer>(serde_json::json!("not-a-url"));
assert!(result.is_err(), "expected error for invalid URL");
}
}