core: Introduce RepoId
Introduce the `radicle-core` crate for housing data types that are at the core of the Radicle protocol. The initial data type being added is `RepoId`. To make the crate as lightweight as possible many of dependencies are optional and gated by feature flags. The crate is marked as `no_std` even though it unconditionally depends on `radicle-crypto`, which is *not* `no_std`. This is to invite refactoring of `radicle-crypto` to `no_std` at a later time.
This commit is contained in:
parent
af3f07627b
commit
d5fea6324c
|
|
@ -637,7 +637,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
|
@ -649,7 +649,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
|
|
@ -878,7 +878,7 @@ dependencies = [
|
|||
"generic-array",
|
||||
"group",
|
||||
"pkcs8",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
"sec1",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
|
|
@ -1006,7 +1006,7 @@ version = "0.13.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
|
|
@ -1044,6 +1044,12 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.1"
|
||||
|
|
@ -1675,7 +1681,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
|
||||
dependencies = [
|
||||
"ff",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
|
|
@ -2347,7 +2353,7 @@ dependencies = [
|
|||
"num-integer",
|
||||
"num-iter",
|
||||
"num-traits",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"smallvec",
|
||||
"zeroize",
|
||||
]
|
||||
|
|
@ -2493,7 +2499,7 @@ dependencies = [
|
|||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
"primeorder",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
|
|
@ -2724,13 +2730,32 @@ dependencies = [
|
|||
"parking_lot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proptest"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"bit-vec",
|
||||
"bitflags 2.9.1",
|
||||
"num-traits",
|
||||
"rand 0.9.2",
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_xorshift",
|
||||
"regex-syntax 0.8.5",
|
||||
"rusty-fork",
|
||||
"tempfile",
|
||||
"unarray",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qcheck"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b439bd4242da51d62d18c95e6a6add749346756b0d1a587dfd0cc22fa6b5f3f0"
|
||||
dependencies = [
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2744,6 +2769,12 @@ dependencies = [
|
|||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.38.1"
|
||||
|
|
@ -2895,6 +2926,24 @@ dependencies = [
|
|||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radicle-core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"git2",
|
||||
"gix-hash",
|
||||
"multibase",
|
||||
"proptest",
|
||||
"qcheck",
|
||||
"radicle-git-ref-format",
|
||||
"radicle-oid",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlite",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radicle-crypto"
|
||||
version = "0.14.0"
|
||||
|
|
@ -3158,8 +3207,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3169,7 +3228,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3181,6 +3250,24 @@ dependencies = [
|
|||
"getrandom 0.2.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_xorshift"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
|
||||
dependencies = [
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.4.1"
|
||||
|
|
@ -3300,7 +3387,7 @@ dependencies = [
|
|||
"num-traits",
|
||||
"pkcs1",
|
||||
"pkcs8",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
"sha2",
|
||||
"signature 2.2.0",
|
||||
"spki",
|
||||
|
|
@ -3346,6 +3433,18 @@ version = "1.0.20"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
|
||||
|
||||
[[package]]
|
||||
name = "rusty-fork"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"quick-error",
|
||||
"tempfile",
|
||||
"wait-timeout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.17"
|
||||
|
|
@ -3630,7 +3729,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3789,7 +3888,7 @@ dependencies = [
|
|||
"p256",
|
||||
"p384",
|
||||
"p521",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
"rsa",
|
||||
"sec1",
|
||||
"sha2",
|
||||
|
|
@ -4376,6 +4475,12 @@ version = "1.17.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "unarray"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-display-width"
|
||||
version = "0.3.0"
|
||||
|
|
@ -4543,6 +4648,15 @@ version = "0.8.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64"
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
|
|
@ -4984,7 +5098,7 @@ checksum = "1ccf671d62d1bd0c913d9059e69bb4a6b51f7a4c899ab83c62d921e35f206053"
|
|||
dependencies = [
|
||||
"defer-heavy",
|
||||
"log",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"sync-ptr",
|
||||
"windows",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ log = "0.4.17"
|
|||
multibase = "0.9.1"
|
||||
nonempty = "0.9.0"
|
||||
pretty_assertions = "1.3.0"
|
||||
proptest = "1.9"
|
||||
qcheck = { version = "1", default-features = false }
|
||||
qcheck-macros = { version = "1", default-features = false }
|
||||
radicle = { version = "0.20", path = "crates/radicle" }
|
||||
|
|
@ -65,7 +66,7 @@ signature = "2.2"
|
|||
snapbox = "0.4.3"
|
||||
sqlite = "0.32.0"
|
||||
tempfile = "3.3.0"
|
||||
thiserror = "2"
|
||||
thiserror = { version = "2", default-features = false }
|
||||
winpipe = "0.1.1"
|
||||
zeroize = "1.5.7"
|
||||
|
||||
|
|
|
|||
|
|
@ -18,4 +18,4 @@ pretty_assertions = { workspace = true }
|
|||
radicle = { workspace = true, features = ["logger", "test"]}
|
||||
shlex = { workspace = true }
|
||||
snapbox = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
thiserror = { workspace = true, default-features = true }
|
||||
|
|
@ -35,7 +35,7 @@ serde = { workspace = true }
|
|||
serde_json = { workspace = true }
|
||||
shlex = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
thiserror = { workspace = true, default-features = true }
|
||||
timeago = { version = "0.4.2", default-features = false }
|
||||
tree-sitter = "0.24.4"
|
||||
tree-sitter-bash = "0.23.3"
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ radicle-oid = { workspace = true, features = ["git2", "serde", "std"] }
|
|||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
signature = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
thiserror = { workspace = true, default-features = true }
|
||||
|
||||
[dev-dependencies]
|
||||
fastrand = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
[package]
|
||||
name = "radicle-core"
|
||||
description = "Radicle core data type definitions"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
keywords = ["radicle", "git", "data types"]
|
||||
rust-version.workspace = true
|
||||
|
||||
# For documentation of features refer to the module documentation in `./lib.rs`
|
||||
[features]
|
||||
default = ["std"]
|
||||
git2 = ["dep:git2", "radicle-oid/git2"]
|
||||
gix = ["dep:gix-hash", "radicle-oid/gix"]
|
||||
std = ["radicle-oid/std", "thiserror/std", "schemars/std", "serde/std"]
|
||||
|
||||
[dependencies]
|
||||
git2 = { workspace = true, optional = true }
|
||||
gix-hash = { workspace = true, optional = true }
|
||||
multibase = { workspace = true }
|
||||
proptest = { workspace = true, optional = true }
|
||||
qcheck = { workspace = true, optional = true }
|
||||
radicle-git-ref-format = { workspace = true, optional = true }
|
||||
radicle-oid = { workspace = true, default-features = false, features = ["sha1"] }
|
||||
schemars = { workspace = true, optional = true, default-features = false, features = ["derive"] }
|
||||
serde = { workspace = true, optional = true, default-features = false }
|
||||
sqlite = { workspace = true, optional = true }
|
||||
thiserror = { workspace = true, default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
proptest = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
#![no_std]
|
||||
|
||||
//! This a crate for defining core data type for the Radicle protocol, such as
|
||||
//! [`RepoId`].
|
||||
//!
|
||||
//! # Feature Flags
|
||||
//!
|
||||
//! The only default feature is `std`.
|
||||
//!
|
||||
//! ## `std`
|
||||
//!
|
||||
//! [`OsString`]: ::doc_std::ffi::OsString
|
||||
//!
|
||||
//! Provides implementation of [`TryFrom<OsString>`].
|
||||
//!
|
||||
//! Enabled by default, since it is expected that most dependents will use the
|
||||
//! standard library.
|
||||
//!
|
||||
//! ## `git2`
|
||||
//!
|
||||
//! [`git2::Oid`]: ::git2::Oid
|
||||
//!
|
||||
//! Provides conversion from a [`git2::Oid`] to a [`RepoId`].
|
||||
//!
|
||||
//! ## `gix`
|
||||
//!
|
||||
//! [`ObjectId`]: ::gix_hash::ObjectId
|
||||
//!
|
||||
//! Provides conversion from a [`ObjectId`] to a [`RepoId`].
|
||||
//!
|
||||
//! ## `radicle-git-ref-format`
|
||||
//!
|
||||
//! Provides conversions from data types defined in `radicle-core` into valid
|
||||
//! reference components and/or strings.
|
||||
//!
|
||||
//! ## `serde`
|
||||
//!
|
||||
//! [`Serialize`]: ::serde::ser::Serialize
|
||||
//! [`Deserialize`]: ::serde::de::Deserialize
|
||||
//!
|
||||
//! Provides implementations of [`Serialize`] and [`Deserialize`].
|
||||
//!
|
||||
//! ## `schemars`
|
||||
//!
|
||||
//! [`JsonSchema`]: ::schemars::JsonSchema
|
||||
//!
|
||||
//! Provides implementations of [`JsonSchema`].
|
||||
//!
|
||||
//! ## `proptest`
|
||||
//!
|
||||
//! [`proptest::Strategy`]: ::proptest::strategy::Strategy
|
||||
//!
|
||||
//! Provides functions for generating different types of [`proptest::Strategy`].
|
||||
//!
|
||||
//! ## `qcheck`
|
||||
//!
|
||||
//! [`qcheck::Arbitrary`]: ::qcheck::Arbitrary
|
||||
//!
|
||||
//! Provides implementations of [`qcheck::Arbitrary`].
|
||||
//!
|
||||
//! ## `sqlite`
|
||||
//!
|
||||
//! [`sqlite::BindableWithIndex`]: ::sqlite::BindableWithIndex
|
||||
//! [`sqlite::Value`]: ::sqlite::Value
|
||||
//!
|
||||
//! Provides implementations of [`sqlite::BindableWithIndex`] and `TryFrom`
|
||||
//! implementations from the [`sqlite::Value`] type to the domain type.
|
||||
|
||||
#[cfg(doc)]
|
||||
extern crate std as doc_std;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
pub mod repo;
|
||||
pub use repo::RepoId;
|
||||
|
|
@ -0,0 +1,294 @@
|
|||
use alloc::string::String;
|
||||
use alloc::string::ToString as _;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use radicle_oid::Oid;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Radicle identifier prefix.
|
||||
pub const RAD_PREFIX: &str = "rad:";
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum IdError {
|
||||
#[error(transparent)]
|
||||
Multibase(#[from] multibase::Error),
|
||||
#[error("invalid length: expected {expected} bytes, got {actual} bytes")]
|
||||
Length { expected: usize, actual: usize },
|
||||
}
|
||||
|
||||
/// A repository identifier.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct RepoId(
|
||||
#[cfg_attr(feature = "schemars", schemars(
|
||||
with = "String",
|
||||
description = "A repository identifier. Starts with \"rad:\", followed by a multibase Base58 encoded Git object identifier.",
|
||||
regex(pattern = r"rad:z[1-9a-km-zA-HJ-NP-Z]+"),
|
||||
length(min = 5),
|
||||
example = &"rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5",
|
||||
))]
|
||||
Oid,
|
||||
);
|
||||
|
||||
impl core::fmt::Display for RepoId {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.write_str(self.urn().as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for RepoId {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "RepoId({self})")
|
||||
}
|
||||
}
|
||||
|
||||
impl RepoId {
|
||||
/// Format the identifier as a human-readable URN.
|
||||
///
|
||||
/// Eg. `rad:z3XncAdkZjeK9mQS5Sdc4qhw98BUX`.
|
||||
///
|
||||
pub fn urn(&self) -> String {
|
||||
RAD_PREFIX.to_string() + &self.canonical()
|
||||
}
|
||||
|
||||
/// Parse an identifier from the human-readable URN format.
|
||||
/// Accepts strings without the radicle prefix as well,
|
||||
/// for convenience.
|
||||
pub fn from_urn(s: &str) -> Result<Self, IdError> {
|
||||
let s = s.strip_prefix(RAD_PREFIX).unwrap_or(s);
|
||||
let id = Self::from_canonical(s)?;
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
/// Format the identifier as a multibase string.
|
||||
///
|
||||
/// Eg. `z3XncAdkZjeK9mQS5Sdc4qhw98BUX`.
|
||||
///
|
||||
pub fn canonical(&self) -> String {
|
||||
multibase::encode(multibase::Base::Base58Btc, AsRef::<[u8]>::as_ref(&self.0))
|
||||
}
|
||||
|
||||
pub fn from_canonical(input: &str) -> Result<Self, IdError> {
|
||||
const EXPECTED_LEN: usize = 20;
|
||||
let (_, bytes) = multibase::decode(input)?;
|
||||
let bytes: [u8; EXPECTED_LEN] =
|
||||
bytes.try_into().map_err(|bytes: Vec<u8>| IdError::Length {
|
||||
expected: EXPECTED_LEN,
|
||||
actual: bytes.len(),
|
||||
})?;
|
||||
Ok(Self(Oid::from_sha1(bytes)))
|
||||
}
|
||||
}
|
||||
|
||||
impl core::str::FromStr for RepoId {
|
||||
type Err = IdError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Self::from_urn(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
mod std_impls {
|
||||
extern crate std;
|
||||
|
||||
use super::{IdError, RepoId};
|
||||
|
||||
use std::ffi::OsString;
|
||||
|
||||
impl TryFrom<OsString> for RepoId {
|
||||
type Error = IdError;
|
||||
|
||||
fn try_from(value: OsString) -> Result<Self, Self::Error> {
|
||||
let string = value.to_string_lossy();
|
||||
Self::from_canonical(&string)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::hash::Hash for RepoId {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.0.hash(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Oid> for RepoId {
|
||||
fn from(oid: Oid) -> Self {
|
||||
Self(oid)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Deref for RepoId {
|
||||
type Target = Oid;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "git2")]
|
||||
mod git2_impls {
|
||||
use super::RepoId;
|
||||
|
||||
impl From<git2::Oid> for RepoId {
|
||||
fn from(oid: git2::Oid) -> Self {
|
||||
Self(oid.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "gix")]
|
||||
mod gix_impls {
|
||||
use super::RepoId;
|
||||
|
||||
impl From<gix_hash::ObjectId> for RepoId {
|
||||
fn from(oid: gix_hash::ObjectId) -> Self {
|
||||
Self(oid.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "radicle-git-ref-format")]
|
||||
mod radicle_git_ref_format_impls {
|
||||
use alloc::string::ToString;
|
||||
|
||||
use radicle_git_ref_format::{Component, RefString};
|
||||
|
||||
use super::RepoId;
|
||||
|
||||
impl From<&RepoId> for Component<'_> {
|
||||
fn from(id: &RepoId) -> Self {
|
||||
let refstr = RefString::try_from(id.0.to_string())
|
||||
.expect("repository id's are valid ref strings");
|
||||
Component::from_refstr(refstr).expect("repository id's are valid refname components")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
mod serde_impls {
|
||||
use alloc::string::String;
|
||||
|
||||
use serde::{de, Deserialize, Deserializer, Serialize};
|
||||
|
||||
use super::RepoId;
|
||||
|
||||
impl Serialize for RepoId {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.collect_str(&self.urn())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for RepoId {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
String::deserialize(deserializer)?
|
||||
.parse()
|
||||
.map_err(de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use proptest::proptest;
|
||||
|
||||
use super::super::*;
|
||||
|
||||
fn prop_roundtrip_serde_json(rid: RepoId) {
|
||||
let encoded = serde_json::to_string(&rid).unwrap();
|
||||
let decoded = serde_json::from_str(&encoded).unwrap();
|
||||
|
||||
assert_eq!(rid, decoded);
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn assert_prop_roundtrip_serde_json(rid in arbitrary::rid()) {
|
||||
prop_roundtrip_serde_json(rid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
mod sqlite_impls {
|
||||
use alloc::format;
|
||||
use alloc::string::ToString;
|
||||
|
||||
use super::RepoId;
|
||||
|
||||
use sqlite::{BindableWithIndex, Error, ParameterIndex, Statement, Value};
|
||||
|
||||
impl TryFrom<&Value> for RepoId {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &Value) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Value::String(id) => RepoId::from_urn(id).map_err(|e| Error {
|
||||
code: None,
|
||||
message: Some(e.to_string()),
|
||||
}),
|
||||
_ => Err(Error {
|
||||
code: None,
|
||||
message: Some(format!("sql: invalid type `{:?}` for id", value.kind())),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BindableWithIndex for &RepoId {
|
||||
fn bind<I: ParameterIndex>(self, stmt: &mut Statement<'_>, i: I) -> sqlite::Result<()> {
|
||||
self.urn().as_str().bind(stmt, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "proptest"))]
|
||||
pub mod arbitrary {
|
||||
use proptest::prelude::Strategy;
|
||||
|
||||
use super::RepoId;
|
||||
|
||||
pub fn rid() -> impl Strategy<Value = RepoId> {
|
||||
proptest::array::uniform20(proptest::num::u8::ANY)
|
||||
.prop_map(|bytes| RepoId::from(radicle_oid::Oid::from_sha1(bytes)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "qcheck")]
|
||||
impl qcheck::Arbitrary for RepoId {
|
||||
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
||||
let bytes = <[u8; 20]>::arbitrary(g);
|
||||
let oid = radicle_oid::Oid::from_sha1(bytes);
|
||||
|
||||
RepoId::from(oid)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use proptest::proptest;
|
||||
|
||||
fn prop_roundtrip_parse(rid: RepoId) {
|
||||
use core::str::FromStr as _;
|
||||
let encoded = rid.to_string();
|
||||
let decoded = RepoId::from_str(&encoded).unwrap();
|
||||
|
||||
assert_eq!(rid, decoded);
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn assert_prop_roundtrip_parse(rid in arbitrary::rid()) {
|
||||
prop_roundtrip_parse(rid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ serde = { workspace = true, features = ["derive", "std"] }
|
|||
signature = { workspace = true }
|
||||
sqlite = { workspace = true, features = ["bundled"], optional = true }
|
||||
ssh-key = { version = "0.6.3", default-features = false, features = ["std", "encryption", "getrandom"], optional = true }
|
||||
thiserror = { workspace = true }
|
||||
thiserror = { workspace = true, default-features = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
|||
|
|
@ -22,4 +22,4 @@ nonempty = { workspace = true }
|
|||
radicle = { workspace = true }
|
||||
radicle-oid = { workspace = true, features = ["gix"] }
|
||||
radicle-git-ref-format = { workspace = true, features = ["bstr"] }
|
||||
thiserror = { workspace = true }
|
||||
thiserror = { workspace = true, default-features = true }
|
||||
|
|
@ -10,4 +10,4 @@ keywords = ["radicle", "git", "metadata"]
|
|||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
thiserror = { workspace = true }
|
||||
thiserror = { workspace = true, default-features = true }
|
||||
|
|
@ -41,7 +41,7 @@ snapbox = { workspace = true, optional = true }
|
|||
socket2 = { version = "0.5.7", features = ["all"], optional = true }
|
||||
structured-logger = { version = "1.0.4", optional = true }
|
||||
tempfile = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
thiserror = { workspace = true, default-features = true }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
radicle-systemd = { workspace = true, optional = true }
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ sqlite = { workspace = true, features = ["bundled"] }
|
|||
scrypt = { version = "0.11.0", default-features = false }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true, features = ["preserve_order"] }
|
||||
thiserror = { workspace = true }
|
||||
thiserror = { workspace = true, default-features = true }
|
||||
|
||||
[dev-dependencies]
|
||||
paste = "1.0.15"
|
||||
|
|
|
|||
|
|
@ -19,4 +19,4 @@ log = { workspace = true }
|
|||
radicle = { workspace = true }
|
||||
radicle-cli = { workspace = true }
|
||||
radicle-crypto = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
thiserror = { workspace = true, default-features = true }
|
||||
|
|
@ -14,7 +14,7 @@ edition.workspace = true
|
|||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
thiserror = { workspace = true }
|
||||
thiserror = { workspace = true, default-features = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ inquire = { version = "0.7.4", default-features = false, features = [
|
|||
"crossterm",
|
||||
"editor",
|
||||
] }
|
||||
thiserror = { workspace = true }
|
||||
thiserror = { workspace = true, default-features = true }
|
||||
unicode-display-width = "0.3.0"
|
||||
unicode-segmentation = "1.7.1"
|
||||
zeroize = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ serde-untagged = "0.1.7"
|
|||
siphasher = "1.0.0"
|
||||
sqlite = { workspace = true, features = ["bundled"] }
|
||||
tempfile = { workspace = true, optional = true }
|
||||
thiserror = { workspace = true }
|
||||
thiserror = { workspace = true, default-features = true }
|
||||
unicode-normalization = { version = "0.1" }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
|
|
|
|||
Loading…
Reference in New Issue