use core::{ convert::{From, Infallible}, fmt::{Debug, Display, Error as FmtError, Formatter}, str::FromStr, }; use ibc_types_identifier::{validate_connection_identifier, IdentifierError}; use crate::prelude::*; // TODO: where does ChainId live? /// This type is subject to future changes. /// /// TODO: ChainId validation is not standardized yet. /// `is_epoch_format` will most likely be replaced by validate_chain_id()-style function. /// See: . /// /// Also, contrast with tendermint-rs `ChainId` type. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ChainId { pub id: String, pub version: u64, } impl ChainId { /// Creates a new `ChainId` given a chain name and an epoch number. /// /// The returned `ChainId` will have the format: `{chain name}-{epoch number}`. /// ``` /// # use ibc_types_core_connection::ChainId; /// let epoch_number = 10; /// let id = ChainId::new("chainA".to_string(), epoch_number); /// assert_eq!(id.version(), epoch_number); /// ``` pub fn new(name: String, version: u64) -> Self { Self { id: format!("{name}-{version}"), version, } } pub fn from_string(id: &str) -> Self { let version = if Self::is_epoch_format(id) { Self::chain_version(id) } else { 0 }; Self { id: id.to_string(), version, } } /// Get a reference to the underlying string. pub fn as_str(&self) -> &str { &self.id } // TODO: this should probably be named epoch_number. /// Extract the version from this chain identifier. pub fn version(&self) -> u64 { self.version } /// Extract the version from the given chain identifier. /// ``` /// # use ibc_types_core_connection::ChainId; /// assert_eq!(ChainId::chain_version("chain--a-0"), 0); /// assert_eq!(ChainId::chain_version("ibc-10"), 10); /// assert_eq!(ChainId::chain_version("cosmos-hub-97"), 97); /// assert_eq!(ChainId::chain_version("testnet-helloworld-2"), 2); /// ``` pub fn chain_version(chain_id: &str) -> u64 { if !ChainId::is_epoch_format(chain_id) { return 0; } let split: Vec<_> = chain_id.split('-').collect(); split .last() .expect("get revision number from chain_id") .parse() .unwrap_or(0) } /// is_epoch_format() checks if a chain_id is in the format required for parsing epochs /// The chainID must be in the form: `{chainID}-{version}` /// ``` /// # use ibc_types_core_connection::ChainId; /// assert_eq!(ChainId::is_epoch_format("chainA-0"), false); /// assert_eq!(ChainId::is_epoch_format("chainA"), false); /// assert_eq!(ChainId::is_epoch_format("chainA-1"), true); /// assert_eq!(ChainId::is_epoch_format("c-1"), true); /// ``` pub fn is_epoch_format(chain_id: &str) -> bool { let re = safe_regex::regex!(br".*[^-]-[1-9][0-9]*"); re.is_match(chain_id.as_bytes()) } /// with_version() checks if a chain_id is in the format required for parsing epochs, and if so /// replaces it's version with the specified version /// ``` /// # use ibc_types_core_connection::ChainId; /// assert_eq!(ChainId::new("chainA".to_string(), 1).with_version(2), ChainId::new("chainA".to_string(), 2)); /// assert_eq!("chain1".parse::().unwrap().with_version(2), "chain1".parse::().unwrap()); /// ``` pub fn with_version(mut self, version: u64) -> Self { if Self::is_epoch_format(&self.id) { self.id = { let mut split: Vec<&str> = self.id.split('-').collect(); let version = version.to_string(); *split.last_mut().unwrap() = &version; split.join("-") }; self.version = version; } self } } impl FromStr for ChainId { type Err = Infallible; fn from_str(id: &str) -> Result { Ok(Self::from_string(id)) } } impl Display for ChainId { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { write!(f, "{}", self.id) } } impl From for tendermint::chain::Id { fn from(id: ChainId) -> Self { tendermint::chain::Id::from_str(id.as_str()).unwrap() } } impl From for ChainId { fn from(id: tendermint::chain::Id) -> Self { ChainId::from_str(id.as_str()).unwrap() } } impl Default for ChainId { fn default() -> Self { "defaultChainId".to_string().parse().unwrap() } } impl From for ChainId { fn from(value: String) -> Self { Self::from_string(&value) } } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ConnectionId(pub String); impl ConnectionId { /// Builds a new connection identifier. Connection identifiers are deterministically formed from /// two elements: a prefix `prefix`, and a monotonically increasing `counter`; these are /// separated by a dash "-". The prefix is currently determined statically (see /// `ConnectionId::prefix()`) so this method accepts a single argument, the `counter`. /// /// ``` /// # use ibc_types_core_connection::ConnectionId; /// let conn_id = ConnectionId::new(11); /// assert_eq!(&conn_id, "connection-11"); /// ``` pub fn new(identifier: u64) -> Self { let id = format!("{}-{}", Self::prefix(), identifier); Self::from_str(id.as_str()).unwrap() } /// Returns the static prefix to be used across all connection identifiers. pub fn prefix() -> &'static str { "connection" } /// Get this identifier as a borrowed `&str` pub fn as_str(&self) -> &str { &self.0 } /// Get this identifier as a borrowed byte slice pub fn as_bytes(&self) -> &[u8] { self.0.as_bytes() } } /// This implementation provides a `to_string` method. impl Display for ConnectionId { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { write!(f, "{}", self.0) } } impl FromStr for ConnectionId { type Err = IdentifierError; fn from_str(s: &str) -> Result { validate_connection_identifier(s).map(|_| Self(s.to_string())) } } impl Default for ConnectionId { fn default() -> Self { Self::new(0) } } /// Equality check against string literal (satisfies &ConnectionId == &str). /// ``` /// # use core::str::FromStr; /// # use ibc_types_core_connection::ConnectionId; /// let conn_id = ConnectionId::from_str("connectionId-0"); /// assert!(conn_id.is_ok()); /// conn_id.map(|id| {assert_eq!(&id, "connectionId-0")}); /// ``` impl PartialEq for ConnectionId { fn eq(&self, other: &str) -> bool { self.as_str().eq(other) } }