Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 39 additions & 2 deletions gitoid/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use hex::FromHexError as HexError;
use std::error::Error as StdError;
use std::fmt::{self, Display, Formatter};
use std::io::Error as IoError;
use url::ParseError as UrlError;
use url::{ParseError as UrlError, Url};

/// A `Result` with `gitoid::Error` as the error type.
pub(crate) type Result<T> = std::result::Result<T, Error>;
Expand All @@ -12,6 +13,20 @@ pub enum Error {
/// The expected and actual length of the data being read didn't
/// match, indicating something has likely gone wrong.
BadLength { expected: usize, actual: usize },
/// Tried to construct a `GitOid` from a `Url` with a scheme besides `gitoid`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate all these error variations!

InvalidScheme(Url),
/// Tried to construct a `GitOid` from a `Url` without an `ObjectType` in it.
MissingObjectType(Url),
/// Tried to construct a `GitOid` from a `Url` without a `HashAlgorithm` in it.
MissingHashAlgorithm(Url),
/// Tried to construct a `GitOid` from a `Url` without a hash in it.
MissingHash(Url),
/// Tried to parse an unknown object type.
UnknownObjectType(String),
/// Tried to parse an unknown hash algorithm.
UnknownHashAlgorithm(String),
/// Tried to parse an invalid hex string.
InvalidHex(HexError),
/// Could not construct a valid URL based on the `GitOid` data.
Url(UrlError),
/// Could not perform the IO operations necessary to construct the `GitOid`.
Expand All @@ -24,6 +39,15 @@ impl Display for Error {
Error::BadLength { expected, actual } => {
write!(f, "expected length {}, actual length {}", expected, actual)
}
Error::InvalidScheme(url) => write!(f, "invalid scheme in URL '{}'", url.scheme()),
Error::MissingObjectType(url) => write!(f, "missing object type in URL '{}'", url),
Error::MissingHashAlgorithm(url) => {
write!(f, "missing hash algorithm in URL '{}'", url)
}
Error::MissingHash(url) => write!(f, "missing hash in URL '{}'", url),
Error::UnknownObjectType(s) => write!(f, "unknown object type '{}'", s),
Error::UnknownHashAlgorithm(s) => write!(f, "unknown hash algorithm '{}'", s),
Error::InvalidHex(_) => write!(f, "invalid hex string"),
Error::Url(e) => write!(f, "{}", e),
Error::Io(e) => write!(f, "{}", e),
}
Expand All @@ -33,13 +57,26 @@ impl Display for Error {
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
Error::BadLength { .. } => None,
Error::BadLength { .. }
| Error::InvalidScheme(_)
| Error::MissingObjectType(_)
| Error::MissingHashAlgorithm(_)
| Error::MissingHash(_)
| Error::UnknownObjectType(_)
| Error::UnknownHashAlgorithm(_) => None,
Error::InvalidHex(e) => Some(e),
Error::Url(e) => Some(e),
Error::Io(e) => Some(e),
}
}
}

impl From<HexError> for Error {
fn from(e: HexError) -> Error {
Error::InvalidHex(e)
}
}

impl From<UrlError> for Error {
fn from(e: UrlError) -> Error {
Error::Url(e)
Expand Down
61 changes: 60 additions & 1 deletion gitoid/src/gitoid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use core::marker::Unpin;
use sha2::digest::DynDigest;
use std::hash::Hash;
use std::io::{BufReader, Read};
use std::ops::Not as _;
use std::str::FromStr;
use tokio::io::AsyncReadExt;
use url::Url;

Expand Down Expand Up @@ -114,12 +116,17 @@ impl GitOid {
})
}

/// Construct a new `GitOid` from a `Url`.
pub fn new_from_url(url: Url) -> Result<GitOid> {
url.try_into()
}

//===========================================================================================
// Getters
//-------------------------------------------------------------------------------------------

