Coverage Report

Created: 2025-11-16 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}