From 10aa50487bbdd851c58a2ed73071a50452441370 Mon Sep 17 00:00:00 2001 From: Tyrone Tudehope Date: Fri, 17 May 2024 22:24:57 +0200 Subject: [PATCH] feat: Support typeid v0.3 spec --- .github/workflows/ci.yaml | 4 --- strong_id/Cargo.toml | 6 ++--- strong_id/src/dynamic.rs | 37 +++++++++++++++++----------- strong_id/src/lib.rs | 22 ++++++++--------- strong_id_macros/src/lib.rs | 17 +++++++++---- tests/smoke_test/.cargo/config.toml | 2 -- tests/typeid_spec/.cargo/config.toml | 2 -- tests/typeid_spec/Cargo.toml | 6 ++--- tests/typeid_spec/src/main.rs | 15 ++++++++++- 9 files changed, 66 insertions(+), 45 deletions(-) delete mode 100644 tests/smoke_test/.cargo/config.toml delete mode 100644 tests/typeid_spec/.cargo/config.toml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6fc09a2..d52af03 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -14,8 +14,6 @@ jobs: with: toolchain: stable - run: cargo test --all-features - env: - RUSTFLAGS: "--cfg uuid_unstable" msrv: name: "Build / MSRV" runs-on: ubuntu-latest @@ -25,5 +23,3 @@ jobs: with: toolchain: 1.60.0 - run: cargo +1.60.0 build --all-features --manifest-path tests/smoke_test/Cargo.toml - env: - RUSTFLAGS: "--cfg uuid_unstable" diff --git a/strong_id/Cargo.toml b/strong_id/Cargo.toml index d31b3b6..62b0b7d 100644 --- a/strong_id/Cargo.toml +++ b/strong_id/Cargo.toml @@ -30,7 +30,7 @@ strong_id_macros = { version = "=0.3.0", path = "../strong_id_macros" } bitvec = { version = "1", default-features = false, features = ["atomic", "alloc"] } serde = { version = "1.0", optional = true, default-features = false, features = ["std"] } thiserror = "1.0" -uuid = { version = "1.4", default-features = false, features = ["std"], optional = true } +uuid = { version = "1.6", default-features = false, features = ["std"], optional = true } [dev-dependencies] serde_json = "1.0" @@ -58,6 +58,7 @@ uuid-v8 = ["strong_id_macros/uuid-v8", "uuid?/v8"] # note: the TypeID spec does not allow delimited prefixes, so this should be used alongside # `default-features = false` typeid = [ + "delimited", "uuid", "uuid-v7", ] @@ -77,7 +78,6 @@ all = [ ] [package.metadata.docs.rs] -rustc-args = ["--cfg", "uuid_unstable"] -rustdoc-args = ["--cfg", "docsrs", "--cfg", "uuid_unstable"] +rustdoc-args = ["--cfg", "docsrs"] targets = ["x86_64-unknown-linux-gnu"] all-features = true diff --git a/strong_id/src/dynamic.rs b/strong_id/src/dynamic.rs index 8e67013..b0cf6f8 100644 --- a/strong_id/src/dynamic.rs +++ b/strong_id/src/dynamic.rs @@ -11,16 +11,25 @@ fn map_prefix<'p, I: Into>>(prefix: I) -> Result, Error> { return Err(Error::PrefixTooLong(prefix.inner.len())); } - for b in prefix.inner.as_bytes() { + if prefix.inner.is_empty() { + return Err(Error::PrefixExpected); + } + + let underscore = b'_'; + let bytes = prefix.inner.as_bytes(); + + if *bytes.first().unwrap() == underscore || *bytes.last().unwrap() == underscore { + return Err(Error::IncorrectPrefixCharacter(underscore as char)); + } + + for b in bytes { if cfg!(feature = "delimited") && *b == b'_' { continue; } else if !b.is_ascii_lowercase() { return Err(Error::IncorrectPrefixCharacter(*b as char)); } } - if prefix.inner.is_empty() { - return Err(Error::PrefixExpected); - } + Ok(prefix) } @@ -322,7 +331,7 @@ impl<'p> DynamicStrongId<'p, Uuid> { } } - #[cfg(all(uuid_unstable, feature = "uuid-v6"))] + #[cfg(feature = "uuid-v6")] #[cfg_attr(docsrs, doc(cfg(feature = "uuid-v6")))] /// Create a new UUID-backed ID by generating a v6 UUID with a prefix /// @@ -338,7 +347,7 @@ impl<'p> DynamicStrongId<'p, Uuid> { }) } - #[cfg(all(uuid_unstable, feature = "uuid-v6"))] + #[cfg(feature = "uuid-v6")] #[cfg_attr(docsrs, doc(cfg(feature = "uuid-v6")))] /// Create a new UUID-backed ID by generating a v6 UUID without a prefix /// @@ -350,7 +359,7 @@ impl<'p> DynamicStrongId<'p, Uuid> { } } - #[cfg(all(uuid_unstable, feature = "uuid-v6"))] + #[cfg(feature = "uuid-v6")] #[cfg_attr(docsrs, doc(cfg(feature = "uuid-v6")))] /// Create a new UUID-backed ID by generating a v6 UUID with a prefix /// @@ -362,7 +371,7 @@ impl<'p> DynamicStrongId<'p, Uuid> { }) } - #[cfg(all(uuid_unstable, feature = "uuid-v6"))] + #[cfg(feature = "uuid-v6")] #[cfg_attr(docsrs, doc(cfg(feature = "uuid-v6")))] /// Create a new UUID-backed ID by generating a v6 UUID without a prefix /// @@ -374,7 +383,7 @@ impl<'p> DynamicStrongId<'p, Uuid> { } } - #[cfg(all(uuid_unstable, feature = "uuid-v7"))] + #[cfg(feature = "uuid-v7")] #[cfg_attr(docsrs, doc(cfg(feature = "uuid-v7")))] /// Create a new UUID-backed ID by generating a v7 UUID with a prefix /// @@ -386,7 +395,7 @@ impl<'p> DynamicStrongId<'p, Uuid> { }) } - #[cfg(all(uuid_unstable, feature = "uuid-v7"))] + #[cfg(feature = "uuid-v7")] #[cfg_attr(docsrs, doc(cfg(feature = "uuid-v7")))] /// Create a new UUID-backed ID by generating a v7 UUID without a prefix /// @@ -398,7 +407,7 @@ impl<'p> DynamicStrongId<'p, Uuid> { } } - #[cfg(all(uuid_unstable, feature = "uuid-v7"))] + #[cfg(feature = "uuid-v7")] #[cfg_attr(docsrs, doc(cfg(feature = "uuid-v7")))] /// Create a new UUID-backed ID by generating a v7 UUID with a prefix /// @@ -410,7 +419,7 @@ impl<'p> DynamicStrongId<'p, Uuid> { }) } - #[cfg(all(uuid_unstable, feature = "uuid-v7"))] + #[cfg(feature = "uuid-v7")] #[cfg_attr(docsrs, doc(cfg(feature = "uuid-v7")))] /// Create a new UUID-backed ID by generating a v7 UUID without a prefix /// @@ -422,7 +431,7 @@ impl<'p> DynamicStrongId<'p, Uuid> { } } - #[cfg(all(uuid_unstable, feature = "uuid-v8"))] + #[cfg(feature = "uuid-v8")] #[cfg_attr(docsrs, doc(cfg(feature = "uuid-v8")))] /// Create a new UUID-backed ID by generating a v7 UUID with a prefix /// @@ -434,7 +443,7 @@ impl<'p> DynamicStrongId<'p, Uuid> { }) } - #[cfg(all(uuid_unstable, feature = "uuid-v8"))] + #[cfg(feature = "uuid-v8")] #[cfg_attr(docsrs, doc(cfg(feature = "uuid-v8")))] /// Create a new UUID-backed ID by generating a v7 UUID without a prefix /// diff --git a/strong_id/src/lib.rs b/strong_id/src/lib.rs index 98f9692..d808bf8 100644 --- a/strong_id/src/lib.rs +++ b/strong_id/src/lib.rs @@ -244,24 +244,24 @@ pub trait StrongUuid { #[cfg_attr(docsrs, doc(cfg(feature = "uuid-v5")))] fn new_v5(namespace: &Uuid, name: &[u8]) -> Self; - #[cfg(all(uuid_unstable, feature = "uuid-v6"))] - #[cfg_attr(docsrs, doc(cfg(all(uuid_unstable, feature = "uuid-v6"))))] + #[cfg(feature = "uuid-v6")] + #[cfg_attr(docsrs, doc(cfg(all(feature = "uuid-v6"))))] fn new_v6(ts: uuid::Timestamp, node_id: &[u8; 6]) -> Self; - #[cfg(all(uuid_unstable, feature = "uuid-v6"))] - #[cfg_attr(docsrs, doc(cfg(all(uuid_unstable, feature = "uuid-v6"))))] + #[cfg(feature = "uuid-v6")] + #[cfg_attr(docsrs, doc(cfg(all(feature = "uuid-v6"))))] fn now_v6(node_id: &[u8; 6]) -> Self; - #[cfg(all(uuid_unstable, feature = "uuid-v7"))] - #[cfg_attr(docsrs, doc(cfg(all(uuid_unstable, feature = "uuid-v7"))))] + #[cfg(feature = "uuid-v7")] + #[cfg_attr(docsrs, doc(cfg(all(feature = "uuid-v7"))))] fn new_v7(ts: uuid::Timestamp) -> Self; - #[cfg(all(uuid_unstable, feature = "uuid-v7"))] - #[cfg_attr(docsrs, doc(cfg(all(uuid_unstable, feature = "uuid-v7"))))] + #[cfg(feature = "uuid-v7")] + #[cfg_attr(docsrs, doc(cfg(all(feature = "uuid-v7"))))] fn now_v7() -> Self; - #[cfg(all(uuid_unstable, feature = "uuid-v8"))] - #[cfg_attr(docsrs, doc(cfg(all(uuid_unstable, feature = "uuid-v8"))))] + #[cfg(feature = "uuid-v8")] + #[cfg_attr(docsrs, doc(cfg(all(feature = "uuid-v8"))))] fn new_v8(buf: [u8; 16]) -> Self; } @@ -280,7 +280,7 @@ macro_rules! impl_strong_uint { if val.len() != encoded_len::<$t>() { return Err(::strong_id::Error::InvalidLength(encoded_len::<$t>(), val.len())); } - + let mut out = [0; ::core::mem::size_of::<$t>()]; ::strong_id::base32::decode(val.as_bytes(), &mut out)?; diff --git a/strong_id_macros/src/lib.rs b/strong_id_macros/src/lib.rs index 72cf7e7..566fd4a 100644 --- a/strong_id_macros/src/lib.rs +++ b/strong_id_macros/src/lib.rs @@ -4,10 +4,17 @@ use quote::quote; use syn::{parse_macro_input, Data, DeriveInput, Fields, LitStr, Type}; fn assert_prefix_valid(prefix: &str) { + assert!(!prefix.is_empty(), "prefix must be non-empty"); assert!(prefix.len() < 64, "prefix is longer than 63 characters"); - for b in prefix.as_bytes() { - if cfg!(feature = "delimited") && *b == b'_' { + let underscore = b'_'; + let bytes = prefix.as_bytes(); + + assert_ne!(*bytes.first().unwrap(), underscore, "prefix cannot start with an underscore"); + assert_ne!(*bytes.last().unwrap(), underscore, "prefix cannot end with an underscore"); + + for (index, b) in prefix.as_bytes().iter().enumerate() { + if cfg!(feature = "delimited") && *b == underscore && index > 0 { continue; } @@ -194,7 +201,7 @@ pub fn derive_strong_id_uuid(input: proc_macro::TokenStream) -> proc_macro::Toke quote!() }; - let uuid_v6_impl = if cfg!(all(uuid_unstable, feature = "uuid-v6")) { + let uuid_v6_impl = if cfg!(feature = "uuid-v6") { quote! { fn new_v6(ts: ::strong_id::uuid::Timestamp, node_id: &[u8; 6]) -> Self { Self(::strong_id::uuid::Uuid::new_v6(ts, node_id)) @@ -208,7 +215,7 @@ pub fn derive_strong_id_uuid(input: proc_macro::TokenStream) -> proc_macro::Toke quote!() }; - let uuid_v7_impl = if cfg!(all(uuid_unstable, feature = "uuid-v7")) { + let uuid_v7_impl = if cfg!(feature = "uuid-v7") { quote! { fn new_v7(ts: ::strong_id::uuid::Timestamp) -> Self { Self(::strong_id::uuid::Uuid::new_v7(ts)) @@ -222,7 +229,7 @@ pub fn derive_strong_id_uuid(input: proc_macro::TokenStream) -> proc_macro::Toke quote!() }; - let uuid_v8_impl = if cfg!(all(uuid_unstable, feature = "uuid-v8")) { + let uuid_v8_impl = if cfg!(feature = "uuid-v8") { quote! { fn new_v8(buf: [u8; 16]) -> Self { Self(::strong_id::uuid::Uuid::new_v8(buf)) diff --git a/tests/smoke_test/.cargo/config.toml b/tests/smoke_test/.cargo/config.toml deleted file mode 100644 index 78df690..0000000 --- a/tests/smoke_test/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[build] -rustflags = ["--cfg", "uuid_unstable"] diff --git a/tests/typeid_spec/.cargo/config.toml b/tests/typeid_spec/.cargo/config.toml deleted file mode 100644 index 78df690..0000000 --- a/tests/typeid_spec/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[build] -rustflags = ["--cfg", "uuid_unstable"] diff --git a/tests/typeid_spec/Cargo.toml b/tests/typeid_spec/Cargo.toml index b67e394..d74fc66 100644 --- a/tests/typeid_spec/Cargo.toml +++ b/tests/typeid_spec/Cargo.toml @@ -5,10 +5,10 @@ edition = "2021" publish = false [dependencies] -libtest-mimic = "0.6" -reqwest = { version = "0.11", features = ["blocking", "json"] } +libtest-mimic = "0.7.3" +reqwest = { version = "0.12.4", features = ["blocking", "json"] } serde = { version = "1.0", features = ["derive"] } -uuid = { version = "1.4.0", features = ["v7"] } +uuid = { version = "1.6.0", features = ["v7"] } [dependencies.strong_id] path = "../../strong_id" diff --git a/tests/typeid_spec/src/main.rs b/tests/typeid_spec/src/main.rs index 3de96ea..7d44376 100644 --- a/tests/typeid_spec/src/main.rs +++ b/tests/typeid_spec/src/main.rs @@ -53,6 +53,7 @@ fn fetch_cases() -> Vec { strong_uuid!(struct NoPrefix(Uuid)); strong_uuid!(struct Prefix(Uuid => "prefix")); +strong_uuid!(struct PrefixUnderscore(Uuid => "pre_fix")); fn main() { let valid_cases = fetch_cases::(); @@ -84,7 +85,7 @@ fn main() { .chain(valid_cases.into_iter().map(|case| { Trial::test(format!("valid::static::{}", case.name), move || { match case.prefix() { - Some(_prefix) => { + Some(prefix) if !prefix.contains('_') => { let uuid = Uuid::from_str(&case.uuid).unwrap(); // encode let encoded = Prefix::from(uuid); @@ -96,6 +97,18 @@ fn main() { assert_eq!(encoded.to_string(), case.typeid); assert_eq!(*encoded.id(), uuid); } + Some(_) => { + let uuid = Uuid::from_str(&case.uuid).unwrap(); + // encode + let encoded = PrefixUnderscore::from(uuid); + assert_eq!(encoded.to_string(), case.typeid); + assert_eq!(*encoded.id(), uuid); + + // decode + let encoded = PrefixUnderscore::from_str(&case.typeid).unwrap(); + assert_eq!(encoded.to_string(), case.typeid); + assert_eq!(*encoded.id(), uuid); + } None => { let uuid = Uuid::from_str(&case.uuid).unwrap(); // encode