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:
Fintan Halpenny 2025-11-06 08:52:30 +00:00 committed by Lorenz Leutgeb
parent af3f07627b
commit d5fea6324c
17 changed files with 549 additions and 28 deletions

144
Cargo.lock generated
View File

@ -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",
]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,4 +10,4 @@ keywords = ["radicle", "git", "metadata"]
rust-version.workspace = true
[dependencies]
thiserror = { workspace = true }
thiserror = { workspace = true, default-features = true }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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