ibc_grpc_server/
types.rs

1use std::fmt::{Display, Formatter};
2use std::ops::Deref;
3use std::str::{from_utf8, FromStr};
4
5use ibc::core::ics24_host::{path, validate::validate_identifier, Path as IbcPath};
6
7use crate::{Result, ServerError};
8
9macro_rules! impl_into_path_for {
10    ($($path:ty),+) => {
11        $(impl From<$path> for Path {
12            fn from(ibc_path: $path) -> Self {
13                Self::try_from(ibc_path.to_string()).unwrap() // safety - `IbcPath`s are correct-by-construction
14            }
15        })+
16    };
17}
18
19impl_into_path_for!(
20    path::ClientTypePath,
21    path::ClientStatePath,
22    path::ClientConsensusStatePath,
23    path::ConnectionsPath,
24    path::ClientConnectionsPath,
25    path::ChannelEndsPath,
26    path::SeqSendsPath,
27    path::SeqRecvsPath,
28    path::SeqAcksPath,
29    path::CommitmentsPath,
30    path::ReceiptsPath,
31    path::AcksPath
32);
33
34/// A new type representing a valid ICS024 identifier.
35/// Implements `Deref<Target=String>`.
36#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
37pub struct Identifier(String);
38
39impl Identifier {
40    /// Identifiers MUST be non-empty (of positive integer length).
41    /// Identifiers MUST consist of characters in one of the following
42    /// categories only:
43    /// * Alphanumeric
44    /// * `.`, `_`, `+`, `-`, `#`
45    /// * `[`, `]`, `<`, `>`
46    fn validate(s: impl AsRef<str>) -> Result<()> {
47        let s = s.as_ref();
48
49        // give a `min` parameter of 0 here to allow id's of arbitrary
50        // length as inputs; `validate_identifier` itself checks for
51        // empty inputs and returns an error as appropriate
52        validate_identifier(s, 0, s.len()).map_err(ServerError::ValidateIdentifier)
53    }
54}
55
56impl Deref for Identifier {
57    type Target = String;
58
59    fn deref(&self) -> &Self::Target {
60        &self.0
61    }
62}
63
64impl TryFrom<String> for Identifier {
65    type Error = ServerError;
66
67    fn try_from(s: String) -> Result<Self> {
68        Identifier::validate(&s).map(|_| Self(s))
69    }
70}
71
72impl Display for Identifier {
73    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
74        write!(f, "{}", self.0)
75    }
76}
77
78#[derive(Copy, Clone, Debug, PartialEq, Eq)]
79pub enum StoreHeight {
80    Latest,
81    Stable(u64),
82}
83
84/// A new type representing a valid ICS024 `Path`.
85#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
86pub struct Path(Vec<Identifier>);
87
88impl Path {
89    pub fn starts_with(&self, prefix: &Path) -> bool {
90        KeyPrefix::from(self)
91            .as_ref()
92            .starts_with(KeyPrefix::from(prefix).as_ref())
93    }
94}
95
96impl TryFrom<String> for Path {
97    type Error = ServerError;
98
99    fn try_from(s: String) -> Result<Self> {
100        let mut identifiers = vec![];
101        let parts = s.split('/'); // split will never return an empty iterator
102        for part in parts {
103            identifiers.push(Identifier::try_from(part.to_owned())?);
104        }
105        Ok(Self(identifiers))
106    }
107}
108
109impl TryFrom<&[u8]> for Path {
110    type Error = ServerError;
111
112    fn try_from(value: &[u8]) -> Result<Self> {
113        let s = from_utf8(value).map_err(ServerError::FromUtf8)?;
114        s.to_owned().try_into()
115    }
116}
117
118impl From<Identifier> for Path {
119    fn from(id: Identifier) -> Self {
120        Self(vec![id])
121    }
122}
123
124impl Display for Path {
125    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
126        write!(
127            f,
128            "{}",
129            self.0
130                .iter()
131                .map(|iden| iden.as_str().to_owned())
132                .collect::<Vec<String>>()
133                .join("/")
134        )
135    }
136}
137
138impl TryFrom<Path> for IbcPath {
139    type Error = path::PathError;
140
141    fn try_from(path: Path) -> std::result::Result<Self, Self::Error> {
142        Self::from_str(path.to_string().as_str())
143    }
144}
145
146impl From<IbcPath> for Path {
147    fn from(ibc_path: IbcPath) -> Self {
148        Self::try_from(ibc_path.to_string()).unwrap() // safety - `IbcPath`s are correct-by-construction
149    }
150}
151
152#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone)]
153pub struct KeyPrefix(String);
154
155impl From<&Path> for KeyPrefix {
156    fn from(ibc_path: &Path) -> Self {
157        let strs: Vec<String> = ibc_path.0.iter().map(|x| x.to_string()).collect();
158        KeyPrefix(strs.join("/"))
159    }
160}
161
162impl From<&KeyPrefix> for String {
163    fn from(key_prefix: &KeyPrefix) -> Self {
164        key_prefix.0.clone()
165    }
166}
167
168impl AsRef<[u8]> for KeyPrefix {
169    fn as_ref(&self) -> &[u8] {
170        self.0.as_bytes()
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use ibc::core::ics02_client::client_type::ClientType;
177    use ibc::core::ics24_host::{identifier::ClientId, path::ClientTypePath};
178
179    use super::*;
180
181    #[test]
182    fn starts_with_test_case01() {
183        let dst_path: Path = "clients/07-tendermint-1033".to_owned().try_into().unwrap();
184        let client_id = ClientId::new(ClientType::Tendermint, 103344).unwrap();
185        let path = ClientTypePath(client_id);
186        let src_path: Path = Path::from(path);
187
188        assert!(src_path.starts_with(&dst_path));
189    }
190
191    #[test]
192
193    fn starts_with_test_case02() {
194        use ibc::core::ics24_host::{identifier::ClientId, path::ClientStatePath};
195        let dst_path = "clients/07-tend".to_owned().try_into().unwrap();
196        let client_id = ClientId::new(ClientType::Tendermint, 1033).unwrap();
197        let path = ClientStatePath(client_id);
198        let src_path: Path = Path::from(path);
199
200        assert!(src_path.starts_with(&dst_path));
201    }
202
203    #[test]
204
205    fn starts_with_test_case03() {
206        use ibc::core::ics24_host::{identifier::ClientId, path::ClientStatePath};
207        let dst_path = "clients".to_owned().try_into().unwrap();
208        let client_id = ClientId::new(ClientType::Tendermint, 1033).unwrap();
209        let path = ClientStatePath(client_id);
210        let src_path: Path = Path::from(path);
211
212        assert!(src_path.starts_with(&dst_path));
213    }
214
215    #[test]
216
217    fn test_key_prefix_from_path_case01() {
218        let src = String::from("connections");
219        let src_path: Path = src
220            .clone()
221            .try_into()
222            .expect("'connections' expected to be a valid Path");
223        let prefix_key = KeyPrefix::from(&src_path);
224        assert_eq!(src, String::from(&prefix_key))
225    }
226
227    #[test]
228
229    fn test_key_prefix_from_path_case02() {
230        let src = String::from("channelEnds/ports");
231        let src_path: Path = src
232            .clone()
233            .try_into()
234            .expect("'channelEnds/ports' expected to be a valid Path");
235        let prefix_key = KeyPrefix::from(&src_path);
236        assert_eq!(src, String::from(&prefix_key))
237    }
238
239    #[test]
240
241    fn test_key_prefix_from_path_case03() {
242        let src = format!("clients/{}/consensusStates", "123client_id");
243        let src_path = src
244            .clone()
245            .try_into()
246            .expect("'channelEnds/ports' expected to be a valid Path");
247        let prefix_key = KeyPrefix::from(&src_path);
248        assert_eq!(src, String::from(&prefix_key))
249    }
250}