Create `url` module shared between crates
Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
parent
617e0c1877
commit
657a951df0
|
|
@ -1,6 +1,5 @@
|
|||
#![allow(clippy::collapsible_if)]
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::{env, io, process};
|
||||
|
||||
use thiserror::Error;
|
||||
|
|
@ -8,6 +7,7 @@ use thiserror::Error;
|
|||
use radicle::crypto::{PublicKey, Signer};
|
||||
use radicle::node::Handle;
|
||||
use radicle::ssh;
|
||||
use radicle::storage::git::transport::{Url, UrlError};
|
||||
use radicle::storage::{ReadRepository, WriteRepository, WriteStorage};
|
||||
|
||||
/// The service invoked by git on the remote repository, during a push.
|
||||
|
|
@ -35,78 +35,6 @@ pub enum Error {
|
|||
RemoteUrl(#[from] UrlError),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum UrlError {
|
||||
/// Failed to parse.
|
||||
#[error(transparent)]
|
||||
Parse(#[from] radicle::git::url::parse::Error),
|
||||
/// Unsupported URL scheme.
|
||||
#[error("{0}: unsupported scheme: expected `rad://`")]
|
||||
UnsupportedScheme(radicle::git::Url),
|
||||
/// Missing host.
|
||||
#[error("{0}: missing id")]
|
||||
MissingId(radicle::git::Url),
|
||||
/// Invalid remote repository identifier.
|
||||
#[error("{0}: id: {1}")]
|
||||
InvalidId(radicle::git::Url, radicle::identity::IdError),
|
||||
/// Invalid public key.
|
||||
#[error("{0}: key: {1}")]
|
||||
InvalidKey(radicle::git::Url, radicle::crypto::PublicKeyError),
|
||||
}
|
||||
|
||||
/// A git remote URL.
|
||||
///
|
||||
/// `rad://<id>/[<pubkey>]`
|
||||
///
|
||||
/// Eg. `rad://zUBDc1UdoEzbpaGcNXqauQkERJ8r` without the public key,
|
||||
/// and `rad://zUBDc1UdoEzbpaGcNXqauQkERJ8r/zCQTxdZGCzQXWBV3XbY3fgkHM3gfkLGyYMd2nL5R2MxQv` with.
|
||||
///
|
||||
#[derive(Debug)]
|
||||
pub struct Url {
|
||||
pub id: radicle::identity::Id,
|
||||
pub public_key: Option<PublicKey>,
|
||||
}
|
||||
|
||||
impl FromStr for Url {
|
||||
type Err = UrlError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let url: radicle::git::Url = s.as_bytes().try_into()?;
|
||||
Url::try_from(url)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<radicle::git::Url> for Url {
|
||||
type Error = UrlError;
|
||||
|
||||
fn try_from(url: radicle::git::Url) -> Result<Self, Self::Error> {
|
||||
if url.scheme != radicle::git::url::Scheme::Radicle {
|
||||
return Err(Self::Error::UnsupportedScheme(url));
|
||||
}
|
||||
|
||||
let id: radicle::identity::Id = url
|
||||
.host
|
||||
.as_ref()
|
||||
.ok_or_else(|| Self::Error::MissingId(url.clone()))?
|
||||
.parse()
|
||||
.map_err(|e| Self::Error::InvalidId(url.clone(), e))?;
|
||||
|
||||
let public_key: Option<PublicKey> = if url.path.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
let path = url.path.to_string();
|
||||
|
||||
path.strip_prefix('/')
|
||||
.unwrap_or(&path)
|
||||
.parse()
|
||||
.map(Some)
|
||||
.map_err(|e| Self::Error::InvalidKey(url.clone(), e))
|
||||
}?;
|
||||
|
||||
Ok(Url { id, public_key })
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the radicle remote helper using the given profile.
|
||||
pub fn run(profile: radicle::Profile) -> Result<(), Box<dyn std::error::Error + 'static>> {
|
||||
// `GIT_DIR` is expected to be set, though we aren't using it right now.
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@
|
|||
//!
|
||||
//! This module is meant to be used by first registering our transport with [`register`] and then
|
||||
//! adding or removing streams through [`Smart`], which can be obtained via [`Smart::singleton`].
|
||||
mod url;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic;
|
||||
|
|
@ -25,6 +27,8 @@ use once_cell::sync::Lazy;
|
|||
use crate::git;
|
||||
use crate::identity::Id;
|
||||
|
||||
pub use url::{Url, UrlError};
|
||||
|
||||
/// The map of git smart sub-transport streams. We keep a global map because we have
|
||||
/// no control over how [`git2::transport::register`] instantiates our [`Smart`] transport
|
||||
/// or its underlying streams.
|
||||
|
|
@ -75,16 +79,9 @@ impl git2::transport::SmartSubtransport for Smart {
|
|||
url: &str,
|
||||
action: git2::transport::Service,
|
||||
) -> Result<Box<dyn git2::transport::SmartSubtransportStream>, git2::Error> {
|
||||
let url = git::Url::from_bytes(url.as_bytes())
|
||||
.map_err(|e| git2::Error::from_str(e.to_string().as_str()))?;
|
||||
let id = Id::from_str(url.host.unwrap_or_default().as_str())
|
||||
.map_err(|_| git2::Error::from_str("Git URL does not contain a valid project id"))?;
|
||||
let url = Url::from_str(url).map_err(|e| git2::Error::from_str(e.to_string().as_str()))?;
|
||||
|
||||
if url.scheme != git::url::Scheme::Radicle {
|
||||
return Err(git2::Error::from_str("Git URL scheme must be `rad`"));
|
||||
}
|
||||
|
||||
if let Some(stream) = self.take(&id) {
|
||||
if let Some(stream) = self.take(&url.id) {
|
||||
match action {
|
||||
git2::transport::Service::UploadPackLs => {}
|
||||
git2::transport::Service::UploadPack => {}
|
||||
|
|
@ -102,7 +99,8 @@ impl git2::transport::SmartSubtransport for Smart {
|
|||
Ok(stream)
|
||||
} else {
|
||||
Err(git2::Error::from_str(&format!(
|
||||
"repository {id} does not have an associated stream"
|
||||
"repository {} does not have an associated stream",
|
||||
url.id
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
//! Git transport URLs.
|
||||
use std::str::FromStr;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::crypto::PublicKey;
|
||||
use crate::{crypto, git, identity};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum UrlError {
|
||||
/// Failed to parse.
|
||||
#[error(transparent)]
|
||||
Parse(#[from] git::url::parse::Error),
|
||||
/// Unsupported URL scheme.
|
||||
#[error("{0}: unsupported scheme: expected `rad://`")]
|
||||
UnsupportedScheme(git::Url),
|
||||
/// Missing host.
|
||||
#[error("{0}: missing id")]
|
||||
MissingId(git::Url),
|
||||
/// Invalid remote repository identifier.
|
||||
#[error("{0}: id: {1}")]
|
||||
InvalidId(git::Url, identity::IdError),
|
||||
/// Invalid public key.
|
||||
#[error("{0}: key: {1}")]
|
||||
InvalidKey(git::Url, crypto::PublicKeyError),
|
||||
}
|
||||
|
||||
/// A git remote URL.
|
||||
///
|
||||
/// `rad://<id>/[<pubkey>]`
|
||||
///
|
||||
/// Eg. `rad://zUBDc1UdoEzbpaGcNXqauQkERJ8r` without the public key,
|
||||
/// and `rad://zUBDc1UdoEzbpaGcNXqauQkERJ8r/zCQTxdZGCzQXWBV3XbY3fgkHM3gfkLGyYMd2nL5R2MxQv` with.
|
||||
///
|
||||
#[derive(Debug)]
|
||||
pub struct Url {
|
||||
pub id: identity::Id,
|
||||
pub public_key: Option<PublicKey>,
|
||||
}
|
||||
|
||||
impl FromStr for Url {
|
||||
type Err = UrlError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let url: git::Url = s.as_bytes().try_into()?;
|
||||
Url::try_from(url)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<git::Url> for Url {
|
||||
type Error = UrlError;
|
||||
|
||||
fn try_from(url: git::Url) -> Result<Self, Self::Error> {
|
||||
if url.scheme != git::url::Scheme::Radicle {
|
||||
return Err(Self::Error::UnsupportedScheme(url));
|
||||
}
|
||||
|
||||
let id: identity::Id = url
|
||||
.host
|
||||
.as_ref()
|
||||
.ok_or_else(|| Self::Error::MissingId(url.clone()))?
|
||||
.parse()
|
||||
.map_err(|e| Self::Error::InvalidId(url.clone(), e))?;
|
||||
|
||||
let public_key: Option<PublicKey> = if url.path.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
let path = url.path.to_string();
|
||||
|
||||
path.strip_prefix('/')
|
||||
.unwrap_or(&path)
|
||||
.parse()
|
||||
.map(Some)
|
||||
.map_err(|e| Self::Error::InvalidKey(url.clone(), e))
|
||||
}?;
|
||||
|
||||
Ok(Url { id, public_key })
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue