use serde::de::{Deserializer, Visitor};
use serde::ser::Serializer;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct Url(String, bool);
impl std::ops::Deref for Url {
type Target = str;
#[allow(clippy::explicit_auto_deref)]
fn deref(&self) -> &Self::Target {
&*self.0
}
}
impl fmt::Display for Url {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl Url {
pub fn new(s: &str) -> Url {
Url(s.to_owned(), s.parse::<http::Uri>().is_ok())
}
pub fn trim(&mut self) {
if self.0.ends_with('/') {
self.0 = self.0.trim_end_matches('/').to_string();
}
}
pub fn inner(&self) -> &str {
&self.0
}
pub fn is_valid_relay_url(&self) -> bool {
if let Ok(uri) = self.0.parse::<http::Uri>() {
if let Some(scheme) = uri.scheme() {
if scheme.as_str() == "wss" || scheme.as_str() == "ws" {
if let Some(authority) = uri.authority() {
let host = authority.host();
if host == host.trim()
&& !host.starts_with("localhost")
&& !host.starts_with("127.")
&& !host.starts_with("[::1/")
&& !host.starts_with("[0:")
{
return true;
}
}
}
}
}
false
}
pub fn is_valid(&self) -> bool {
self.1
}
#[allow(dead_code)]
pub(crate) fn mock() -> Url {
Url("wss://example.com".to_string(), true)
}
}
impl Serialize for Url {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.0)
}
}
impl<'de> Deserialize<'de> for Url {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(UrlVisitor)
}
}
struct UrlVisitor;
impl Visitor<'_> for UrlVisitor {
type Value = Url;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "a string representing a nostr URL")
}
fn visit_str<E>(self, v: &str) -> Result<Url, E>
where
E: serde::de::Error,
{
Ok(Url::new(v))
}
}
#[cfg(test)]
mod test {
use super::*;
test_serde! {Url, test_url_serde}
}