/rust/registry/src/index.crates.io-1949cf8c6b5b557f/uuid-0.8.2/src/parser/mod.rs
Line | Count | Source |
1 | | // Copyright 2013-2014 The Rust Project Developers. |
2 | | // Copyright 2018 The Uuid Project Developers. |
3 | | // |
4 | | // See the COPYRIGHT file at the top-level directory of this distribution. |
5 | | // |
6 | | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
7 | | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
8 | | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
9 | | // option. This file may not be copied, modified, or distributed |
10 | | // except according to those terms. |
11 | | |
12 | | //! [`Uuid`] parsing constructs and utilities. |
13 | | //! |
14 | | //! [`Uuid`]: ../struct.Uuid.html |
15 | | |
16 | | pub(crate) mod error; |
17 | | pub(crate) use self::error::Error; |
18 | | |
19 | | use crate::{adapter, Uuid}; |
20 | | |
21 | | /// Check if the length matches any of the given criteria lengths. |
22 | 9.63k | fn len_matches_any(len: usize, crits: &[usize]) -> bool { |
23 | 13.0k | for crit in crits { |
24 | 11.8k | if len == *crit { |
25 | 8.48k | return true; |
26 | 3.38k | } |
27 | | } |
28 | | |
29 | 1.15k | false |
30 | 9.63k | } |
31 | | |
32 | | /// Check if the length matches any criteria lengths in the given range |
33 | | /// (inclusive). |
34 | | #[allow(dead_code)] |
35 | 0 | fn len_matches_range(len: usize, min: usize, max: usize) -> bool { |
36 | 0 | for crit in min..=max { |
37 | 0 | if len == crit { |
38 | 0 | return true; |
39 | 0 | } |
40 | | } |
41 | | |
42 | 0 | false |
43 | 0 | } |
44 | | |
45 | | // Accumulated length of each hyphenated group in hex digits. |
46 | | const ACC_GROUP_LENS: [usize; 5] = [8, 12, 16, 20, 32]; |
47 | | |
48 | | // Length of each hyphenated group in hex digits. |
49 | | const GROUP_LENS: [usize; 5] = [8, 4, 4, 4, 12]; |
50 | | |
51 | | impl Uuid { |
52 | | /// Parses a `Uuid` from a string of hexadecimal digits with optional |
53 | | /// hyphens. |
54 | | /// |
55 | | /// Any of the formats generated by this module (simple, hyphenated, urn) |
56 | | /// are supported by this parsing function. |
57 | 9.64k | pub fn parse_str(mut input: &str) -> Result<Uuid, crate::Error> { |
58 | | // Ensure length is valid for any of the supported formats |
59 | 9.64k | let len = input.len(); |
60 | | |
61 | 9.64k | if len == adapter::Urn::LENGTH && input.starts_with("urn:uuid:") { |
62 | 1 | input = &input[9..]; |
63 | 9.63k | } else if !len_matches_any( |
64 | 9.63k | len, |
65 | 9.63k | &[adapter::Hyphenated::LENGTH, adapter::Simple::LENGTH], |
66 | 9.63k | ) { |
67 | 1.15k | Err(Error::InvalidLength { |
68 | 1.15k | expected: error::ExpectedLength::Any(&[ |
69 | 1.15k | adapter::Hyphenated::LENGTH, |
70 | 1.15k | adapter::Simple::LENGTH, |
71 | 1.15k | ]), |
72 | 1.15k | found: len, |
73 | 1.15k | })?; |
74 | 8.48k | } |
75 | | |
76 | | // `digit` counts only hexadecimal digits, `i_char` counts all chars. |
77 | 8.49k | let mut digit = 0; |
78 | 8.49k | let mut group = 0; |
79 | 8.49k | let mut acc = 0; |
80 | 8.49k | let mut buffer = [0u8; 16]; |
81 | | |
82 | 288k | for (i_char, chr) in input.bytes().enumerate() { |
83 | 288k | if digit as usize >= adapter::Simple::LENGTH && group != 4 { |
84 | 21 | if group == 0 { |
85 | 1 | Err(Error::InvalidLength { |
86 | 1 | expected: error::ExpectedLength::Any(&[ |
87 | 1 | adapter::Hyphenated::LENGTH, |
88 | 1 | adapter::Simple::LENGTH, |
89 | 1 | ]), |
90 | 1 | found: len, |
91 | 1 | })?; |
92 | 20 | } |
93 | | |
94 | 20 | Err(Error::InvalidGroupCount { |
95 | 20 | expected: error::ExpectedLength::Any(&[1, 5]), |
96 | 20 | found: group + 1, |
97 | 20 | })?; |
98 | 288k | } |
99 | | |
100 | 288k | if digit % 2 == 0 { |
101 | | // First digit of the byte. |
102 | 160k | match chr { |
103 | | // Calulate upper half. |
104 | 128k | b'0'..=b'9' => acc = chr - b'0', |
105 | 32.8k | b'a'..=b'f' => acc = chr - b'a' + 10, |
106 | 145 | b'A'..=b'F' => acc = chr - b'A' + 10, |
107 | | // Found a group delimiter |
108 | | b'-' => { |
109 | | // TODO: remove the u8 cast |
110 | | // BODY: this only needed until we switch to |
111 | | // ParseError |
112 | 32.5k | if ACC_GROUP_LENS[group] as u8 != digit { |
113 | | // Calculate how many digits this group consists of |
114 | | // in the input. |
115 | 360 | let found = if group > 0 { |
116 | | // TODO: remove the u8 cast |
117 | | // BODY: this only needed until we switch to |
118 | | // ParseError |
119 | 348 | digit - ACC_GROUP_LENS[group - 1] as u8 |
120 | | } else { |
121 | 12 | digit |
122 | | }; |
123 | | |
124 | 360 | Err(Error::InvalidGroupLength { |
125 | 360 | expected: error::ExpectedLength::Exact( |
126 | 360 | GROUP_LENS[group], |
127 | 360 | ), |
128 | 360 | found: found as usize, |
129 | 360 | group, |
130 | 360 | })?; |
131 | 32.2k | } |
132 | | // Next group, decrement digit, it is incremented again |
133 | | // at the bottom. |
134 | 32.2k | group += 1; |
135 | 32.2k | digit -= 1; |
136 | | } |
137 | | _ => { |
138 | 102 | Err(Error::InvalidCharacter { |
139 | 102 | expected: "0123456789abcdefABCDEF-", |
140 | 102 | found: input[i_char..].chars().next().unwrap(), |
141 | 102 | index: i_char, |
142 | 102 | urn: error::UrnPrefix::Optional, |
143 | 102 | })?; |
144 | | } |
145 | | } |
146 | | } else { |
147 | | // Second digit of the byte, shift the upper half. |
148 | 128k | acc *= 16; |
149 | 128k | match chr { |
150 | 127k | b'0'..=b'9' => acc += chr - b'0', |
151 | 25.2k | b'a'..=b'f' => acc += chr - b'a' + 10, |
152 | 310 | b'A'..=b'F' => acc += chr - b'A' + 10, |
153 | | b'-' => { |
154 | | // The byte isn't complete yet. |
155 | 194 | let found = if group > 0 { |
156 | | // TODO: remove the u8 cast |
157 | | // BODY: this only needed until we switch to |
158 | | // ParseError |
159 | 112 | digit - ACC_GROUP_LENS[group - 1] as u8 |
160 | | } else { |
161 | 82 | digit |
162 | | }; |
163 | | |
164 | 194 | Err(Error::InvalidGroupLength { |
165 | 194 | expected: error::ExpectedLength::Exact( |
166 | 194 | GROUP_LENS[group], |
167 | 194 | ), |
168 | 194 | found: found as usize, |
169 | 194 | group, |
170 | 194 | })?; |
171 | | } |
172 | | _ => { |
173 | 787 | Err(Error::InvalidCharacter { |
174 | 787 | expected: "0123456789abcdefABCDEF-", |
175 | 787 | found: input[i_char..].chars().next().unwrap(), |
176 | 787 | index: i_char, |
177 | 787 | urn: error::UrnPrefix::Optional, |
178 | 787 | })?; |
179 | | } |
180 | | } |
181 | 127k | buffer[(digit / 2) as usize] = acc; |
182 | | } |
183 | 287k | digit += 1; |
184 | | } |
185 | | |
186 | | // Now check the last group. |
187 | | // TODO: remove the u8 cast |
188 | | // BODY: this only needed until we switch to |
189 | | // ParseError |
190 | 7.02k | if ACC_GROUP_LENS[4] as u8 != digit { |
191 | 14 | Err(Error::InvalidGroupLength { |
192 | 14 | expected: error::ExpectedLength::Exact(GROUP_LENS[4]), |
193 | 14 | found: (digit as usize - ACC_GROUP_LENS[3]), |
194 | 14 | group, |
195 | 14 | })?; |
196 | 7.01k | } |
197 | | |
198 | 7.01k | Ok(Uuid::from_bytes(buffer)) |
199 | 9.64k | } |
200 | | } |
201 | | |
202 | | #[cfg(test)] |
203 | | mod tests { |
204 | | use super::*; |
205 | | use crate::{adapter, std::string::ToString, test_util}; |
206 | | |
207 | | #[test] |
208 | | fn test_parse_uuid_v4() { |
209 | | const EXPECTED_UUID_LENGTHS: error::ExpectedLength = |
210 | | error::ExpectedLength::Any(&[ |
211 | | adapter::Hyphenated::LENGTH, |
212 | | adapter::Simple::LENGTH, |
213 | | ]); |
214 | | |
215 | | const EXPECTED_GROUP_COUNTS: error::ExpectedLength = |
216 | | error::ExpectedLength::Any(&[1, 5]); |
217 | | |
218 | | const EXPECTED_CHARS: &'static str = "0123456789abcdefABCDEF-"; |
219 | | |
220 | | // Invalid |
221 | | assert_eq!( |
222 | | Uuid::parse_str("").map_err(crate::Error::expect_parser), |
223 | | Err(Error::InvalidLength { |
224 | | expected: EXPECTED_UUID_LENGTHS, |
225 | | found: 0, |
226 | | }) |
227 | | ); |
228 | | |
229 | | assert_eq!( |
230 | | Uuid::parse_str("!").map_err(crate::Error::expect_parser), |
231 | | Err(Error::InvalidLength { |
232 | | expected: EXPECTED_UUID_LENGTHS, |
233 | | found: 1 |
234 | | }) |
235 | | ); |
236 | | |
237 | | assert_eq!( |
238 | | Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E45") |
239 | | .map_err(crate::Error::expect_parser), |
240 | | Err(Error::InvalidLength { |
241 | | expected: EXPECTED_UUID_LENGTHS, |
242 | | found: 37, |
243 | | }) |
244 | | ); |
245 | | |
246 | | assert_eq!( |
247 | | Uuid::parse_str("F9168C5E-CEB2-4faa-BBF-329BF39FA1E4") |
248 | | .map_err(crate::Error::expect_parser), |
249 | | Err(Error::InvalidLength { |
250 | | expected: EXPECTED_UUID_LENGTHS, |
251 | | found: 35 |
252 | | }) |
253 | | ); |
254 | | |
255 | | assert_eq!( |
256 | | Uuid::parse_str("F9168C5E-CEB2-4faa-BGBF-329BF39FA1E4") |
257 | | .map_err(crate::Error::expect_parser), |
258 | | Err(Error::InvalidCharacter { |
259 | | expected: EXPECTED_CHARS, |
260 | | found: 'G', |
261 | | index: 20, |
262 | | urn: error::UrnPrefix::Optional, |
263 | | }) |
264 | | ); |
265 | | |
266 | | assert_eq!( |
267 | | Uuid::parse_str("F9168C5E-CEB2F4faaFB6BFF329BF39FA1E4") |
268 | | .map_err(crate::Error::expect_parser), |
269 | | Err(Error::InvalidGroupCount { |
270 | | expected: EXPECTED_GROUP_COUNTS, |
271 | | found: 2 |
272 | | }) |
273 | | ); |
274 | | |
275 | | assert_eq!( |
276 | | Uuid::parse_str("F9168C5E-CEB2-4faaFB6BFF329BF39FA1E4") |
277 | | .map_err(crate::Error::expect_parser), |
278 | | Err(Error::InvalidGroupCount { |
279 | | expected: EXPECTED_GROUP_COUNTS, |
280 | | found: 3, |
281 | | }) |
282 | | ); |
283 | | |
284 | | assert_eq!( |
285 | | Uuid::parse_str("F9168C5E-CEB2-4faa-B6BFF329BF39FA1E4") |
286 | | .map_err(crate::Error::expect_parser), |
287 | | Err(Error::InvalidGroupCount { |
288 | | expected: EXPECTED_GROUP_COUNTS, |
289 | | found: 4, |
290 | | }) |
291 | | ); |
292 | | |
293 | | assert_eq!( |
294 | | Uuid::parse_str("F9168C5E-CEB2-4faa") |
295 | | .map_err(crate::Error::expect_parser), |
296 | | Err(Error::InvalidLength { |
297 | | expected: EXPECTED_UUID_LENGTHS, |
298 | | found: 18, |
299 | | }) |
300 | | ); |
301 | | |
302 | | assert_eq!( |
303 | | Uuid::parse_str("F9168C5E-CEB2-4faaXB6BFF329BF39FA1E4") |
304 | | .map_err(crate::Error::expect_parser), |
305 | | Err(Error::InvalidCharacter { |
306 | | expected: EXPECTED_CHARS, |
307 | | found: 'X', |
308 | | index: 18, |
309 | | urn: error::UrnPrefix::Optional, |
310 | | }) |
311 | | ); |
312 | | |
313 | | assert_eq!( |
314 | | Uuid::parse_str("F9168C5E-CEB-24fa-eB6BFF32-BF39FA1E4") |
315 | | .map_err(crate::Error::expect_parser), |
316 | | Err(Error::InvalidGroupLength { |
317 | | expected: error::ExpectedLength::Exact(4), |
318 | | found: 3, |
319 | | group: 1, |
320 | | }) |
321 | | ); |
322 | | // (group, found, expecting) |
323 | | // |
324 | | assert_eq!( |
325 | | Uuid::parse_str("01020304-1112-2122-3132-41424344") |
326 | | .map_err(crate::Error::expect_parser), |
327 | | Err(Error::InvalidGroupLength { |
328 | | expected: error::ExpectedLength::Exact(12), |
329 | | found: 8, |
330 | | group: 4, |
331 | | }) |
332 | | ); |
333 | | |
334 | | assert_eq!( |
335 | | Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c") |
336 | | .map_err(crate::Error::expect_parser), |
337 | | Err(Error::InvalidLength { |
338 | | expected: EXPECTED_UUID_LENGTHS, |
339 | | found: 31, |
340 | | }) |
341 | | ); |
342 | | |
343 | | assert_eq!( |
344 | | Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c88") |
345 | | .map_err(crate::Error::expect_parser), |
346 | | Err(Error::InvalidLength { |
347 | | expected: EXPECTED_UUID_LENGTHS, |
348 | | found: 33, |
349 | | }) |
350 | | ); |
351 | | |
352 | | assert_eq!( |
353 | | Uuid::parse_str("67e5504410b1426f9247bb680e5fe0cg8") |
354 | | .map_err(crate::Error::expect_parser), |
355 | | Err(Error::InvalidLength { |
356 | | expected: EXPECTED_UUID_LENGTHS, |
357 | | found: 33, |
358 | | }) |
359 | | ); |
360 | | |
361 | | assert_eq!( |
362 | | Uuid::parse_str("67e5504410b1426%9247bb680e5fe0c8") |
363 | | .map_err(crate::Error::expect_parser), |
364 | | Err(Error::InvalidCharacter { |
365 | | expected: EXPECTED_CHARS, |
366 | | found: '%', |
367 | | index: 15, |
368 | | urn: error::UrnPrefix::Optional, |
369 | | }) |
370 | | ); |
371 | | |
372 | | assert_eq!( |
373 | | Uuid::parse_str("231231212212423424324323477343246663") |
374 | | .map_err(crate::Error::expect_parser), |
375 | | Err(Error::InvalidLength { |
376 | | expected: EXPECTED_UUID_LENGTHS, |
377 | | found: 36, |
378 | | }) |
379 | | ); |
380 | | |
381 | | // Valid |
382 | | assert!(Uuid::parse_str("00000000000000000000000000000000").is_ok()); |
383 | | assert!(Uuid::parse_str("67e55044-10b1-426f-9247-bb680e5fe0c8").is_ok()); |
384 | | assert!(Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4").is_ok()); |
385 | | assert!(Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c8").is_ok()); |
386 | | assert!(Uuid::parse_str("01020304-1112-2122-3132-414243444546").is_ok()); |
387 | | assert!(Uuid::parse_str( |
388 | | "urn:uuid:67e55044-10b1-426f-9247-bb680e5fe0c8" |
389 | | ) |
390 | | .is_ok()); |
391 | | |
392 | | // Nil |
393 | | let nil = Uuid::nil(); |
394 | | assert_eq!( |
395 | | Uuid::parse_str("00000000000000000000000000000000").unwrap(), |
396 | | nil |
397 | | ); |
398 | | assert_eq!( |
399 | | Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap(), |
400 | | nil |
401 | | ); |
402 | | |
403 | | // Round-trip |
404 | | let uuid_orig = test_util::new(); |
405 | | let orig_str = uuid_orig.to_string(); |
406 | | let uuid_out = Uuid::parse_str(&orig_str).unwrap(); |
407 | | assert_eq!(uuid_orig, uuid_out); |
408 | | |
409 | | // Test error reporting |
410 | | assert_eq!( |
411 | | Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c") |
412 | | .map_err(crate::Error::expect_parser), |
413 | | Err(Error::InvalidLength { |
414 | | expected: EXPECTED_UUID_LENGTHS, |
415 | | found: 31, |
416 | | }) |
417 | | ); |
418 | | assert_eq!( |
419 | | Uuid::parse_str("67e550X410b1426f9247bb680e5fe0cd") |
420 | | .map_err(crate::Error::expect_parser), |
421 | | Err(Error::InvalidCharacter { |
422 | | expected: EXPECTED_CHARS, |
423 | | found: 'X', |
424 | | index: 6, |
425 | | urn: error::UrnPrefix::Optional, |
426 | | }) |
427 | | ); |
428 | | assert_eq!( |
429 | | Uuid::parse_str("67e550-4105b1426f9247bb680e5fe0c") |
430 | | .map_err(crate::Error::expect_parser), |
431 | | Err(Error::InvalidGroupLength { |
432 | | expected: error::ExpectedLength::Exact(8), |
433 | | found: 6, |
434 | | group: 0, |
435 | | }) |
436 | | ); |
437 | | assert_eq!( |
438 | | Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF1-02BF39FA1E4") |
439 | | .map_err(crate::Error::expect_parser), |
440 | | Err(Error::InvalidGroupLength { |
441 | | expected: error::ExpectedLength::Exact(4), |
442 | | found: 5, |
443 | | group: 3, |
444 | | }) |
445 | | ); |
446 | | } |
447 | | } |