tx5_core/url.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
use crate::*;
use std::sync::Arc;
use ::url::Url;
/// Tx5 url.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Tx5Url(Arc<Url>);
impl std::fmt::Debug for Tx5Url {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl std::fmt::Display for Tx5Url {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl AsRef<str> for Tx5Url {
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
impl AsRef<Arc<Url>> for Tx5Url {
fn as_ref(&self) -> &Arc<Url> {
&self.0
}
}
impl AsRef<Url> for Tx5Url {
fn as_ref(&self) -> &Url {
&self.0
}
}
impl Tx5Url {
/// Parse a url from a str.
pub fn new<R: AsRef<str>>(r: R) -> Result<Self> {
let url = Url::parse(r.as_ref()).map_err(Error::err)?;
match url.scheme() {
"ws" | "wss" => (),
scheme => {
return Err(Error::err(format!(
"invalid scheme, expected \"ws\" or \"wss\", got: {scheme:?}",
)));
}
}
if url.host_str().is_none() {
return Err(Error::id("InvalidHost"));
}
match url.path_segments() {
// None is okay, it's a signal url
None => (),
Some(mut seg) => {
// None on the first next is still okay
if let Some(first) = seg.next() {
if !first.is_empty() {
if first != "tx5-ws" {
return Err(Error::err(format!(
"invalid first path segment, expected \"tx5-ws\", got: {first:?}",
)));
}
match seg.next() {
None => return Err(Error::id("InvalidPubKey")),
Some(pk) => {
let _id = Id::from_b64(pk)?;
}
}
}
}
}
}
Ok(Self(Arc::new(url)))
}
/// If this url does not contain the `/tx5-ws/[pubkey]` path,
/// then it references a signal server directly, and this function
/// will return `true`. Otherwise, it is a client url.
#[inline]
pub fn is_server(&self) -> bool {
!self.is_client()
}
/// If this url contains the `/tx5-ws/[pubkey]` path,
/// then it is a client url, otherwise, it is a signal
/// server url.
pub fn is_client(&self) -> bool {
match self.0.path_segments() {
None => false,
Some(mut seg) => match seg.next() {
None => false,
Some(first) => !first.is_empty(),
},
}
}
/// If this is a client url, convert it into a server (signal) url,
/// by dropping the path components.
pub fn to_server(&self) -> Self {
Self::new(format!("{}://{}", self.0.scheme(), self.endpoint())).unwrap()
}
/// If this is a server url, convert it to a client (peer) url,
/// by providing the client id/pubkey.
pub fn to_client(&self, id: Id) -> Self {
Self::new(format!(
"{}://{}/tx5-ws/{}",
self.0.scheme(),
self.endpoint(),
id,
))
.unwrap()
}
/// Parse the "id" path segment of this url, if it is a client url.
pub fn id(&self) -> Option<Id> {
match self.0.path_segments() {
None => None,
Some(mut seg) => match seg.next() {
None => None,
Some(_) => match seg.next() {
None => None,
Some(id) => match Id::from_b64(id) {
Err(_) => None,
Ok(id) => Some(id),
},
},
},
}
}
/// Parse the "host:port" portion of this url.
pub fn endpoint(&self) -> String {
let host = self.0.host_str().unwrap();
let port = self.0.port().unwrap_or(443);
format!("{host}:{port}")
}
}