use std::{collections::BTreeSet, fmt, net::SocketAddr, ops::Deref, str::FromStr};
use anyhow::Context;
use serde::{Deserialize, Serialize};
use url::Url;
use crate::key::{NodeId, PublicKey};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct NodeAddr {
pub node_id: NodeId,
pub info: AddrInfo,
}
impl NodeAddr {
pub fn new(node_id: PublicKey) -> Self {
NodeAddr {
node_id,
info: Default::default(),
}
}
pub fn with_relay_url(mut self, relay_url: RelayUrl) -> Self {
self.info.relay_url = Some(relay_url);
self
}
pub fn with_direct_addresses(
mut self,
addresses: impl IntoIterator<Item = SocketAddr>,
) -> Self {
self.info.direct_addresses = addresses.into_iter().collect();
self
}
pub fn from_parts(
node_id: PublicKey,
relay_url: Option<RelayUrl>,
direct_addresses: Vec<SocketAddr>,
) -> Self {
Self {
node_id,
info: AddrInfo {
relay_url,
direct_addresses: direct_addresses.into_iter().collect(),
},
}
}
pub fn apply_options(&mut self, opts: AddrInfoOptions) {
self.info.apply_options(opts);
}
pub fn direct_addresses(&self) -> impl Iterator<Item = &SocketAddr> {
self.info.direct_addresses.iter()
}
pub fn relay_url(&self) -> Option<&RelayUrl> {
self.info.relay_url.as_ref()
}
}
impl From<(PublicKey, Option<RelayUrl>, &[SocketAddr])> for NodeAddr {
fn from(value: (PublicKey, Option<RelayUrl>, &[SocketAddr])) -> Self {
let (node_id, relay_url, direct_addresses_iter) = value;
NodeAddr {
node_id,
info: AddrInfo {
relay_url,
direct_addresses: direct_addresses_iter.iter().copied().collect(),
},
}
}
}
impl From<NodeId> for NodeAddr {
fn from(node_id: NodeId) -> Self {
NodeAddr::new(node_id)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default, PartialOrd, Ord)]
pub struct AddrInfo {
pub relay_url: Option<RelayUrl>,
pub direct_addresses: BTreeSet<SocketAddr>,
}
impl AddrInfo {
pub fn is_empty(&self) -> bool {
self.relay_url.is_none() && self.direct_addresses.is_empty()
}
pub fn apply_options(&mut self, opts: AddrInfoOptions) {
match opts {
AddrInfoOptions::Id => {
self.direct_addresses.clear();
self.relay_url = None;
}
AddrInfoOptions::RelayAndAddresses => {
}
AddrInfoOptions::Relay => {
self.direct_addresses.clear();
}
AddrInfoOptions::Addresses => {
self.relay_url = None;
}
}
}
}
#[derive(
Copy,
Clone,
PartialEq,
Eq,
Default,
Debug,
derive_more::Display,
derive_more::FromStr,
Serialize,
Deserialize,
)]
pub enum AddrInfoOptions {
#[default]
Id,
RelayAndAddresses,
Relay,
Addresses,
}
#[derive(
Clone, derive_more::Display, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
pub struct RelayUrl(Url);
impl From<Url> for RelayUrl {
fn from(mut url: Url) -> Self {
if let Some(domain) = url.domain() {
if !domain.ends_with('.') {
let domain = String::from(domain) + ".";
url.set_host(Some(&domain)).ok();
}
}
Self(url)
}
}
impl FromStr for RelayUrl {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let inner = Url::from_str(s).context("invalid URL")?;
Ok(RelayUrl::from(inner))
}
}
impl From<RelayUrl> for Url {
fn from(value: RelayUrl) -> Self {
value.0
}
}
impl Deref for RelayUrl {
type Target = Url;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl fmt::Debug for RelayUrl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("RelayUrl")
.field(&DbgStr(self.0.as_str()))
.finish()
}
}
struct DbgStr<'a>(&'a str);
impl<'a> fmt::Debug for DbgStr<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, r#""{}""#, self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_relay_url_debug_display() {
let url = RelayUrl::from(Url::parse("https://example.com").unwrap());
assert_eq!(format!("{url:?}"), r#"RelayUrl("https://example.com./")"#);
assert_eq!(format!("{url}"), "https://example.com./");
}
#[test]
fn test_relay_url_absolute() {
let url = RelayUrl::from(Url::parse("https://example.com").unwrap());
assert_eq!(url.domain(), Some("example.com."));
let url1 = RelayUrl::from(Url::parse("https://example.com.").unwrap());
assert_eq!(url, url1);
let url2 = RelayUrl::from(Url::parse("https://example.com./").unwrap());
assert_eq!(url, url2);
let url3 = RelayUrl::from(Url::parse("https://example.com/").unwrap());
assert_eq!(url, url3);
}
}