use crate::{error::Lib3hProtocolError, types::*};
use std::convert::{TryFrom, TryInto};
use url::Url;
static AGENT_SCHEME: &'static str = "agentpubkey";
static NODE_SCHEME: &'static str = "nodepubkey";
static MEMORY_SCHEME: &'static str = "mem";
static UNDEFINED_SCHEME: &'static str = "none";
pub enum UriScheme {
Undefined,
Agent,
Node,
Memory,
Other(String),
}
impl From<UriScheme> for &str {
fn from(scheme: UriScheme) -> &'static str {
match scheme {
UriScheme::Undefined => UNDEFINED_SCHEME,
UriScheme::Agent => AGENT_SCHEME,
UriScheme::Node => NODE_SCHEME,
UriScheme::Memory => MEMORY_SCHEME,
UriScheme::Other(_) => "",
}
}
}
impl From<UriScheme> for String {
fn from(scheme: UriScheme) -> String {
match scheme {
UriScheme::Undefined => UNDEFINED_SCHEME.into(),
UriScheme::Agent => AGENT_SCHEME.into(),
UriScheme::Node => NODE_SCHEME.into(),
UriScheme::Memory => MEMORY_SCHEME.into(),
UriScheme::Other(s) => s,
}
}
}
#[derive(Shrinkwrap, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[shrinkwrap(mutable)]
pub struct Lib3hUri(pub Url);
impl Lib3hUri {
pub fn with_node_and_agent_id(node_id: &NodePubKey, agent_id: &AgentPubKey) -> Self {
let url = Self::parse(&format!("{}:{}?a={}", NODE_SCHEME, node_id, agent_id));
Lib3hUri(url)
}
pub fn with_node_id(node_id: &NodePubKey) -> Self {
let url = Self::parse(&format!("{}:{}", NODE_SCHEME, node_id));
Lib3hUri(url)
}
pub fn with_agent_id(agent_id: &AgentPubKey) -> Self {
let url = Self::parse(&format!("{}:{}", AGENT_SCHEME, agent_id));
Lib3hUri(url)
}
pub fn with_undefined() -> Self {
let url = Self::parse(&format!("{}:", UNDEFINED_SCHEME));
Lib3hUri(url)
}
pub fn with_memory(other: &str) -> Self {
let url = Self::parse(&format!("{}://{}", MEMORY_SCHEME, other));
Lib3hUri(url)
}
pub fn is_scheme(&self, scheme: UriScheme) -> bool {
let s: String = scheme.into();
self.scheme() == s
}
pub fn is_transport(&self) -> bool {
let this_scheme = self.scheme();
this_scheme != UNDEFINED_SCHEME && this_scheme != NODE_SCHEME && this_scheme != AGENT_SCHEME
}
fn parse(url_str: &str) -> Url {
Url::parse(url_str).unwrap_or_else(|_| panic!("Invalid url format: '{}'", url_str))
}
pub fn port(&self) -> Option<u16> {
self.0.port()
}
pub fn host(&self) -> Option<url::Host<&str>> {
self.0.host()
}
pub fn raw_scheme(&self) -> &str {
self.0.scheme()
}
pub fn hostname(&self) -> Option<String> {
self.host().map(|host| host.to_string())
}
pub fn with_port(&self, port: u16) -> Self {
Builder::with_url(self.clone()).with_port(port).build()
}
pub fn set_agent_id(&mut self, agent_id: &AgentPubKey) {
assert!(self.is_scheme(UriScheme::Node));
self.0
.query_pairs_mut()
.clear()
.append_pair("a", &agent_id.to_string());
}
pub fn clear_agent_id(&mut self) {
self.0.set_query(None);
}
pub fn get_agent_id(&self) -> Option<AgentPubKey> {
if !self.is_scheme(UriScheme::Node) {
return None;
}
for (n, v) in self.0.query_pairs() {
if &n == "a" {
return Some(v.to_string().as_str().into());
}
}
None
}
pub fn node_id(&self) -> NodePubKey {
assert!(self.is_scheme(UriScheme::Node), "{:?}", self);
self.0.path().into()
}
pub fn agent_id(&self) -> AgentPubKey {
assert!(self.is_scheme(UriScheme::Agent), "{:?}", self);
self.0.path().into()
}
}
#[derive(Debug, Clone)]
pub struct Builder {
url: url::Url,
}
impl Builder {
pub fn new() -> Self {
Self {
url: Lib3hUri::with_undefined().into(),
}
}
pub fn with_url<T: Into<Lib3hUri>>(url: T) -> Self {
let builder = Builder { url: url.into().0 };
builder
}
pub fn with_raw_url<T: TryInto<Lib3hUri>>(url: T) -> Result<Self, T::Error> {
url.try_into().map(|url| Builder { url: url.0 })
}
pub fn with_host(&mut self, host: &str) -> &mut Self {
self.url
.set_host(Some(host))
.unwrap_or_else(|e| panic!("Error setting host {:?}: {:?}", host, e));
self
}
pub fn with_scheme(&mut self, scheme: &str) -> &mut Self {
self.url
.set_scheme(scheme)
.unwrap_or_else(|e| panic!("Error setting scheme {:?}: {:?}", scheme, e));
self
}
pub fn with_port(&mut self, port: u16) -> &mut Self {
self.url
.set_port(Some(port))
.unwrap_or_else(|e| panic!("Error setting port {:?}: {:?}", port, e));
self
}
pub fn build(&self) -> Lib3hUri {
self.url.clone().into()
}
}
impl TryFrom<&str> for Lib3hUri {
type Error = Lib3hProtocolError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
let url = Url::parse(s)?;
Ok(Lib3hUri(url))
}
}
impl From<Lib3hUri> for Url {
fn from(u: Lib3hUri) -> Url {
u.0
}
}
impl From<Url> for Lib3hUri {
fn from(u: Url) -> Lib3hUri {
Lib3hUri(u)
}
}
impl std::fmt::Display for Lib3hUri {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_uri_scheme_convert_str() {
let s: &str = UriScheme::Agent.into();
assert_eq!(s, AGENT_SCHEME);
let s: &str = UriScheme::Node.into();
assert_eq!(s, NODE_SCHEME);
let s: &str = UriScheme::Memory.into();
assert_eq!(s, MEMORY_SCHEME);
let s: &str = UriScheme::Undefined.into();
assert_eq!(s, UNDEFINED_SCHEME);
}
#[test]
fn test_uri_scheme_convert_string() {
let s: String = UriScheme::Agent.into();
assert_eq!(s, AGENT_SCHEME.to_string());
let s: String = UriScheme::Node.into();
assert_eq!(s, NODE_SCHEME.to_string());
let s: String = UriScheme::Memory.into();
assert_eq!(s, MEMORY_SCHEME.to_string());
let s: String = UriScheme::Undefined.into();
assert_eq!(s, UNDEFINED_SCHEME.to_string());
let s: String = UriScheme::Other("http".to_string()).into();
assert_eq!(s, "http".to_string());
}
#[test]
fn test_uri_from() {
let uri = Lib3hUri::try_from("agentid:HcAsdkfjsdflkjsdf");
assert_eq!(
"Ok(Lib3hUri(\"agentid:HcAsdkfjsdflkjsdf\"))",
format!("{:?}", uri)
);
let uri = Lib3hUri::try_from("badurl");
assert_eq!(
"Err(Lib3hProtocolError(UrlError(RelativeUrlWithoutBase)))",
format!("{:?}", uri)
);
}
#[test]
fn test_address_from_uri() {
let id: AgentPubKey = "HcAsdkfjsdflkjsdf".into();
let uri = Lib3hUri::with_agent_id(&id);
let roundtrip: AgentPubKey = uri.agent_id();
assert_eq!(roundtrip, id);
let id: NodePubKey = "HcAsdkfjsdflkjsdf".into();
let uri = Lib3hUri::with_node_id(&id);
let roundtrip: NodePubKey = uri.node_id();
assert_eq!(roundtrip, id);
}
#[test]
fn test_uri_is_scheme() {
let uri = Lib3hUri::try_from("agentpubkey:HcAsdkfjsdflkjsdf").unwrap();
assert!(uri.is_scheme(UriScheme::Agent));
assert!(!uri.is_scheme(UriScheme::Node));
let uri = Lib3hUri::try_from("ws:x").unwrap();
assert!(!uri.is_scheme(UriScheme::Agent));
assert!(uri.is_scheme(UriScheme::Other("ws".to_string())));
assert!(!uri.is_scheme(UriScheme::Other("http".to_string())));
}
#[test]
fn test_uri_create_transport() {
let node_id: NodePubKey = "fake_node_id".into();
let agent_id: AgentPubKey = "HcAfake_agent_id".into();
let mut uri = Lib3hUri::with_node_and_agent_id(&node_id, &agent_id);
assert_eq!(
"Lib3hUri(\"nodepubkey:fake_node_id?a=HcAfake_agent_id\")",
format!("{:?}", uri)
);
assert_eq!(Some("HcAfake_agent_id".into()), uri.get_agent_id());
uri.set_agent_id(&"bla".into());
assert_eq!(Some("bla".into()), uri.get_agent_id());
assert_eq!(NodePubKey::from("fake_node_id"), uri.node_id());
uri.clear_agent_id();
assert_eq!(None, uri.get_agent_id());
}
#[test]
fn test_uri_builder() {
let scheme = "wss";
let host = "ws1://127.0.0.1/";
let port = 9000;
let url = Builder::with_raw_url(host)
.unwrap_or_else(|e| panic!("with_raw_url: {:?}", e))
.with_scheme(scheme)
.with_port(port)
.build();
assert_eq!(url.to_string(), "wss://127.0.0.1:9000/");
}
}