@@ -36,16 +36,16 @@ impl ResourceKeyHash {
36
36
///
37
37
/// ```
38
38
/// # 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");
40
40
/// ```
41
41
///
42
42
/// The path string has to contain at least one `/`, and end with `@` followed by one or more ASCII
43
43
/// 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`).
45
45
///
46
- /// ```compile_fail
46
+ /// ```compile_fail,E0080
47
47
/// # 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");
49
49
/// ```
50
50
#[ derive( PartialEq , Eq , Copy , Clone ) ]
51
51
pub struct ResourceKey {
@@ -114,9 +114,10 @@ impl ResourceKey {
114
114
}
115
115
116
116
#[ 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 ) > {
118
119
if path. len ( ) < leading_tag ! ( ) . len ( ) + trailing_tag ! ( ) . len ( ) {
119
- return None ;
120
+ return Err ( ( "tag" , 0 ) ) ;
120
121
}
121
122
// Start and end of the untagged part
122
123
let start = leading_tag ! ( ) . len ( ) ;
@@ -126,46 +127,75 @@ impl ResourceKey {
126
127
let mut i = 0 ;
127
128
while i < leading_tag ! ( ) . len ( ) {
128
129
if path. as_bytes ( ) [ i] != leading_tag ! ( ) . as_bytes ( ) [ i] {
129
- return None ;
130
+ return Err ( ( "tag" , 0 ) ) ;
130
131
}
131
132
i += 1 ;
132
133
}
133
134
i = 0 ;
134
135
while i < trailing_tag ! ( ) . len ( ) {
135
136
if path. as_bytes ( ) [ end + i] != trailing_tag ! ( ) . as_bytes ( ) [ i] {
136
- return None ;
137
+ return Err ( ( "tag" , end + 1 ) ) ;
137
138
}
138
139
i += 1 ;
139
140
}
140
141
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 :: * ;
148
152
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
+ }
159
196
} ;
160
197
i += 1 ;
161
198
}
162
- if state != 5 {
163
- return None ;
164
- }
165
- Some ( Self {
166
- path,
167
- hash : ResourceKeyHash :: compute_from_str ( path) ,
168
- } )
169
199
}
170
200
171
201
/// Gets the last path component of a [`ResourceKey`] without the version suffix.
@@ -234,27 +264,56 @@ impl ResourceKey {
234
264
#[ test]
235
265
fn test_path_syntax ( ) {
236
266
// 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 ( ) ) ;
242
272
243
273
// 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
+ ) ;
245
278
246
279
// 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
+ ) ;
250
297
251
298
// 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
+ ) ;
253
303
254
304
// 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
+ ) ;
258
317
}
259
318
260
319
/// See [`ResourceKey`].
@@ -263,9 +322,16 @@ macro_rules! resource_key {
263
322
( $path: expr) => { {
264
323
// Force the ResourceKey into a const context
265
324
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
+ // );
269
335
}
270
336
} ;
271
337
RESOURCE_KEY_MACRO_CONST
0 commit comments