Skip to content

Commit f1916b8

Browse files
committed
adding error messages
1 parent c468f85 commit f1916b8

File tree

1 file changed

+114
-48
lines changed

1 file changed

+114
-48
lines changed

provider/core/src/resource.rs

Lines changed: 114 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,16 @@ impl ResourceKeyHash {
3636
///
3737
/// ```
3838
/// # use icu_provider::prelude::ResourceKey;
39-
/// const key: ResourceKey = icu_provider::resource_key!("foo/bar@1");
39+
/// const K: ResourceKey = icu_provider::resource_key!("foo/bar@1");
4040
/// ```
4141
///
4242
/// The path string has to contain at least one `/`, and end with `@` followed by one or more ASCII
4343
/// digits. Paths do not contain characters other than ASCII letters and digits, `_`, `/`, `=`, and
44-
/// `@`. Invalid paths are compile-time errors (as [`resource_key!`] is `const`).
44+
/// `@`. Invalid paths are compile-time errors (as [`resource_key!`] uses `const`).
4545
///
46-
/// ```compile_fail
46+
/// ```compile_fail,E0080
4747
/// # use icu_provider::prelude::ResourceKey;
48-
/// const key: ResourceKey = icu_provider::resource_key!("foobar@1");
48+
/// const K: ResourceKey = icu_provider::resource_key!("foobar@1");
4949
/// ```
5050
#[derive(PartialEq, Eq, Copy, Clone)]
5151
pub struct ResourceKey {
@@ -114,9 +114,10 @@ impl ResourceKey {
114114
}
115115

116116
#[doc(hidden)]
117-
pub const fn try_new(path: &'static str) -> Option<Self> {
117+
// Error is a str of the expected character class and the index where it wasn't encountered
118+
pub const fn construct_internal(path: &'static str) -> Result<Self, (&'static str, usize)> {
118119
if path.len() < leading_tag!().len() + trailing_tag!().len() {
119-
return None;
120+
return Err(("tag", 0));
120121
}
121122
// Start and end of the untagged part
122123
let start = leading_tag!().len();
@@ -126,46 +127,75 @@ impl ResourceKey {
126127
let mut i = 0;
127128
while i < leading_tag!().len() {
128129
if path.as_bytes()[i] != leading_tag!().as_bytes()[i] {
129-
return None;
130+
return Err(("tag", 0));
130131
}
131132
i += 1;
132133
}
133134
i = 0;
134135
while i < trailing_tag!().len() {
135136
if path.as_bytes()[end + i] != trailing_tag!().as_bytes()[i] {
136-
return None;
137+
return Err(("tag", end + 1));
137138
}
138139
i += 1;
139140
}
140141

141-
// Approximate regex: \w+(/\w+)*@\d+
142-
// State 0 = start of string
143-
// State 1 = after first character
144-
// State 2 = after a slash
145-
// State 3 = after a character after a slash
146-
// State 4 = after @
147-
// State 5 = after a digit after @
142+
// Regex: [a-zA-Z0-9=_]+(/[a-zA-Z0-9=_]+)*@[0-9]+
143+
enum State {
144+
Start,
145+
AfterChar,
146+
AfterSlash,
147+
AfterCharAfterSlash,
148+
AfterAt,
149+
AfterDigit,
150+
}
151+
use State::*;
148152
i = start;
149-
let mut state = 0;
150-
while i < end {
151-
state = match (state, path.as_bytes()[i]) {
152-
(0 | 1, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | b'=') => 1,
153-
(1, b'/') => 2,
154-
(2 | 3, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | b'=') => 3,
155-
(3, b'/') => 2,
156-
(3, b'@') => 4,
157-
(4 | 5, b'0'..=b'9') => 5,
158-
_ => return None,
153+
let mut state = Start;
154+
loop {
155+
state = match (
156+
state,
157+
if i < end {
158+
Some(path.as_bytes()[i])
159+
} else {
160+
None
161+
},
162+
) {
163+
(Start, Some(b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | b'=')) => AfterChar,
164+
(Start, _) => return Err(("[a-zA-Z0-9=_]", i)),
165+
166+
(AfterChar, Some(b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | b'=')) => {
167+
AfterChar
168+
}
169+
(AfterChar, Some(b'/')) => AfterSlash,
170+
(AfterChar, _) => return Err(("[a-zA-z0-9=_/]", i)),
171+
172+
(AfterSlash, Some(b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | b'=')) => {
173+
AfterCharAfterSlash
174+
}
175+
(AfterSlash, _) => return Err(("[a-zA-Z0-9=_]", i)),
176+
177+
(
178+
AfterCharAfterSlash,
179+
Some(b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | b'='),
180+
) => AfterCharAfterSlash,
181+
(AfterCharAfterSlash, Some(b'/')) => AfterSlash,
182+
(AfterCharAfterSlash, Some(b'@')) => AfterAt,
183+
(AfterCharAfterSlash, _) => return Err(("[a-zA-z0-9=_/@]", i)),
184+
185+
(AfterAt, Some(b'0'..=b'9')) => AfterDigit,
186+
(AfterAt, _) => return Err(("[0-9]", i)),
187+
188+
(AfterDigit, Some(b'0'..=b'9')) => AfterDigit,
189+
(AfterDigit, Some(_)) => return Err(("[0-9]", i)),
190+
(AfterDigit, None) => {
191+
return Ok(Self {
192+
path,
193+
hash: ResourceKeyHash::compute_from_str(path),
194+
})
195+
}
159196
};
160197
i += 1;
161198
}
162-
if state != 5 {
163-
return None;
164-
}
165-
Some(Self {
166-
path,
167-
hash: ResourceKeyHash::compute_from_str(path),
168-
})
169199
}
170200

171201
/// Gets the last path component of a [`ResourceKey`] without the version suffix.
@@ -234,27 +264,56 @@ impl ResourceKey {
234264
#[test]
235265
fn test_path_syntax() {
236266
// Valid keys:
237-
assert!(ResourceKey::try_new(tagged!("hello/world@1")).is_some());
238-
assert!(ResourceKey::try_new(tagged!("hello/world/foo@1")).is_some());
239-
assert!(ResourceKey::try_new(tagged!("hello/world@999")).is_some());
240-
assert!(ResourceKey::try_new(tagged!("hello_world/foo@1")).is_some());
241-
assert!(ResourceKey::try_new(tagged!("hello_458/world@1")).is_some());
267+
assert!(ResourceKey::construct_internal(tagged!("hello/world@1")).is_ok());
268+
assert!(ResourceKey::construct_internal(tagged!("hello/world/foo@1")).is_ok());
269+
assert!(ResourceKey::construct_internal(tagged!("hello/world@999")).is_ok());
270+
assert!(ResourceKey::construct_internal(tagged!("hello_world/foo@1")).is_ok());
271+
assert!(ResourceKey::construct_internal(tagged!("hello_458/world@1")).is_ok());
242272

243273
// No slash:
244-
assert!(ResourceKey::try_new(tagged!("hello_world@1")).is_none());
274+
assert_eq!(
275+
ResourceKey::construct_internal(tagged!("hello_world@1")),
276+
Err(("[a-zA-z0-9=_/]", 25))
277+
);
245278

246279
// No version:
247-
assert!(ResourceKey::try_new(tagged!("hello/world")).is_none());
248-
assert!(ResourceKey::try_new(tagged!("hello/world@")).is_none());
249-
assert!(ResourceKey::try_new(tagged!("hello/world@foo")).is_none());
280+
assert_eq!(
281+
ResourceKey::construct_internal(tagged!("hello/world")),
282+
Err(("[a-zA-z0-9=_/@]", 25))
283+
);
284+
285+
assert_eq!(
286+
ResourceKey::construct_internal(tagged!("hello/world@")),
287+
Err(("[0-9]", 26))
288+
);
289+
assert_eq!(
290+
ResourceKey::construct_internal(tagged!("hello/world@foo")),
291+
Err(("[0-9]", 26))
292+
);
293+
assert_eq!(
294+
ResourceKey::construct_internal(tagged!("hello/world@1foo")),
295+
Err(("[0-9]", 27))
296+
);
250297

251298
// Invalid characters:
252-
assert!(ResourceKey::try_new(tagged!("你好/世界@1")).is_none());
299+
assert_eq!(
300+
ResourceKey::construct_internal(tagged!("你好/世界@1")),
301+
Err(("[a-zA-Z0-9=_]", 14))
302+
);
253303

254304
// Invalid tag:
255-
assert!(ResourceKey::try_new(concat!("hello/world@1", trailing_tag!())).is_none());
256-
assert!(ResourceKey::try_new(concat!(leading_tag!(), "hello/world@1")).is_none());
257-
assert!(ResourceKey::try_new("hello/world@1").is_none());
305+
assert_eq!(
306+
ResourceKey::construct_internal(concat!("hello/world@1", trailing_tag!())),
307+
Err(("tag", 0))
308+
);
309+
assert_eq!(
310+
ResourceKey::construct_internal(concat!(leading_tag!(), "hello/world@1")),
311+
Err(("tag", 27))
312+
);
313+
assert_eq!(
314+
ResourceKey::construct_internal("hello/world@1"),
315+
Err(("tag", 0))
316+
);
258317
}
259318

260319
/// See [`ResourceKey`].
@@ -263,9 +322,16 @@ macro_rules! resource_key {
263322
($path:expr) => {{
264323
// Force the ResourceKey into a const context
265324
const RESOURCE_KEY_MACRO_CONST: $crate::ResourceKey = {
266-
match $crate::ResourceKey::try_new($crate::tagged!($path)) {
267-
Some(v) => v,
268-
None => panic!(concat!("Invalid resource key: ", $path)),
325+
match $crate::ResourceKey::construct_internal($crate::tagged!($path)) {
326+
Ok(v) => v,
327+
Err(_) => panic!(concat!("Invalid resource key: ", $path)),
328+
// TODO Once formatting is const:
329+
// Err((expected, index)) => panic!(
330+
// "Invalid resource key {:?}: expected {:?}, found {:?} ",
331+
// $path,
332+
// expected,
333+
// $crate::tagged!($path).get(index..))
334+
// );
269335
}
270336
};
271337
RESOURCE_KEY_MACRO_CONST

0 commit comments

Comments
 (0)