use core::fmt;
use std::{collections::HashSet, str::FromStr};
use anyhow::{Context, Error, Result, anyhow, bail};
use nostr::nips::nip01::Coordinate;
use nostr_sdk::{PublicKey, Url};
#[derive(Debug, PartialEq, Default, Clone)]
pub enum ServerProtocol {
Ssh,
Https,
Http,
Git,
Ftp,
Filesystem,
#[default]
Unspecified,
UnauthHttps,
UnauthHttp,
}
impl fmt::Display for ServerProtocol {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ServerProtocol::Http => write!(f, "http"),
ServerProtocol::Https => write!(f, "https"),
ServerProtocol::Ftp => write!(f, "ftp"),
ServerProtocol::Ssh => write!(f, "ssh"),
ServerProtocol::Git => write!(f, "git"),
ServerProtocol::Filesystem => write!(f, "filesystem"),
ServerProtocol::Unspecified => write!(f, "unsepcified"),
ServerProtocol::UnauthHttps => {
write!(f, "https (unauthenticated)")
}
ServerProtocol::UnauthHttp => {
write!(f, "http (unauthenticated)")
}
}
}
}
impl FromStr for ServerProtocol {
type Err = Error;
fn from_str(s: &str) -> Result<ServerProtocol> {
match s {
"http" => Ok(ServerProtocol::Http),
"https" => Ok(ServerProtocol::Https),
"ftp" => Ok(ServerProtocol::Ftp),
"ssh" => Ok(ServerProtocol::Ssh),
"git" => Ok(ServerProtocol::Git),
"filesystem" => Ok(ServerProtocol::Filesystem),
"http (unauthenticated)" => {
Ok(ServerProtocol::UnauthHttp)
}
"https (unauthenticated)" => {
Ok(ServerProtocol::UnauthHttps)
}
_ => bail!("not listed as a server protocol"),
}
}
}
#[derive(Debug, PartialEq)]
pub struct NostrUrlDecoded {
pub original_string: String,
pub coordinates: HashSet<Coordinate>,
pub protocol: Option<ServerProtocol>,
pub user: Option<String>,
}
static INCORRECT_NOSTR_URL_FORMAT_ERROR: &str = "incorrect nostr git url format. try nostr://naddr123 or nostr://npub123/my-repo or nostr://ssh/npub123/relay.damus.io/my-repo";
impl std::str::FromStr for NostrUrlDecoded {
type Err = anyhow::Error;
fn from_str(url: &str) -> Result<Self> {
let mut coordinates = HashSet::new();
let mut protocol = None;
let mut user = None;
let mut relays = vec![];
if !url.starts_with("nostr://") {
bail!("nostr git url must start with nostr://");
}
for (name, value) in Url::parse(url)?.query_pairs() {
if name.contains("relay") {
let mut decoded = urlencoding::decode(&value)
.context(
"could not parse relays in nostr git url",
)?
.to_string();
if !decoded.starts_with("ws://")
&& !decoded.starts_with("wss://")
{
decoded = format!("wss://{decoded}");
}
let url = Url::parse(&decoded).context(
"could not parse relays in nostr git url",
)?;
relays.push(url.to_string());
} else if name == "protocol" {
protocol = match value.as_ref() {
"ssh" => Some(ServerProtocol::Ssh),
"https" => Some(ServerProtocol::Https),
"http" => Some(ServerProtocol::Http),
"git" => Some(ServerProtocol::Git),
_ => None,
};
} else if name == "user" {
user = Some(value.to_string());
}
}
let mut parts: Vec<&str> = url[8..]
.split('?')
.next()
.unwrap_or("")
.split('/')
.collect();
if protocol.is_none() {
let part = parts
.first()
.context(INCORRECT_NOSTR_URL_FORMAT_ERROR)?;
let protocol_str = if let Some(at_index) = part.find('@')
{
user = Some(part[..at_index].to_string());
&part[at_index + 1..]
} else {
part
};
protocol = match protocol_str {
"ssh" => Some(ServerProtocol::Ssh),
"https" => Some(ServerProtocol::Https),
"http" => Some(ServerProtocol::Http),
"git" => Some(ServerProtocol::Git),
_ => protocol,
};
if protocol.is_some() {
parts.remove(0);
}
}
let part = parts
.first()
.context(INCORRECT_NOSTR_URL_FORMAT_ERROR)?;
if let Ok(coordinate) = Coordinate::parse(part) {
if coordinate
.kind
.eq(&nostr_sdk::Kind::GitRepoAnnouncement)
{
coordinates.insert(coordinate);
} else {
bail!(
"naddr doesnt point to a git repository announcement"
);
}
} else if let Ok(public_key) = PublicKey::parse(part) {
parts.remove(0);
let identifier = parts
.pop()
.context("nostr url must have an identifier eg. nostr://npub123/repo-identifier")?
.to_string();
for relay in parts {
let mut decoded = urlencoding::decode(relay)
.context(
"could not parse relays in nostr git url",
)?
.to_string();
if !decoded.starts_with("ws://")
&& !decoded.starts_with("wss://")
{
decoded = format!("wss://{decoded}");
}
let url = Url::parse(&decoded).context(
"could not parse relays in nostr git url",
)?;
relays.push(url.to_string());
}
coordinates.insert(Coordinate {
identifier,
public_key,
kind: nostr_sdk::Kind::GitRepoAnnouncement,
relays,
});
} else {
bail!(INCORRECT_NOSTR_URL_FORMAT_ERROR);
}
Ok(Self {
original_string: url.to_string(),
coordinates,
protocol,
user,
})
}
}
#[derive(Debug, PartialEq, Default)]
pub struct CloneUrl {
original_string: String,
host: String,
path: String,
parameters: Option<String>,
protocol: ServerProtocol,
user: Option<String>,
port: Option<u16>,
fragment: Option<String>,
}
impl FromStr for CloneUrl {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
if s.starts_with('/')
|| s.starts_with("./")
|| s.starts_with("../")
{
return Ok(Self {
original_string: s.to_string(),
protocol: ServerProtocol::Filesystem,
..CloneUrl::default()
});
}
let url_str = if s.contains("://") {
s.to_string() } else {
let protocol = if s.contains('@') && s
.split('@')
.nth(0)
.map_or(false, |part| !part.contains('/')) {
"ssh"
}
else {
"unspecified"
};
format!(
"{protocol}://{}",
if contains_port(s) {
s.to_string()
} else {
s.replace(":/", "/").replace(':', "/")
}
)
};
let url =
Url::parse(&url_str).context("Failed to parse URL")?;
let protocol = match url.scheme() {
"ssh" => ServerProtocol::Ssh,
"https" => ServerProtocol::Https,
"http" => ServerProtocol::Http,
"git" => ServerProtocol::Git,
"ftp" => ServerProtocol::Ftp,
"unspecified" => ServerProtocol::Unspecified,
_ => {
return Err(anyhow::anyhow!(
"Unsupported protocol: {}",
url.scheme()
));
}
};
let host =
url.host_str().context("Missing host")?.to_string();
let path = url.path().to_string();
let parameters = url.query().map(|s| s.to_string());
let port = url.port();
let fragment = url.fragment().map(|s| s.to_string());
let user = if url.username().is_empty() {
None
} else {
Some(url.username().to_string())
};
Ok(CloneUrl {
original_string: s.to_string(),
host,
path,
parameters,
protocol,
user,
port,
fragment,
})
}
}
fn contains_port(s: &str) -> bool {
if let Some(after_host) =
s.split('@').nth(1).unwrap_or(s).split(':').nth(1)
{
if let Some(port) = after_host.split('/').next() {
if port.parse::<u16>().is_ok() {
return true;
}
}
}
false
}
impl CloneUrl {
pub fn format_as(
&self,
protocol: &ServerProtocol,
user: &Option<String>,
) -> Result<String> {
if *protocol == ServerProtocol::Filesystem {
if self.protocol == ServerProtocol::Filesystem {
return Ok(self.original_string.clone());
} else {
bail!(
"Cannot convert to Filesystem protocol from {:?}",
self.protocol
);
}
}
let mut url = Url::parse(&format!(
"{}{}",
match protocol {
ServerProtocol::Https => "https://",
ServerProtocol::UnauthHttps => "https://",
ServerProtocol::Http => "http://",
ServerProtocol::UnauthHttp => "http://",
ServerProtocol::Git => "git://",
ServerProtocol::Ftp => "ftp://",
ServerProtocol::Ssh => "ssh://",
ServerProtocol::Unspecified => "https://",
_ => bail!("unsupported protocol"),
},
&self.host
))
.context("Failed to parse base URL")?;
url.set_path(&self.path);
if let Some(port) = self.port {
url.set_port(Some(port))
.map_err(|_| anyhow!("cannot add port"))?;
}
if let Some(ref parameters) = self.parameters {
url.set_query(Some(parameters));
}
if let Some(ref fragment) = self.fragment {
url.set_fragment(Some(fragment));
}
let mut formatted_url = url.to_string();
if *protocol == ServerProtocol::Ssh {
formatted_url = formatted_url.replace(
"ssh://",
format!("{}@", user.as_deref().unwrap_or("git"))
.as_str(),
);
if !contains_port(&formatted_url) {
formatted_url = replace_first_occurrence(
&formatted_url,
'/',
':',
);
}
} else if *protocol == ServerProtocol::Unspecified {
formatted_url = formatted_url.replace("https://", "");
}
Ok(strip_trailing_slash(&formatted_url))
}
pub fn domain(&self) -> String {
self.host.to_string()
}
pub fn protocol(&self) -> ServerProtocol {
self.protocol.clone()
}
pub fn short_name(&self) -> String {
let domain = self.domain();
if domain.is_empty() {
self.original_string.to_string()
} else {
format!("{domain}{}", self.path)
}
}
}
fn replace_first_occurrence(
s: &str,
target: char,
replacement: char,
) -> String {
let mut result = s.to_string();
if let Some(index) = result.find(target) {
result.replace_range(
index..index + 1,
&replacement.to_string(),
);
}
result
}
fn strip_trailing_slash(s: &str) -> String {
s.strip_suffix('/').unwrap_or(s).to_string()
}
pub fn convert_clone_url_to_https(url: &str) -> Result<String> {
let stripped_url = strip_credentials(url);
if stripped_url.starts_with("https://") {
return Ok(stripped_url);
}
else if stripped_url.starts_with("http://") {
return Ok(stripped_url.replace("http://", "https://"));
}
else if stripped_url.starts_with("ssh://") {
let parts: Vec<&str> = stripped_url
.trim_start_matches("ssh://")
.split('/')
.collect();
if parts.len() >= 2 {
return Ok(format!(
"https://{}/{}",
parts[0],
parts[1..].join("/")
));
}
bail!("Invalid SSH URL format: {}", url);
}
else if stripped_url.starts_with("ftp://") {
return Ok(stripped_url.replace("ftp://", "https://"));
}
else if stripped_url.starts_with("git://") {
return Ok(stripped_url.replace("git://", "https://"));
}
bail!("Unsupported URL protocol: {}", url);
}
fn strip_credentials(url: &str) -> String {
if let Some(pos) = url.find("://") {
let (protocol, rest) = url.split_at(pos + 3); let rest_parts: Vec<&str> = rest.split('@').collect();
if rest_parts.len() > 1 {
return format!("{}{}", protocol, rest_parts[1]);
}
} else if let Some(at_pos) = url.find('@') {
let (_, rest) = url.split_at(at_pos);
let host_and_repo = &rest[1..]; return format!("ssh://{}", host_and_repo.replace(':', "/"));
}
url.to_string() }
#[cfg(test)]
mod tests {
use super::*;
mod clone_url_from_str_format_as {
use super::*;
mod when_user_specified {
use super::*;
mod but_not_in_original_url {
use super::*;
#[test]
fn https_to_https_ignores_user() {
let result = "https://github.com/user/repo.git"
.parse::<CloneUrl>()
.unwrap()
.format_as(
&ServerProtocol::Https,
&Some("user1".to_string()),
)
.unwrap();
assert_eq!(
result,
"https://github.com/user/repo.git"
);
}
#[test]
fn https_to_ssh_uses_specified_user() {
let result = "https://github.com/user/repo.git"
.parse::<CloneUrl>()
.unwrap()
.format_as(
&ServerProtocol::Ssh,
&Some("user1".to_string()),
)
.unwrap();
assert_eq!(
result,
"user1@github.com:user/repo.git"
);
}
}
mod and_a_different_user_in_original_url {
use super::*;
#[test]
fn ssh_uses_specified_user() {
let result = "user2@github.com/user/repo.git"
.parse::<CloneUrl>()
.unwrap()
.format_as(
&ServerProtocol::Ssh,
&Some("user1".to_string()),
)
.unwrap();
assert_eq!(
result,
"user1@github.com:user/repo.git"
);
}
}
}
#[test]
fn format_as_ssh_defaults_to_git_user() {
let result = "https://github.com/user/repo.git"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Ssh, &None)
.unwrap();
assert_eq!(result, "git@github.com:user/repo.git");
}
#[test]
fn format_as_ssh_includes_port() {
let result = "https://github.com:1000/user/repo.git"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Ssh, &None)
.unwrap();
assert_eq!(result, "git@github.com:1000/user/repo.git");
}
#[test]
fn format_as_unspecified_ommits_prefix() {
let result = "https://github.com/user/repo.git"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Unspecified, &None)
.unwrap();
assert_eq!(result, "github.com/user/repo.git");
}
mod input_all_formats_to_from_str_and_correctly_format_as_https {
use super::*;
#[test]
fn test_https_url() {
let result = "https://github.com/user/repo.git"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Https, &None)
.unwrap();
assert_eq!(
result,
"https://github.com/user/repo.git"
);
}
mod with_unspecified_and_additional_features {
use super::*;
#[test]
fn port() {
let result = "github.com:1000/user/repo.git"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Https, &None)
.unwrap();
assert_eq!(
result,
"https://github.com:1000/user/repo.git"
);
}
#[test]
fn colon() {
let result = "github.com:user/repo.git"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Https, &None)
.unwrap();
assert_eq!(
result,
"https://github.com/user/repo.git"
);
}
#[test]
fn path_with_fragment() {
let result = "github.com/user/repo.git#readme"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Https, &None)
.unwrap();
assert_eq!(
result,
"https://github.com/user/repo.git#readme"
);
}
#[test]
fn path_with_parameters() {
let result = "github.com/user/repo.git?ref=main"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Https, &None)
.unwrap();
assert_eq!(
result,
"https://github.com/user/repo.git?ref=main"
);
}
#[test]
fn port_with_parameters_and_fragment() {
let result = "github.com:2222/repo.git?version=1.0#section1"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Https, &None)
.unwrap();
assert_eq!(
result,
"https://github.com:2222/repo.git?version=1.0#section1"
);
}
}
mod with_https_and_additional_features {
use super::*;
#[test]
fn credentials_and_they_are_stripped() {
let result = "https://username:password@github.com/user/repo.git"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Https, &None)
.unwrap();
assert_eq!(
result,
"https://github.com/user/repo.git"
);
}
#[test]
fn port() {
let result =
"https://github.com:1000/user/repo.git"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Https, &None)
.unwrap();
assert_eq!(
result,
"https://github.com:1000/user/repo.git"
);
}
#[test]
fn path_with_fragment() {
let result =
"https://github.com/user/repo.git#readme"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Https, &None)
.unwrap();
assert_eq!(
result,
"https://github.com/user/repo.git#readme"
);
}
#[test]
fn path_with_parameters() {
let result =
"https://github.com/user/repo.git?ref=main"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Https, &None)
.unwrap();
assert_eq!(
result,
"https://github.com/user/repo.git?ref=main"
);
}
#[test]
fn port_with_parameters_and_fragment() {
let result = "https://github.com:2222/repo.git?version=1.0#section1"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Https, &None)
.unwrap();
assert_eq!(
result,
"https://github.com:2222/repo.git?version=1.0#section1"
);
}
}
#[test]
fn test_http_url() {
let result = "http://github.com/user/repo.git"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Https, &None)
.unwrap();
assert_eq!(
result,
"https://github.com/user/repo.git"
);
}
mod ssh_input {
use super::*;
#[test]
fn test_git_at_url() {
let result = "git@github.com:user/repo.git"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Https, &None)
.unwrap();
assert_eq!(
result,
"https://github.com/user/repo.git"
);
}
#[test]
fn test_user_at_url() {
let result = "user1@github.com:user/repo.git"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Https, &None)
.unwrap();
assert_eq!(
result,
"https://github.com/user/repo.git"
);
}
#[test]
fn path_has_colon_slash_prefix() {
let result = "user1@github.com:/user/repo.git"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Https, &None)
.unwrap();
assert_eq!(
result,
"https://github.com/user/repo.git"
);
}
#[test]
fn port_specified_with_path() {
let result = "user@github.com:2222/repo.git"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Https, &None)
.unwrap();
assert_eq!(
result,
"https://github.com:2222/repo.git"
);
}
#[test]
fn port_specified_without_path() {
let result = "user@github.com:2222"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Https, &None)
.unwrap();
assert_eq!(result, "https://github.com:2222");
}
#[test]
fn path_with_fragment() {
let result =
"user1@github.com:/user/repo.git#readme"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Https, &None)
.unwrap();
assert_eq!(
result,
"https://github.com/user/repo.git#readme"
);
}
#[test]
fn path_with_parameters() {
let result =
"user@github.com:/user/repo.git?ref=main"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Https, &None)
.unwrap();
assert_eq!(
result,
"https://github.com/user/repo.git?ref=main"
);
}
#[test]
fn port_with_parameters_and_fragment() {
let result = "user@github.com:2222/repo.git?version=1.0#section1"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Https, &None)
.unwrap();
assert_eq!(
result,
"https://github.com:2222/repo.git?version=1.0#section1"
);
}
}
#[test]
fn test_ftp_url() {
let result = "ftp://example.com/repo.git"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Https, &None)
.unwrap();
assert_eq!(result, "https://example.com/repo.git");
}
#[test]
fn test_git_protocol_url() {
let result = "git://example.com/repo.git"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Https, &None)
.unwrap();
assert_eq!(result, "https://example.com/repo.git");
}
#[test]
fn test_invalid_url() {
let clone_url_result =
"unsupported://example.com/repo.git"
.parse::<CloneUrl>();
assert!(clone_url_result.is_err());
}
mod local_addresses_should_return_error {
use super::*;
#[test]
fn test_absolute_local_path() {
let result = "/path/to/repo.git"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Https, &None);
assert!(result.is_err()); }
#[test]
fn test_relative_local_path() {
let result = "./path/to/repo.git"
.parse::<CloneUrl>()
.unwrap()
.format_as(&ServerProtocol::Https, &None);
assert!(result.is_err()); }
}
}
}
mod convert_clone_url_to_https {
use super::*;
#[test]
fn test_https_url() {
let url = "https://github.com/user/repo.git";
let result = convert_clone_url_to_https(url).unwrap();
assert_eq!(result, "https://github.com/user/repo.git");
}
#[test]
fn test_http_url() {
let url = "http://github.com/user/repo.git";
let result = convert_clone_url_to_https(url).unwrap();
assert_eq!(result, "https://github.com/user/repo.git");
}
#[test]
fn test_http_url_with_credentials() {
let url =
"http://username:password@github.com/user/repo.git";
let result = convert_clone_url_to_https(url).unwrap();
assert_eq!(result, "https://github.com/user/repo.git");
}
#[test]
fn test_git_at_url() {
let url = "git@github.com:user/repo.git";
let result = convert_clone_url_to_https(url).unwrap();
assert_eq!(result, "https://github.com/user/repo.git");
}
#[test]
fn test_user_at_url() {
let url = "user1@github.com:user/repo.git";
let result = convert_clone_url_to_https(url).unwrap();
assert_eq!(result, "https://github.com/user/repo.git");
}
#[test]
fn test_ssh_url() {
let url = "ssh://github.com/user/repo.git";
let result = convert_clone_url_to_https(url).unwrap();
assert_eq!(result, "https://github.com/user/repo.git");
}
#[test]
fn test_ftp_url() {
let url = "ftp://example.com/repo.git";
let result = convert_clone_url_to_https(url).unwrap();
assert_eq!(result, "https://example.com/repo.git");
}
#[test]
fn test_git_protocol_url() {
let url = "git://example.com/repo.git";
let result = convert_clone_url_to_https(url).unwrap();
assert_eq!(result, "https://example.com/repo.git");
}
#[test]
fn test_invalid_url() {
let url = "unsupported://example.com/repo.git";
let result = convert_clone_url_to_https(url);
assert!(result.is_err());
}
}
mod nostr_git_url_paramemters_from_str {
use std::str::FromStr;
use super::*;
fn get_model_coordinate(relays: bool) -> Coordinate {
Coordinate {
identifier: "ngit".to_string(),
public_key: PublicKey::parse(
"npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr",
)
.unwrap(),
kind: nostr_sdk::Kind::GitRepoAnnouncement,
relays: if relays {
vec!["wss://nos.lol/".to_string()]
} else {
vec![]
},
}
}
#[test]
fn from_naddr() -> Result<()> {
let url = "nostr://naddr1qqzxuemfwsqs6amnwvaz7tmwdaejumr0dspzpgqgmmc409hm4xsdd74sf68a2uyf9pwel4g9mfdg8l5244t6x4jdqvzqqqrhnym0k2qj".to_string();
assert_eq!(
NostrUrlDecoded::from_str(&url)?,
NostrUrlDecoded {
original_string: url.clone(),
coordinates: HashSet::from([Coordinate {
identifier: "ngit".to_string(),
public_key: PublicKey::parse(
"npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr",
)
.unwrap(),
kind: nostr_sdk::Kind::GitRepoAnnouncement,
relays: vec!["wss://nos.lol".to_string()], }]),
protocol: None,
user: None,
},
);
Ok(())
}
mod from_npub_slash_identifier {
use super::*;
#[test]
fn without_relay() -> Result<()> {
let url =
"nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit"
.to_string();
assert_eq!(
NostrUrlDecoded::from_str(&url)?,
NostrUrlDecoded {
original_string: url.clone(),
coordinates: HashSet::from([
get_model_coordinate(false)
]),
protocol: None,
user: None,
},
);
Ok(())
}
mod with_url_parameters {
use super::*;
#[test]
fn with_relay_without_scheme_defaults_to_wss()
-> Result<()> {
let url = "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit?relay=nos.lol".to_string();
assert_eq!(
NostrUrlDecoded::from_str(&url)?,
NostrUrlDecoded {
original_string: url.clone(),
coordinates: HashSet::from([
get_model_coordinate(true)
]),
protocol: None,
user: None,
},
);
Ok(())
}
#[test]
fn with_encoded_relay() -> Result<()> {
let url = format!(
"nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit?relay={}",
urlencoding::encode("wss://nos.lol")
);
assert_eq!(
NostrUrlDecoded::from_str(&url)?,
NostrUrlDecoded {
original_string: url.clone(),
coordinates: HashSet::from([
get_model_coordinate(true)
]),
protocol: None,
user: None,
},
);
Ok(())
}
#[test]
fn with_multiple_encoded_relays() -> Result<()> {
let url = format!(
"nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit?relay={}&relay1={}",
urlencoding::encode("wss://nos.lol"),
urlencoding::encode("wss://relay.damus.io"),
);
assert_eq!(
NostrUrlDecoded::from_str(&url)?,
NostrUrlDecoded {
original_string: url.clone(),
coordinates: HashSet::from([Coordinate {
identifier: "ngit".to_string(),
public_key: PublicKey::parse(
"npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr",
)
.unwrap(),
kind: nostr_sdk::Kind::GitRepoAnnouncement,
relays: vec![
"wss://nos.lol/".to_string(),
"wss://relay.damus.io/".to_string(),
],
}]),
protocol: None,
user: None,
},
);
Ok(())
}
#[test]
fn with_server_protocol() -> Result<()> {
let url = "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit?protocol=ssh".to_string();
assert_eq!(
NostrUrlDecoded::from_str(&url)?,
NostrUrlDecoded {
original_string: url.clone(),
coordinates: HashSet::from([
get_model_coordinate(false)
]),
protocol: Some(ServerProtocol::Ssh),
user: None,
},
);
Ok(())
}
#[test]
fn with_server_protocol_and_user() -> Result<()> {
let url = "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit?protocol=ssh&user=fred".to_string();
assert_eq!(
NostrUrlDecoded::from_str(&url)?,
NostrUrlDecoded {
original_string: url.clone(),
coordinates: HashSet::from([
get_model_coordinate(false)
]),
protocol: Some(ServerProtocol::Ssh),
user: Some("fred".to_string()),
},
);
Ok(())
}
}
mod with_parameters_embedded_with_slashes {
use super::*;
#[test]
fn with_relay_without_scheme_defaults_to_wss()
-> Result<()> {
let url = "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/nos.lol/ngit".to_string();
assert_eq!(
NostrUrlDecoded::from_str(&url)?,
NostrUrlDecoded {
original_string: url.clone(),
coordinates: HashSet::from([
get_model_coordinate(true)
]),
protocol: None,
user: None,
},
);
Ok(())
}
#[test]
fn with_encoded_relay() -> Result<()> {
let url = format!(
"nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/{}/ngit",
urlencoding::encode("wss://nos.lol")
);
assert_eq!(
NostrUrlDecoded::from_str(&url)?,
NostrUrlDecoded {
original_string: url.clone(),
coordinates: HashSet::from([
get_model_coordinate(true)
]),
protocol: None,
user: None,
},
);
Ok(())
}
#[test]
fn with_multiple_encoded_relays() -> Result<()> {
let url = format!(
"nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/{}/{}/ngit",
urlencoding::encode("wss://nos.lol"),
urlencoding::encode("wss://relay.damus.io"),
);
assert_eq!(
NostrUrlDecoded::from_str(&url)?,
NostrUrlDecoded {
original_string: url.clone(),
coordinates: HashSet::from([Coordinate {
identifier: "ngit".to_string(),
public_key: PublicKey::parse(
"npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr",
)
.unwrap(),
kind: nostr_sdk::Kind::GitRepoAnnouncement,
relays: vec![
"wss://nos.lol/".to_string(),
"wss://relay.damus.io/".to_string(),
],
}]),
protocol: None,
user: None,
},
);
Ok(())
}
#[test]
fn with_server_protocol() -> Result<()> {
let url = "nostr://ssh/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit".to_string();
assert_eq!(
NostrUrlDecoded::from_str(&url)?,
NostrUrlDecoded {
original_string: url.clone(),
coordinates: HashSet::from([
get_model_coordinate(false)
]),
protocol: Some(ServerProtocol::Ssh),
user: None,
},
);
Ok(())
}
#[test]
fn with_server_protocol_and_user() -> Result<()> {
let url = "nostr://fred@ssh/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit".to_string();
assert_eq!(
NostrUrlDecoded::from_str(&url)?,
NostrUrlDecoded {
original_string: url.clone(),
coordinates: HashSet::from([
get_model_coordinate(false)
]),
protocol: Some(ServerProtocol::Ssh),
user: Some("fred".to_string()),
},
);
Ok(())
}
}
}
}
}