From 34b184e11ae38ce79cedb043031890a6af0a567a Mon Sep 17 00:00:00 2001 From: Daniil Date: Thu, 10 Feb 2022 15:14:48 +0300 Subject: [PATCH] [feature] #1606: Add ipfs link to domain logo in Domain structure (#1886) Signed-off-by: Arjentix --- core/benches/validation.rs | 1 + data_model/src/lib.rs | 133 +++++++++++++++++- .../src/public_blockchain/mod.rs | 5 + 3 files changed, 138 insertions(+), 1 deletion(-) diff --git a/core/benches/validation.rs b/core/benches/validation.rs index 0d231c091fc..28c2abdbd1e 100644 --- a/core/benches/validation.rs +++ b/core/benches/validation.rs @@ -203,6 +203,7 @@ fn validate_blocks(criterion: &mut Criterion) { accounts, asset_definitions, metadata: Metadata::new(), + logo: None, }; let mut domains = BTreeMap::new(); domains.insert(domain_id, domain); diff --git a/data_model/src/lib.rs b/data_model/src/lib.rs index f5fdb7d5297..80881969985 100644 --- a/data_model/src/lib.rs +++ b/data_model/src/lib.rs @@ -1679,7 +1679,7 @@ pub mod domain { //! This module contains [`Domain`](`crate::domain::Domain`) structure and related implementations and trait implementations. #[cfg(not(feature = "std"))] - use alloc::{collections::btree_map, format, string::String, vec::Vec}; + use alloc::{borrow::ToOwned, collections::btree_map, format, string::String, vec::Vec}; use core::{cmp::Ordering, fmt, str::FromStr}; #[cfg(feature = "std")] use std::collections::btree_map; @@ -1724,6 +1724,7 @@ pub mod domain { .collect(), asset_definitions: btree_map::BTreeMap::default(), metadata: Metadata::new(), + logo: None, } } } @@ -1739,6 +1740,8 @@ pub mod domain { pub asset_definitions: AssetDefinitionsMap, /// Metadata of this domain as a key-value store. pub metadata: Metadata, + /// IPFS link to domain logo + pub logo: Option, } impl PartialOrd for Domain { @@ -1763,6 +1766,7 @@ pub mod domain { accounts: AccountsMap::new(), asset_definitions: AssetDefinitionsMap::new(), metadata: Metadata::new(), + logo: None, } } @@ -1773,6 +1777,7 @@ pub mod domain { accounts: AccountsMap::new(), asset_definitions: AssetDefinitionsMap::new(), metadata: Metadata::new(), + logo: None, } } @@ -1787,6 +1792,7 @@ pub mod domain { accounts: accounts_map, asset_definitions: AssetDefinitionsMap::new(), metadata: Metadata::new(), + logo: None, } } } @@ -1804,6 +1810,90 @@ pub mod domain { } } + /// Represents path in IPFS. Performs some checks to ensure path validity. + /// + /// Should be constructed with `from_str()` method. + #[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, + )] + pub struct IpfsPath(String); + + impl FromStr for IpfsPath { + type Err = ParseError; + + fn from_str(string: &str) -> Result { + let mut subpath = string.split('/'); + let path_segment = subpath.next().ok_or(ParseError { + reason: "Impossible error: first value of str::split() always has value", + })?; + + if path_segment.is_empty() { + let root_type = subpath.next().ok_or(ParseError { + reason: "Expected root type, but nothing found", + })?; + let key = subpath.next().ok_or(ParseError { + reason: "Expected at least one content id", + })?; + + match root_type { + "ipfs" | "ipld" => Self::check_cid(key)?, + "ipns" => (), + _ => { + return Err(ParseError { + reason: "Unexpected root type. Expected `ipfs`, `ipld` or `ipns`", + }) + } + } + } else { + // by default if there is no prefix it's an ipfs or ipld path + Self::check_cid(path_segment)?; + } + + for path in subpath { + Self::check_cid(path)?; + } + + Ok(IpfsPath(string.to_owned())) + } + } + + impl AsRef for IpfsPath { + fn as_ref(&self) -> &str { + &self.0 + } + } + + impl IpfsPath { + /// Instantly construct [`IpfsPath`] assuming the given `path` is valid. + #[inline] + pub fn test(path: String) -> Self { + Self(path) + } + + /// Superficially checks IPFS `cid` (Content Identifier) + #[inline] + fn check_cid(cid: &str) -> Result<(), ParseError> { + if cid.len() < 2 { + return Err(ParseError { + reason: "IPFS cid is too short", + }); + } + + Ok(()) + } + } + /// Identification of a [`Domain`]. #[derive( Debug, @@ -1863,6 +1953,47 @@ pub mod domain { pub mod prelude { pub use super::{Domain, GenesisDomain, Id as DomainId, GENESIS_DOMAIN_NAME}; } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn test_invalid_ipfs_path() { + assert!(matches!( + IpfsPath::from_str(""), + Err(err) if err.to_string() == "Expected root type, but nothing found" + )); + assert!(matches!( + IpfsPath::from_str("/ipld"), + Err(err) if err.to_string() == "Expected at least one content id" + )); + assert!(matches!( + IpfsPath::from_str("/ipfs/a"), + Err(err) if err.to_string() == "IPFS cid is too short" + )); + assert!(matches!( + IpfsPath::from_str("/ipfsssss/QmQqzMTavQgT4f4T5v6PWBp7XNKtoPmC9jvn12WPT3gkSE"), + Err(err) if err.to_string() == "Unexpected root type. Expected `ipfs`, `ipld` or `ipns`" + )); + } + + #[test] + #[allow(clippy::expect_used)] + fn test_valid_ipfs_path() { + // Valid paths + IpfsPath::from_str("QmQqzMTavQgT4f4T5v6PWBp7XNKtoPmC9jvn12WPT3gkSE") + .expect("Path without root should be valid"); + IpfsPath::from_str("/ipfs/QmQqzMTavQgT4f4T5v6PWBp7XNKtoPmC9jvn12WPT3gkSE") + .expect("Path with ipfs root should be valid"); + IpfsPath::from_str("/ipld/QmQqzMTavQgT4f4T5v6PWBp7XNKtoPmC9jvn12WPT3gkSE") + .expect("Path with ipld root should be valid"); + IpfsPath::from_str("/ipns/QmSrPmbaUKA3ZodhzPWZnpFgcPMFWF4QsxXbkWfEptTBJd") + .expect("Path with ipns root should be valid"); + IpfsPath::from_str("/ipfs/SomeFolder/SomeImage") + .expect("Path with folders should be valid"); + } + } } pub mod peer { diff --git a/permissions_validators/src/public_blockchain/mod.rs b/permissions_validators/src/public_blockchain/mod.rs index a1889ffb460..db92413896e 100644 --- a/permissions_validators/src/public_blockchain/mod.rs +++ b/permissions_validators/src/public_blockchain/mod.rs @@ -263,6 +263,7 @@ mod tests { )] .into(), metadata: Metadata::new(), + logo: None, }, )], [], @@ -361,6 +362,7 @@ mod tests { )] .into(), metadata: Metadata::new(), + logo: None, }, )], [], @@ -462,6 +464,7 @@ mod tests { )] .into(), metadata: Metadata::new(), + logo: None, }, )], [], @@ -695,6 +698,7 @@ mod tests { )] .into(), metadata: Metadata::new(), + logo: None, }, )], [], @@ -733,6 +737,7 @@ mod tests { )] .into(), metadata: Metadata::new(), + logo: None, }, )], [],