Skip to content

Commit ff871fb

Browse files
committed
feat: improve set of IdentifierError variants
Firstly, add IdentifierError::InvalidNumericSuffix error indicating identifiers which should be in ‘prefix-number’ having invalid numeric suffix because a) it’s missing, b) is not a number or c) has leading zero. With that, update parse_chain_id_string to return that error when the revision number is invalid. Secondly, remove IdentifierError::Empty and ContainSeparator variants as they are redundant. The former is a special case of InvalidLength; the latter is a special case of InvalidCharacter. With that, remove length checking from validate_identifier_chars (which didn’t belong there anyway) and fix chain name checking in ChainId::from_str.
1 parent ccca3ff commit ff871fb

File tree

3 files changed

+41
-61
lines changed

3 files changed

+41
-61
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
- Remove IdentifierError::Empty and ContainSeparator variants as they are
2+
special cases of other existing errors.
3+
([\#942](https://github.com/cosmos/ibc-rs/pull/942))
4+
- Add IdentifierError::InvalidNumericSuffix to represent identifire
5+
which should have a numeric suffix not having one.
6+
([\#942](https://github.com/cosmos/ibc-rs/pull/942))

crates/ibc/src/core/ics24_host/identifier.rs

Lines changed: 30 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,10 @@ impl ChainId {
6262
/// assert_eq!(id.revision_number(), revision_number);
6363
/// ```
6464
pub fn new(name: &str, revision_number: u64) -> Result<Self, IdentifierError> {
65-
let prefix = name.trim();
66-
validate_identifier_chars(prefix)?;
67-
validate_identifier_length(prefix, 1, 43)?;
68-
let id = format!("{prefix}-{revision_number}");
65+
let name = name.trim();
66+
validate_channel_name(name)?;
6967
Ok(Self {
70-
id,
68+
id: format!("{name}-{revision_number}"),
7169
revision_number,
7270
})
7371
}
@@ -133,37 +131,35 @@ impl Display for ChainId {
133131
}
134132
}
135133

134+
/// Verifies that the channel name is valid.
135+
fn validate_channel_name(name: &str) -> Result<(), IdentifierError> {
136+
validate_identifier_chars(name)?;
137+
validate_identifier_length(name, 1, 43)
138+
}
139+
136140
/// Parses a string intended to represent a `ChainId` and, if successful,
137141
/// returns a tuple containing the chain name and revision number.
138142
fn parse_chain_id_string(chain_id_str: &str) -> Result<(&str, u64), IdentifierError> {
139-
let (name, rev_number_str) = match chain_id_str.rsplit_once('-') {
140-
Some((name, rev_number_str)) => (name, rev_number_str),
141-
None => {
142-
return Err(IdentifierError::InvalidCharacter {
143-
id: chain_id_str.to_string(),
144-
})
145-
}
146-
};
147-
148-
// Validates the chain name for allowed characters according to ICS-24.
149-
validate_identifier_chars(name)?;
143+
let (name, rev_num) =
144+
chain_id_str
145+
.rsplit_once('-')
146+
.ok_or_else(|| IdentifierError::InvalidNumericSuffix {
147+
id: chain_id_str.into(),
148+
})?;
149+
150+
validate_channel_name(name)?;
150151

151152
// Validates the revision number not to start with leading zeros, like "01".
152-
if rev_number_str.as_bytes().first() == Some(&b'0') && rev_number_str.len() > 1 {
153-
return Err(IdentifierError::InvalidCharacter {
154-
id: chain_id_str.to_string(),
155-
});
156-
}
157-
158-
// Parses the revision number string into a `u64` and checks its validity.
159-
let revision_number =
160-
rev_number_str
161-
.parse()
162-
.map_err(|_| IdentifierError::InvalidCharacter {
163-
id: chain_id_str.to_string(),
164-
})?;
153+
let rev_num = if rev_num.starts_with('0') && rev_num.len() > 1 {
154+
None
155+
} else {
156+
u64::from_str(rev_num).ok()
157+
}
158+
.ok_or_else(|| IdentifierError::InvalidNumericSuffix {
159+
id: chain_id_str.into(),
160+
})?;
165161

166-
Ok((name, revision_number))
162+
Ok((name, rev_num))
167163
}
168164

169165
#[cfg_attr(
@@ -496,21 +492,14 @@ impl PartialEq<str> for ChannelId {
496492
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
497493
#[derive(Debug, Display)]
498494
pub enum IdentifierError {
499-
/// identifier `{id}` cannot contain separator '/'
500-
ContainSeparator { id: String },
501-
/// identifier `{id}` has invalid length `{length}` must be between `{min}`-`{max}` characters
502-
InvalidLength {
503-
id: String,
504-
length: u64,
505-
min: u64,
506-
max: u64,
507-
},
495+
/// identifier `{id}` has invalid length must be between `{min}`-`{max}` characters
496+
InvalidLength { id: String, min: u64, max: u64 },
508497
/// identifier `{id}` must only contain alphanumeric characters or `.`, `_`, `+`, `-`, `#`, - `[`, `]`, `<`, `>`
509498
InvalidCharacter { id: String },
510499
/// identifier prefix `{prefix}` is invalid
511500
InvalidPrefix { prefix: String },
512-
/// identifier cannot be empty
513-
Empty,
501+
/// identifier `{id}` doesn’t end with a valid numeric suffix
502+
InvalidNumericSuffix { id: String },
514503
}
515504

516505
#[cfg(feature = "std")]

crates/ibc/src/core/ics24_host/identifier/validate.rs

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,12 @@ use super::IdentifierError as Error;
22
use crate::prelude::*;
33

44
/// Path separator (ie. forward slash '/')
5-
const PATH_SEPARATOR: char = '/';
65
const VALID_SPECIAL_CHARS: &str = "._+-#[]<>";
76

87
/// Checks if the identifier only contains valid characters as specified in the
98
/// [`ICS-24`](https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements#paths-identifiers-separators)]
109
/// spec.
1110
pub fn validate_identifier_chars(id: &str) -> Result<(), Error> {
12-
// Check identifier is not empty
13-
if id.is_empty() {
14-
return Err(Error::Empty);
15-
}
16-
17-
// Check identifier does not contain path separators
18-
if id.contains(PATH_SEPARATOR) {
19-
return Err(Error::ContainSeparator { id: id.into() });
20-
}
21-
2211
// Check that the identifier comprises only valid characters:
2312
// - Alphanumeric
2413
// - `.`, `_`, `+`, `-`, `#`
@@ -38,16 +27,12 @@ pub fn validate_identifier_chars(id: &str) -> Result<(), Error> {
3827
/// [`ICS-24`](https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements#paths-identifiers-separators)]
3928
/// spec.
4029
pub fn validate_identifier_length(id: &str, min: u64, max: u64) -> Result<(), Error> {
41-
assert!(max >= min);
30+
assert!(0 < min && min <= max);
4231

4332
// Check identifier length is between given min/max
44-
if (id.len() as u64) < min || id.len() as u64 > max {
45-
return Err(Error::InvalidLength {
46-
id: id.into(),
47-
length: id.len() as u64,
48-
min,
49-
max,
50-
});
33+
if !(min..=max).contains(&(id.len() as u64)) {
34+
let id = id.into();
35+
return Err(Error::InvalidLength { id, min, max });
5136
}
5237

5338
Ok(())
@@ -200,7 +185,7 @@ mod tests {
200185
#[test]
201186
fn parse_invalid_id_empty() {
202187
// invalid id empty
203-
let id = validate_identifier_chars("");
188+
let id = validate_identifier_length("", 1, 64);
204189
assert!(id.is_err())
205190
}
206191

0 commit comments

Comments
 (0)