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)]
|
#![allow(clippy::collapsible_if)]
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
|
||||||
use std::{env, io, process};
|
use std::{env, io, process};
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
@ -8,6 +7,7 @@ use thiserror::Error;
|
||||||
use radicle::crypto::{PublicKey, Signer};
|
use radicle::crypto::{PublicKey, Signer};
|
||||||
use radicle::node::Handle;
|
use radicle::node::Handle;
|
||||||
use radicle::ssh;
|
use radicle::ssh;
|
||||||
|
use radicle::storage::git::transport::{Url, UrlError};
|
||||||
use radicle::storage::{ReadRepository, WriteRepository, WriteStorage};
|
use radicle::storage::{ReadRepository, WriteRepository, WriteStorage};
|
||||||
|
|
||||||
/// The service invoked by git on the remote repository, during a push.
|
/// The service invoked by git on the remote repository, during a push.
|
||||||
|
|
@ -35,78 +35,6 @@ pub enum Error {
|
||||||
RemoteUrl(#[from] UrlError),
|
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.
|
/// Run the radicle remote helper using the given profile.
|
||||||
pub fn run(profile: radicle::Profile) -> Result<(), Box<dyn std::error::Error + 'static>> {
|
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.
|
// `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
|
//! 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`].
|
//! adding or removing streams through [`Smart`], which can be obtained via [`Smart::singleton`].
|
||||||
|
mod url;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::atomic;
|
use std::sync::atomic;
|
||||||
|
|
@ -25,6 +27,8 @@ use once_cell::sync::Lazy;
|
||||||
use crate::git;
|
use crate::git;
|
||||||
use crate::identity::Id;
|
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
|
/// 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
|
/// no control over how [`git2::transport::register`] instantiates our [`Smart`] transport
|
||||||
/// or its underlying streams.
|
/// or its underlying streams.
|
||||||
|
|
@ -75,16 +79,9 @@ impl git2::transport::SmartSubtransport for Smart {
|
||||||
url: &str,
|
url: &str,
|
||||||
action: git2::transport::Service,
|
action: git2::transport::Service,
|
||||||
) -> Result<Box<dyn git2::transport::SmartSubtransportStream>, git2::Error> {
|
) -> Result<Box<dyn git2::transport::SmartSubtransportStream>, git2::Error> {
|
||||||
let url = git::Url::from_bytes(url.as_bytes())
|
let url = Url::from_str(url).map_err(|e| git2::Error::from_str(e.to_string().as_str()))?;
|
||||||
.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"))?;
|
|
||||||
|
|
||||||
if url.scheme != git::url::Scheme::Radicle {
|
if let Some(stream) = self.take(&url.id) {
|
||||||
return Err(git2::Error::from_str("Git URL scheme must be `rad`"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(stream) = self.take(&id) {
|
|
||||||
match action {
|
match action {
|
||||||
git2::transport::Service::UploadPackLs => {}
|
git2::transport::Service::UploadPackLs => {}
|
||||||
git2::transport::Service::UploadPack => {}
|
git2::transport::Service::UploadPack => {}
|
||||||
|
|
@ -102,7 +99,8 @@ impl git2::transport::SmartSubtransport for Smart {
|
||||||
Ok(stream)
|
Ok(stream)
|
||||||
} else {
|
} else {
|
||||||
Err(git2::Error::from_str(&format!(
|
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