/// Get a URL for the current `GitOid`.
pub fn uri(&self) -> Result<Url> {
pub fn url(&self) -> Result<Url> {
let s = format!(
"gitoid:{}:{}:{}",
self.object_type,
Expand All @@ -145,6 +152,58 @@ impl GitOid {
}
}

impl TryFrom<Url> for GitOid {
type Error = Error;

fn try_from(url: Url) -> Result<GitOid> {
use Error::*;

// Validate the scheme used.
if url.scheme() != "gitoid" {
return Err(InvalidScheme(url));
}

// Get the segments as an iterator over string slices.
let mut segments = url.path().split(':');

// Parse the object type, if present.
let object_type = {
let part = segments
.next()
.and_then(|p| p.is_empty().not().then_some(p))
.ok_or_else(|| MissingObjectType(url.clone()))?;

ObjectType::from_str(part)?
};

// Parse the hash algorithm, if present.
let hash_algorithm = {
let part = segments
.next()
.and_then(|p| p.is_empty().not().then_some(p))
.ok_or_else(|| MissingHashAlgorithm(url.clone()))?;

HashAlgorithm::from_str(part)?
};

// Parse the hash, if present.
let hex_str = segments
.next()
.and_then(|p| p.is_empty().not().then_some(p))
.ok_or_else(|| MissingHash(url.clone()))?;
let mut value = [0u8; 32];
hex::decode_to_slice(hex_str, &mut value)?;

// Construct a new `GitOid` from the parts.
Ok(GitOid {
hash_algorithm,
object_type,
len: value.len(),
value,
})
}
}

//===============================================================================================
// Helpers
//-----------------------------------------------------------------------------------------------
Expand Down
18 changes: 16 additions & 2 deletions gitoid/src/hash_algorithm.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
//! A hash algorithm which can be used to make a `GitOid`.

use core::fmt::{Display, Formatter, Result};
use crate::{Error, Result};
use core::fmt::{self, Display, Formatter};
use sha1::Sha1;
use sha2::{digest::DynDigest, Digest, Sha256};
use std::str::FromStr;

/// The available algorithms for computing hashes
#[derive(Clone, Copy, PartialOrd, Eq, Ord, Debug, Hash, PartialEq)]
Expand Down Expand Up @@ -32,10 +34,22 @@ impl HashAlgorithm {
pub(crate) const NUM_HASH_BYTES: usize = 32;

impl Display for HashAlgorithm {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
HashAlgorithm::Sha1 => write!(f, "sha1"),
HashAlgorithm::Sha256 => write!(f, "sha256"),
}
}
}

impl FromStr for HashAlgorithm {
type Err = Error;

fn from_str(s: &str) -> Result<Self> {
match s {
"sha1" => Ok(HashAlgorithm::Sha1),
"sha256" => Ok(HashAlgorithm::Sha256),
_ => Err(Error::UnknownHashAlgorithm(s.to_owned())),
}
}
}
21 changes: 20 additions & 1 deletion gitoid/src/object_type.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
use std::fmt::{self, Display, Formatter};
use std::{
fmt::{self, Display, Formatter},
str::FromStr,
};

use crate::Error;

/// The types of objects for which a `GitOid` can be made.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
Expand Down Expand Up @@ -27,3 +32,17 @@ impl Display for ObjectType {
)
}
}

impl FromStr for ObjectType {
type Err = Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"blob" => Ok(ObjectType::Blob),
"tree" => Ok(ObjectType::Tree),
"commit" => Ok(ObjectType::Commit),
"tag" => Ok(ObjectType::Tag),
_ => Err(Error::UnknownObjectType(s.to_owned())),
}
}
}
84 changes: 83 additions & 1 deletion gitoid/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use hash_algorithm::HashAlgorithm::*;
use object_type::ObjectType::*;
use std::fs::File;
use std::io::BufReader;
use url::Url;

#[test]
fn generate_sha1_gitoid_from_bytes() {
Expand Down Expand Up @@ -108,9 +109,90 @@ fn validate_uri() -> Result<()> {
let gitoid = GitOid::new_from_bytes(Sha256, Blob, content);

assert_eq!(
gitoid.uri()?.to_string(),
gitoid.url()?.to_string(),
"gitoid:blob:sha256:fee53a18d32820613c0527aa79be5cb30173c823a9b448fa4817767cc84c6f03"
);

Ok(())
}

#[test]
fn try_from_url_bad_scheme() {
let url = Url::parse(
"whatever:blob:sha256:fee53a18d32820613c0527aa79be5cb30173c823a9b448fa4817767cc84c6f03",
)
.unwrap();

match GitOid::new_from_url(url) {
Ok(_) => panic!("gitoid parsing should fail"),
Err(e) => assert_eq!(e.to_string(), "invalid scheme in URL 'whatever'"),
}
}

#[test]
fn try_from_url_missing_object_type() {
let url = Url::parse("gitoid:").unwrap();

match GitOid::new_from_url(url) {
Ok(_) => panic!("gitoid parsing should fail"),
Err(e) => assert_eq!(e.to_string(), "missing object type in URL 'gitoid:'"),
}
}

#[test]
fn try_from_url_bad_object_type() {
let url = Url::parse("gitoid:whatever").unwrap();

match GitOid::new_from_url(url) {
Ok(_) => panic!("gitoid parsing should fail"),
Err(e) => assert_eq!(e.to_string(), "unknown object type 'whatever'"),
}
}

#[test]
fn try_from_url_missing_hash_algorithm() {
let url = Url::parse("gitoid:blob:").unwrap();

match GitOid::new_from_url(url) {
Ok(_) => panic!("gitoid parsing should fail"),
Err(e) => assert_eq!(
e.to_string(),
"missing hash algorithm in URL 'gitoid:blob:'"
),
}
}

#[test]
fn try_from_url_bad_hash_algorithm() {
let url = Url::parse("gitoid:blob:sha10000").unwrap();

match GitOid::new_from_url(url) {
Ok(_) => panic!("gitoid parsing should fail"),
Err(e) => assert_eq!(e.to_string(), "unknown hash algorithm 'sha10000'"),
}
}

#[test]
fn try_from_url_missing_hash() {
let url = Url::parse("gitoid:blob:sha256:").unwrap();

match GitOid::new_from_url(url) {
Ok(_) => panic!("gitoid parsing should fail"),
Err(e) => assert_eq!(e.to_string(), "missing hash in URL 'gitoid:blob:sha256:'"),
}
}

#[test]
fn try_url_roundtrip() {
let url = Url::parse(
"gitoid:blob:sha256:fee53a18d32820613c0527aa79be5cb30173c823a9b448fa4817767cc84c6f03",
)
.unwrap();
let gitoid = GitOid::new_from_url(url.clone()).unwrap();
let output = gitoid.url().unwrap();

eprintln!("{}", url);
eprintln!("{}", output);

assert_eq!(url, output);
}