Compare commits
No commits in common. "main" and "1.6180339887" have entirely different histories.
main
...
1.61803398
9 changed files with 44 additions and 104 deletions
31
Cargo.toml
31
Cargo.toml
|
@ -1,8 +1,8 @@
|
|||
[package]
|
||||
name = "julid-rs"
|
||||
# 1.61803398874989484
|
||||
#----------------^
|
||||
version = "1.6.1803398874989"
|
||||
# ^
|
||||
version = "1.6.180339887"
|
||||
authors = ["Joe Ardent <code@ardent.nebcorp.com>"]
|
||||
edition = "2024"
|
||||
keywords = ["ulid", "sqlite", "julid", "uuid", "guid"]
|
||||
|
@ -12,19 +12,13 @@ readme = "README.md"
|
|||
license-file = "LICENSE.md"
|
||||
repository = "https://git.kittencollective.com/nebkor/julid-rs"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
[features]
|
||||
default = ["serde", "sqlx", "cli", "std", "chrono"] # no uuid or sqlite plugin
|
||||
chrono = ["dep:chrono"]
|
||||
cli = ["dep:clap", "chrono"]
|
||||
default = ["serde", "sqlx"] # just the regular crate
|
||||
serde = ["dep:serde"]
|
||||
sqlx = ["dep:sqlx"]
|
||||
std = ["chrono/std", "serde?/alloc"]
|
||||
uuid = ["dep:uuid"]
|
||||
|
||||
# WARNING! don't enable this feature in your project's Cargo.toml if using julid-rs as a Rust dependency;
|
||||
# WARNING! don't enable this feature in your project's Cargo.toml if using julid-rs as a dependency;
|
||||
# see https://gitlab.com/nebkor/julid/-/issues/1
|
||||
plugin = ["dep:sqlite-loadable"] # builds libjulid.* for loading into sqlite
|
||||
|
||||
|
@ -35,18 +29,19 @@ crate-type = ["cdylib", "rlib"]
|
|||
[dependencies]
|
||||
rand = "0.8"
|
||||
|
||||
# all other deps are optional
|
||||
chrono = { version = "0.4", default-features = false, features = ["std"], optional = true }
|
||||
# for the CLI
|
||||
clap = { version = "4", default-features = false, features = ["help", "usage", "std", "derive"], optional = true }
|
||||
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
|
||||
sqlx = { version = "0.8", features = ["sqlite"], default-features = false, optional = true }
|
||||
clap = { version = "4", default-features = false, features = ["help", "usage", "std", "derive"] }
|
||||
chrono = { version = "0.4", default-features = false, features = ["std", "time"] }
|
||||
|
||||
# all other deps are optional
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
sqlx = { version = "0.7", features = ["sqlite"], default-features = false, optional = true }
|
||||
sqlite-loadable = { version = "0.0.5", optional = true }
|
||||
uuid = { version = "1.17", default-features = false, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
divan = "0.1"
|
||||
uuid = { version = "1", default-features = false, features = ["v4"] }
|
||||
uuid = { version = "1.17", default-features = false, features = ["v7"] }
|
||||
julid-rs = { path = ".", features = ["uuid"] }
|
||||
|
||||
[[bench]]
|
||||
|
@ -56,4 +51,6 @@ harness = false
|
|||
[[bin]]
|
||||
name = "julid-gen"
|
||||
path = "src/bin/gen.rs"
|
||||
required-features = ["chrono", "cli"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
|
|
@ -30,9 +30,6 @@ Docs.rs: <https://docs.rs/julid-rs/latest/julid/>
|
|||
|
||||
Blog post: <https://proclamations.nebcorp-hias.com/sundries/presenting-julids/>
|
||||
|
||||
As of June of 2025, they can also be converted to and from [version 7
|
||||
UUIDs](https://www.ietf.org/rfc/rfc9562.html#name-uuid-version-7), though some precision in the
|
||||
intra-millisecond counter is lost when going to a UUID, via the `uuid` optional feature.
|
||||
|
||||
## A slightly deeper look
|
||||
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
1.61803398874989
|
||||
1.6180339887
|
||||
|
|
|
@ -65,7 +65,7 @@ impl fmt::Display for DecodeError {
|
|||
DecodeError::InvalidLength(len) => format!("invalid length: {len}"),
|
||||
DecodeError::InvalidChar(c) => format!("invalid character: {c}"),
|
||||
};
|
||||
write!(f, "{text}")
|
||||
write!(f, "{}", text)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -116,7 +116,6 @@ impl Julid {
|
|||
(self.0 & bitmask!(UNIQUE_BITS)) as u64
|
||||
}
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
/// Returns the timestamp as a `chrono::DateTime<chrono::Utc>` (feature
|
||||
/// `chrono` (default))
|
||||
pub fn created_at(&self) -> chrono::DateTime<chrono::Utc> {
|
||||
|
|
29
src/lib.rs
29
src/lib.rs
|
@ -2,27 +2,18 @@
|
|||
use sqlite_loadable::prelude::{c_char, c_uint, sqlite3, sqlite3_api_routines};
|
||||
|
||||
mod base32;
|
||||
|
||||
/// Contains the [`Julid`] type, which is publicly exported at the top level.
|
||||
pub mod julid;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
/// Serialization into bytes, and deserialization from a variety of formats,
|
||||
/// with Serde (feature `serde` (default))
|
||||
#[cfg(feature = "serde")]
|
||||
pub mod serde;
|
||||
|
||||
#[cfg(feature = "sqlx")]
|
||||
/// Traits from the SQLx crate for getting Julids into and out of SQLite
|
||||
/// databases from normal Rust applications. (feature `sqlx` (default))
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub mod sqlx;
|
||||
|
||||
/// UUIDv7s are almost as good as Julids, and can be interconverted almost
|
||||
/// perfectly. (feature `uuid` (non-default))
|
||||
///
|
||||
/// See the [`Julid::as_uuid`] and [`Julid::from_uuid`] methods for
|
||||
/// converting a Julid to a UUID and constructing a Julid from a UUID
|
||||
/// respectively.
|
||||
#[cfg(feature = "uuid")]
|
||||
/// UUIDv7s are almost as good as Julids, and can be interconverted almost
|
||||
/// perfectly.
|
||||
pub mod uuid;
|
||||
|
||||
#[doc(inline)]
|
||||
|
@ -30,13 +21,13 @@ pub use base32::DecodeError;
|
|||
#[doc(inline)]
|
||||
pub use julid::Julid;
|
||||
|
||||
/// The number of bits in a Julid's millisecond timestamp (48)
|
||||
/// The number of bits in a Julid's time portion
|
||||
pub const TIME_BITS: u8 = 48;
|
||||
/// The number of bits in the monotonic counter for intra-millisecond IDs (16)
|
||||
/// The number of bits in the monotonic counter for intra-millisecond IDs
|
||||
pub const COUNTER_BITS: u8 = 16;
|
||||
/// The number of random bits + bits in the monotonic counter (80)
|
||||
/// The number of random bits + bits in the monotonic counter
|
||||
pub const UNIQUE_BITS: u8 = 80;
|
||||
/// The number of fully random bits (64)
|
||||
/// The number of fully random bits
|
||||
pub const RANDOM_BITS: u8 = 64;
|
||||
|
||||
/// This `unsafe extern "C"` function is the main entry point into the loadable
|
||||
|
@ -47,7 +38,7 @@ pub const RANDOM_BITS: u8 = 64;
|
|||
/// This is FFI; it's inherently unsafe. But this function is called by
|
||||
/// sqlite, not by a user, so it should be OK.
|
||||
#[cfg(feature = "plugin")]
|
||||
#[unsafe(no_mangle)]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn sqlite3_julid_init(
|
||||
db: *mut sqlite3,
|
||||
_pz_err_msg: *mut *mut c_char,
|
||||
|
@ -61,7 +52,7 @@ pub unsafe extern "C" fn sqlite3_julid_init(
|
|||
}
|
||||
|
||||
/// The code for the SQLite plugin is kept in this module, and exposed via the
|
||||
/// [`sqlite3_julid_init`] function (feature `plugin` (non-default))
|
||||
/// `sqlite3_julid_init` function (feature `plugin`)
|
||||
#[cfg(feature = "plugin")]
|
||||
pub mod sqlite_plugin {
|
||||
use sqlite_loadable::{
|
||||
|
|
|
@ -34,7 +34,6 @@ impl<'de> Visitor<'de> for JulidVisitor {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
|
@ -98,13 +97,10 @@ impl<'de> Deserialize<'de> for Julid {
|
|||
/// }
|
||||
/// ```
|
||||
pub mod julid_as_str {
|
||||
#[cfg(feature = "std")]
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use serde::{Serialize, Serializer};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use crate::Julid;
|
||||
|
||||
/// Serialize a Julid into a String
|
||||
pub fn serialize<S>(value: &Julid, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
|
@ -113,8 +109,6 @@ pub mod julid_as_str {
|
|||
text.serialize(serializer)
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
/// Deserialize a String into a Julid (feature `std` only)
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Julid, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
|
|
10
src/sqlx.rs
10
src/sqlx.rs
|
@ -15,17 +15,11 @@ impl sqlx::Type<sqlx::Sqlite> for Julid {
|
|||
}
|
||||
|
||||
impl<'q> Encode<'q, Sqlite> for Julid {
|
||||
fn encode_by_ref(
|
||||
&self,
|
||||
args: &mut Vec<SqliteArgumentValue<'q>>,
|
||||
) -> std::result::Result<
|
||||
sqlx::encode::IsNull,
|
||||
std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 'static)>,
|
||||
> {
|
||||
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
|
||||
args.push(SqliteArgumentValue::Blob(Cow::Owned(
|
||||
self.as_bytes().to_vec(),
|
||||
)));
|
||||
Ok(IsNull::No)
|
||||
IsNull::No
|
||||
}
|
||||
}
|
||||
|
||||
|
|
62
src/uuid.rs
62
src/uuid.rs
|
@ -2,7 +2,7 @@ use std::fmt;
|
|||
|
||||
use uuid::{Uuid, Variant};
|
||||
|
||||
use crate::{Julid, COUNTER_BITS};
|
||||
use crate::Julid;
|
||||
|
||||
impl Julid {
|
||||
/// Convert to UUIDv7, possibly losing counter bits and altering the top
|
||||
|
@ -13,7 +13,7 @@ impl Julid {
|
|||
/// means that some bits in the original Julid are overwritten with
|
||||
/// UUID-specific values, but only six bits in total are potentially
|
||||
/// altered.
|
||||
pub const fn as_uuid(&self) -> Uuid {
|
||||
pub fn as_uuid(&self) -> Uuid {
|
||||
let counter_mask = (1 << 12) - 1;
|
||||
let entropy_mask = (1 << 62) - 1;
|
||||
let timestamp = self.timestamp();
|
||||
|
@ -29,9 +29,7 @@ impl Julid {
|
|||
///
|
||||
/// UUIDv7s are very similar to Julids, but use 12 bits for a monotonic
|
||||
/// counter instead of 16, and only 62 bits of entropy vs Julids' 64.
|
||||
/// Therefore, no bits technically need to be altered when converting to a
|
||||
/// Julid, but we zero out the high bits of the counter where the UUID
|
||||
/// version was stored.
|
||||
/// Therefore, no bits need to be altered when converting to a Julid.
|
||||
pub fn from_uuid(id: Uuid) -> Result<Self, UuidError> {
|
||||
let ver = id.get_version_num();
|
||||
if ver != 7 {
|
||||
|
@ -42,32 +40,11 @@ impl Julid {
|
|||
return Err(UuidError::UnsupportedVariant(var));
|
||||
}
|
||||
|
||||
let (hi, lo) = id.as_u64_pair();
|
||||
// zero out the high bits of the counter, which are "7" (0b0111) from the uuid
|
||||
let mask = (1 << 12) - 1;
|
||||
let counter = hi & mask;
|
||||
let ts = hi >> COUNTER_BITS;
|
||||
let hi = (ts << COUNTER_BITS) | counter;
|
||||
|
||||
Ok((hi, lo).into())
|
||||
Ok(id.as_u64_pair().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Julid> for Uuid {
|
||||
fn from(value: Julid) -> Self {
|
||||
value.as_uuid()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Uuid> for Julid {
|
||||
type Error = UuidError;
|
||||
|
||||
fn try_from(value: Uuid) -> Result<Self, Self::Error> {
|
||||
Julid::from_uuid(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
pub enum UuidError {
|
||||
UnsupportedVersion(usize),
|
||||
UnsupportedVariant(uuid::Variant),
|
||||
|
@ -87,9 +64,7 @@ impl fmt::Display for UuidError {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{uuid::UuidError, Julid};
|
||||
use crate::Julid;
|
||||
|
||||
#[test]
|
||||
fn into_uuid() {
|
||||
|
@ -103,24 +78,17 @@ mod test {
|
|||
#[test]
|
||||
fn from_uuid() {
|
||||
let j1 = Julid::new();
|
||||
let u1 = j1.as_uuid();
|
||||
let ju1 = Julid::from_uuid(u1).unwrap();
|
||||
// casting a julid to a uuid alters the counter and entropy bits slightly, so
|
||||
// the original julid and one derived from a uuid made from it won't be the
|
||||
// same.
|
||||
assert_ne!(j1, ju1);
|
||||
|
||||
let u1: Uuid = j1.into();
|
||||
let ju1: Julid = u1.try_into().unwrap();
|
||||
assert_eq!(j1.timestamp(), ju1.timestamp());
|
||||
assert_eq!(j1.counter(), ju1.counter());
|
||||
assert_eq!(j1.random() << 2, ju1.random() << 2);
|
||||
assert_eq!(ju1.random() >> 62, 2);
|
||||
// once we've converted to uuid and then back to julid, we've reached the fixed
|
||||
// point
|
||||
let u2 = ju1.as_uuid();
|
||||
let ju2 = u2.try_into().unwrap();
|
||||
let ju2 = Julid::from_uuid(u2).unwrap();
|
||||
// but once we've made that alteration, we've reached the fixed point
|
||||
assert_eq!(ju1, ju2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cant_even_from_uuid_non_v7() {
|
||||
let u = uuid::Uuid::new_v4();
|
||||
let jr: Result<Julid, UuidError> = u.try_into();
|
||||
assert_eq!(jr, Err(UuidError::UnsupportedVersion(4)));
|
||||
assert_eq!(u1, u2);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue