use nostr::prelude::Url; use nostr_sdk::prelude::*; /// `ws://` grasp servers, like ngit, use `http(s)://`. pub(crate) fn grasp_base_url(relay: &RelayUrl) -> Option { let parsed = Url::parse(relay.as_str()).ok()?; let host = parsed.host_str()?; let port = parsed .port() .map(|port| format!(":{port}")) .unwrap_or_default(); let scheme = if relay.scheme().is_secure() { "https" } else { "http" }; Some(format!("{scheme}://{host}{port}")) } pub(crate) fn grasp_clone_url(relay: &RelayUrl, owner: &str, repo_id: &str) -> Option { let base = grasp_base_url(relay)?; Url::parse(&format!("{base}/{owner}/{repo_id}.git")).ok() } /// GRASP-06 contributor namespace URL of a pull request tip. pub(crate) fn grasp06_prs_url(base_url: &str, npub: &str, repo_id: &str) -> String { format!("{base_url}/prs/{npub}/{repo_id}.git") } /// The author's GRASP-06 `/prs/` URLs come first. pub(crate) fn pr_clone_urls(prs_urls: Vec, base_clone_urls: Vec) -> Vec { let mut seen = std::collections::HashSet::new(); let mut urls = Vec::new(); for url in prs_urls.into_iter().chain(base_clone_urls) { if seen.insert(url.to_string()) { urls.push(url); } } urls } #[derive(Debug, Clone)] pub struct GraspServer { relay: RelayUrl, reason: Option, } impl GraspServer { pub(crate) fn ok(relay: RelayUrl) -> Self { Self { relay, reason: None, } } pub(crate) fn failed(relay: &RelayUrl, reason: impl Into) -> Self { Self { relay: relay.to_owned(), reason: Some(reason.into()), } } pub fn relay(&self) -> &RelayUrl { &self.relay } pub fn reason(&self) -> Option<&str> { self.reason.as_deref() } } #[derive(Debug, Clone, Default)] pub struct PushOutcome { pub servers: Vec, /// The newest state event a grasp relay accepted for this push. pub state_event: Option, } impl PushOutcome { pub fn accepted(&self) -> usize { self.servers .iter() .filter(|server| server.reason.is_none()) .count() } fn failing(&self) -> impl Iterator { self.servers.iter().filter(|server| server.reason.is_some()) } pub fn failure_summary(&self) -> String { self.failing() .map(|server| { let reason = utils::flatten_whitespace(server.reason.as_deref().unwrap_or("unknown error")); format!("{}: {reason}", server.relay) }) .collect::>() .join("; ") } /// `None` when every server accepted the push or nothing was pushed. pub fn partial_warning(&self) -> Option { let accepted = self.accepted(); if self.servers.is_empty() || accepted == self.servers.len() { return None; } Some(format!( "Pushed to {accepted} of {} grasp servers: {}. Republish to sync.", self.servers.len(), self.failure_summary() )) } }