diff --git a/Cargo.toml b/Cargo.toml index b6067c1..91536e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,8 @@ name = "ulid" version = "1.0.0" authors = ["dylanhart "] -edition = "2018" +edition = "2021" +rust-version = "1.60" license = "MIT" readme = "README.md" @@ -14,12 +15,12 @@ repository = "https://github.com/dylanhart/ulid-rs" [features] default = ["std"] -std = ["rand"] +std = ["rand", "serde?/std", "uuid?/std"] [dependencies] -serde = { version = "1.0", features = ["derive"], optional = true } +serde = { version = "1.0", features = ["derive"], optional = true, default-features = false } rand = { version = "0.8", optional = true } -uuid = { version = "1.1", optional = true } +uuid = { version = "1.1", optional = true, default-features = false } [dev-dependencies] bencher = "0.1" @@ -34,3 +35,4 @@ members = ["cli"] [package.metadata.docs.rs] all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 6b730ec..c6c7cc7 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -2,6 +2,7 @@ name = "ulid-cli" version = "0.3.1" authors = ["dylanhart ", "Kan-Ru Chen "] +rust-version = "1.60" license = "MIT" readme = "../README.md" @@ -11,7 +12,7 @@ keywords = ["ulid", "uuid", "sortable", "identifier"] repository = "https://github.com/dylanhart/ulid-rs" -edition = "2018" +edition = "2021" [dependencies] structopt = "0.2" diff --git a/src/base32.rs b/src/base32.rs index e2e012c..c2b5d6a 100644 --- a/src/base32.rs +++ b/src/base32.rs @@ -45,6 +45,7 @@ pub enum EncodeError { } #[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl std::error::Error for EncodeError {} impl fmt::Display for EncodeError { @@ -73,6 +74,7 @@ pub fn encode_to(mut value: u128, buffer: &mut [u8]) -> Result String { let mut buffer: [u8; ULID_LEN] = [0; ULID_LEN]; @@ -91,6 +93,7 @@ pub enum DecodeError { } #[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl std::error::Error for DecodeError {} impl fmt::Display for DecodeError { diff --git a/src/generator.rs b/src/generator.rs index 772e62d..8e4a657 100644 --- a/src/generator.rs +++ b/src/generator.rs @@ -5,6 +5,7 @@ use std::fmt; use crate::Ulid; /// A Ulid generator that provides monotonically increasing Ulids +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub struct Generator { previous: Ulid, } @@ -152,6 +153,7 @@ impl Default for Generator { } /// Error while trying to generate a monotonic increment in the same millisecond +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] pub enum MonotonicError { /// Would overflow into the next millisecond diff --git a/src/lib.rs b/src/lib.rs index 1cfe4a7..14a8693 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,6 +30,7 @@ //! //! ``` #![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(docsrs, feature(doc_cfg))] #[doc = include_str!("../README.md")] #[cfg(all(doctest, feature = "std"))] @@ -39,6 +40,7 @@ mod base32; #[cfg(feature = "std")] mod generator; #[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] pub mod serde; #[cfg(feature = "std")] mod time; @@ -206,6 +208,7 @@ impl Ulid { /// ``` #[allow(clippy::inherent_to_string_shadow_display)] // Significantly faster than Display::to_string #[cfg(feature = "std")] + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn to_string(&self) -> String { base32::encode(self.0) } @@ -281,6 +284,7 @@ impl Default for Ulid { } #[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl From for String { fn from(ulid: Ulid) -> String { ulid.to_string() diff --git a/src/serde.rs b/src/serde.rs index 829a6bf..0db3290 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -20,13 +20,63 @@ impl Serialize for Ulid { } } +struct UlidVisitor(&'static str); +impl<'de> serde::de::Visitor<'de> for UlidVisitor { + type Value = Ulid; + + fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { + formatter.write_str(self.0) + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + #[cfg(feature = "uuid")] + if matches!(value.len(), 32 | 36 | 38 | 45) { + return match uuid::Uuid::try_parse(value) { + Ok(a) => Ok(Ulid::from(a)), + Err(e) => Err(serde::de::Error::custom(e)), + }; + } + Ulid::from_string(value).map_err(serde::de::Error::custom) + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: serde::de::Error, + { + // allow either the 16 bytes as is, or a wrongly typed str + match v.len() { + 16 => { + let ptr = v.as_ptr() as *const [u8; 16]; + Ok(Ulid::from_bytes(*unsafe { &*ptr })) + } + crate::ULID_LEN => Ulid::from_string(unsafe { core::str::from_utf8_unchecked(v) }) + .map_err(serde::de::Error::custom), + #[cfg(feature = "uuid")] + 32 | 36 | 38 | 45 => match uuid::Uuid::try_parse_ascii(v) { + Ok(a) => Ok(Ulid::from(a)), + Err(e) => Err(serde::de::Error::custom(e)), + }, + len => Err(E::invalid_length(len, &self.0)), + } + } + + fn visit_u128(self, v: u128) -> Result + where + E: serde::de::Error, + { + Ok(Ulid(v)) + } +} + impl<'de> Deserialize<'de> for Ulid { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { - let deserialized_str = String::deserialize(deserializer)?; - Self::from_string(&deserialized_str).map_err(serde::de::Error::custom) + deserializer.deserialize_str(UlidVisitor("an ulid string or value")) } } @@ -50,7 +100,7 @@ impl<'de> Deserialize<'de> for Ulid { /// ``` pub mod ulid_as_u128 { use crate::Ulid; - use serde::{Deserialize, Deserializer, Serialize, Serializer}; + use serde::{Deserializer, Serialize, Serializer}; /// Serializes a ULID as a u128 type. pub fn serialize(value: &Ulid, serializer: S) -> Result @@ -65,8 +115,7 @@ pub mod ulid_as_u128 { where D: Deserializer<'de>, { - let deserialized_u128 = u128::deserialize(deserializer)?; - Ok(Ulid(deserialized_u128)) + deserializer.deserialize_u128(super::UlidVisitor("an ulid value as u128")) } } @@ -89,9 +138,10 @@ pub mod ulid_as_u128 { /// } /// ``` #[cfg(all(feature = "uuid", feature = "serde"))] +#[cfg_attr(docsrs, doc(cfg(all(feature = "uuid", feature = "serde"))))] pub mod ulid_as_uuid { use crate::Ulid; - use serde::{Deserialize, Deserializer, Serialize, Serializer}; + use serde::{Deserializer, Serializer}; use uuid::Uuid; /// Converts the ULID to a UUID and serializes it as a string. @@ -100,7 +150,9 @@ pub mod ulid_as_uuid { S: Serializer, { let uuid: Uuid = (*value).into(); - uuid.to_string().serialize(serializer) + let mut buffer = uuid::Uuid::encode_buffer(); + let form = uuid.as_hyphenated().encode_lower(&mut buffer); + serializer.serialize_str(form) } /// Deserializes a ULID from a string containing a UUID. @@ -108,8 +160,6 @@ pub mod ulid_as_uuid { where D: Deserializer<'de>, { - let de_string = String::deserialize(deserializer)?; - let de_uuid = Uuid::parse_str(&de_string).map_err(serde::de::Error::custom)?; - Ok(Ulid::from(de_uuid)) + deserializer.deserialize_str(super::UlidVisitor("an uuid string")) } } diff --git a/src/time.rs b/src/time.rs index 9f736ac..18ac28f 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,6 +1,9 @@ use crate::{bitmask, Ulid}; use std::time::{Duration, SystemTime}; +/// The standard library can be used to get the current time. +/// The `std` feature (which is enabled by default) will expose some extra functions to make life easier. +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl Ulid { /// Creates a new Ulid with the current time (UTC) /// @@ -10,6 +13,7 @@ impl Ulid { /// /// let my_ulid = Ulid::new(); /// ``` + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn new() -> Ulid { Ulid::from_datetime(SystemTime::now()) } @@ -24,6 +28,7 @@ impl Ulid { /// let mut rng = StdRng::from_entropy(); /// let ulid = Ulid::with_source(&mut rng); /// ``` + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn with_source(source: &mut R) -> Ulid { Ulid::from_datetime_with_source(SystemTime::now(), source) } @@ -42,6 +47,7 @@ impl Ulid { /// /// let ulid = Ulid::from_datetime(SystemTime::now()); /// ``` + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn from_datetime(datetime: SystemTime) -> Ulid { Ulid::from_datetime_with_source(datetime, &mut rand::thread_rng()) } @@ -60,6 +66,7 @@ impl Ulid { /// let mut rng = StdRng::from_entropy(); /// let ulid = Ulid::from_datetime_with_source(SystemTime::now(), &mut rng); /// ``` + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn from_datetime_with_source(datetime: SystemTime, source: &mut R) -> Ulid where R: rand::Rng + ?Sized, @@ -90,6 +97,7 @@ impl Ulid { /// && dt - Duration::from_millis(1) <= ulid.datetime() /// ); /// ``` + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn datetime(&self) -> SystemTime { let stamp = self.timestamp_ms(); SystemTime::UNIX_EPOCH + Duration::from_millis(stamp) diff --git a/src/uuid.rs b/src/uuid.rs index ed76e6a..6065c11 100644 --- a/src/uuid.rs +++ b/src/uuid.rs @@ -3,12 +3,14 @@ use crate::Ulid; use uuid::Uuid; +#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))] impl From for Ulid { fn from(uuid: Uuid) -> Self { Ulid(uuid.as_u128()) } } +#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))] impl From for Uuid { fn from(ulid: Ulid) -> Self { Uuid::from_u128(ulid.0) @@ -21,7 +23,14 @@ mod test { #[test] fn uuid_cycle() { + #[cfg(feature = "std")] let ulid = Ulid::new(); + #[cfg(not(feature = "std"))] + let ulid = Ulid::from_parts( + 0x0000_1020_3040_5060_u64, + 0x0000_0000_0000_0102_0304_0506_0708_090A_u128, + ); + let uuid: Uuid = ulid.into(); let ulid2: Ulid = uuid.into(); @@ -32,11 +41,17 @@ mod test { fn uuid_str_cycle() { let uuid_txt = "771a3bce-02e9-4428-a68e-b1e7e82b7f9f"; let ulid_txt = "3Q38XWW0Q98GMAD3NHWZM2PZWZ"; + let mut buf = uuid::Uuid::encode_buffer(); let ulid: Ulid = Uuid::parse_str(uuid_txt).unwrap().into(); - assert_eq!(ulid.to_string(), ulid_txt); + let ulid_str = ulid.to_str(&mut buf).unwrap(); + assert_eq!(ulid_str, ulid_txt); + + #[cfg(feature = "std")] + assert_eq!(ulid.to_string().as_str(), ulid_txt); let uuid: Uuid = ulid.into(); - assert_eq!(uuid.to_string(), uuid_txt); + let uuid_str = uuid.hyphenated().encode_lower(&mut buf); + assert_eq!(uuid_str, uuid_txt); } }