Create `url` module shared between crates

Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
Alexis Sellier 2022-10-19 14:30:01 +02:00
parent 617e0c1877
commit 657a951df0
No known key found for this signature in database
3 changed files with 88 additions and 83 deletions

View File

@ -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.

View File

@ -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
)))
}
}

View File

@ -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 })
}
}