diff --git a/Cargo.lock b/Cargo.lock index 6c3e753..b74b7d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,21 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aead" @@ -77,6 +92,21 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + [[package]] name = "base32" version = "0.5.1" @@ -520,6 +550,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + [[package]] name = "hash32" version = "0.2.1" @@ -818,6 +854,15 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + [[package]] name = "nanorand" version = "0.7.0" @@ -836,6 +881,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.20.2" @@ -968,6 +1022,7 @@ dependencies = [ "pubky-common", "serde", "serde_json", + "tokio", "url", "utoipa", ] @@ -1047,6 +1102,12 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + [[package]] name = "rustc_version" version = "0.4.1" @@ -1279,6 +1340,28 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tokio" +version = "1.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +dependencies = [ + "backtrace", + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing" version = "0.1.40" diff --git a/Cargo.toml b/Cargo.toml index dce5752..cc6a3bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,6 @@ url = "2.5.4" base32 = "0.5.1" blake3 = "1.5.4" chrono = "0.4.38" + +[dev-dependencies] +tokio = { version = "1", features = ["macros", "rt"] } diff --git a/src/tag.rs b/src/tag.rs index d52b83a..abf7374 100644 --- a/src/tag.rs +++ b/src/tag.rs @@ -15,7 +15,7 @@ const MAX_TAG_LABEL_LENGTH: usize = 20; /// `/pub/pubky.app/tags/FPB0AM9S93Q3M1GFY1KV09GMQM` /// /// Where tag_id is Crockford-base32(Blake3("{uri_tagged}:{label}")[:half]) -#[derive(Serialize, Deserialize, Default)] +#[derive(Serialize, Deserialize, Default, Debug)] pub struct PubkyAppTag { pub uri: String, pub label: String, @@ -33,11 +33,17 @@ impl HashId for PubkyAppTag { #[async_trait] impl Validatable for PubkyAppTag { async fn sanitize(self) -> Result { + // Remove spaces from the tag and keep it as one word + let mut label = self + .label + .chars() + .filter(|c| !c.is_whitespace()) + .collect::(); // Convert label to lowercase and trim - let label = self.label.trim().to_lowercase(); + label = label.trim().to_lowercase(); // Enforce maximum label length safely - let label = label.chars().take(MAX_TAG_LABEL_LENGTH).collect::(); + label = label.chars().take(MAX_TAG_LABEL_LENGTH).collect::(); // Sanitize URI let uri = match Url::parse(&self.uri) { @@ -66,14 +72,109 @@ impl Validatable for PubkyAppTag { } } -#[test] -fn test_create_id() { - let tag = PubkyAppTag { - uri: "user_id/pub/pubky.app/posts/post_id".to_string(), - created_at: 1627849723, - label: "cool".to_string(), - }; +#[cfg(test)] +mod tests { + use super::*; + use tokio; - let tag_id = tag.create_id(); - println!("Generated Tag ID: {}", tag_id); + #[test] + fn test_label_id() { + // Precomputed earlier + let tag_id = "CBYS8P6VJPHC5XXT4WDW26662W"; + // Create new tag + let tag = PubkyAppTag { + uri: "pubky://user_id/pub/pubky.app/posts/post_id".to_string(), + created_at: 1627849723, + label: "cool".to_string(), + }; + // Check if the tag ID is correct + assert_eq!( + tag.create_id(), + tag_id + ); + + let wrong_tag = PubkyAppTag { + uri: "pubky://user_id/pub/pubky.app/posts/post_id".to_string(), + created_at: 1627849723, + label: "co0l".to_string(), + }; + + // Assure that the new tag has wrong ID + assert_ne!( + wrong_tag.create_id(), + tag_id + ); + } + + #[tokio::test] + async fn test_incorrect_label() -> Result<(), DynError> { + let tag = PubkyAppTag { + uri: "user_id/pub/pubky.app/posts/post_id".to_string(), + created_at: 1627849723, + label: "cool".to_string(), + }; + + match tag.sanitize().await { + Err(e) => assert_eq!(e.to_string(), "Invalid URI in tag".to_string(), "The error message is not related URI or the message description is wrong"), + _ => () + }; + + let tag = PubkyAppTag { + uri: "pubky://user_id/pub/pubky.app/posts/post_id".to_string(), + created_at: 1627849723, + label: "coolc00lcolaca0g00llooll".to_string(), + }; + + // Precomputed earlier + let label_id = "8WXXXXHK028RH8AWBZZNHJYDN4"; + + match tag.validate(label_id).await { + Err(e) => assert_eq!(e.to_string(), "Tag label exceeds maximum length".to_string(), "The error message is not related tag length or the message description is wrong"), + _ => () + }; + + Ok(()) + + + } + + #[tokio::test] + async fn test_white_space_tag() -> Result<(), DynError> { + // All the tags has to be that label after sanitation + let label = "cool"; + + let leading_whitespace = PubkyAppTag { + uri: "pubky://user_id/pub/pubky.app/posts/post_id".to_string(), + created_at: 1627849723, + label: " cool".to_string(), + }; + let mut sanitazed_label = leading_whitespace.sanitize().await?; + assert_eq!(sanitazed_label.label, label); + + let trailing_whitespace = PubkyAppTag { + uri: "pubky://user_id/pub/pubky.app/posts/post_id".to_string(), + created_at: 1627849723, + label: " cool".to_string(), + }; + sanitazed_label = trailing_whitespace.sanitize().await?; + assert_eq!(sanitazed_label.label, label); + + let space_between = PubkyAppTag { + uri: "pubky://user_id/pub/pubky.app/posts/post_id".to_string(), + created_at: 1627849723, + label: "co ol".to_string(), + }; + sanitazed_label = space_between.sanitize().await?; + assert_eq!(sanitazed_label.label, label); + + let space_between = PubkyAppTag { + uri: "pubky://user_id/pub/pubky.app/posts/post_id".to_string(), + created_at: 1627849723, + label: " co ol ".to_string(), + }; + sanitazed_label = space_between.sanitize().await?; + assert_eq!(sanitazed_label.label, label); + + Ok(()) + } }