Skip to main content

uuid/
parser.rs

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
16use crate::{
17    error::*,
18    std::{convert::TryFrom, str},
19    Uuid,
20};
21
22#[cfg(feature = "std")]
23use crate::std::string::String;
24
25impl str::FromStr for Uuid {
26    type Err = Error;
27
28    fn from_str(uuid_str: &str) -> Result<Self, Self::Err> {
29        Uuid::parse_str(uuid_str)
30    }
31}
32
33impl TryFrom<&'_ str> for Uuid {
34    type Error = Error;
35
36    fn try_from(uuid_str: &'_ str) -> Result<Self, Self::Error> {
37        Uuid::parse_str(uuid_str)
38    }
39}
40
41#[cfg(feature = "std")]
42impl TryFrom<String> for Uuid {
43    type Error = Error;
44
45    fn try_from(uuid_str: String) -> Result<Self, Self::Error> {
46        Uuid::try_from(uuid_str.as_ref())
47    }
48}
49
50impl Uuid {
51    /// Parses a `Uuid` from a string of hexadecimal digits with optional
52    /// hyphens.
53    ///
54    /// Any of the formats generated by this module (simple, hyphenated, urn,
55    /// Microsoft GUID) are supported by this parsing function.
56    ///
57    /// Prefer [`try_parse`] unless you need detailed user-facing diagnostics.
58    /// This method will be eventually deprecated in favor of `try_parse`.
59    ///
60    /// # Examples
61    ///
62    /// Parse a hyphenated UUID:
63    ///
64    /// ```
65    /// # use uuid::{Uuid, Version, Variant};
66    /// # fn main() -> Result<(), uuid::Error> {
67    /// let uuid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000")?;
68    ///
69    /// assert_eq!(Some(Version::Random), uuid.get_version());
70    /// assert_eq!(Variant::RFC4122, uuid.get_variant());
71    /// # Ok(())
72    /// # }
73    /// ```
74    ///
75    /// [`try_parse`]: #method.try_parse
76    pub fn parse_str(input: &str) -> Result<Uuid, Error> {
77        try_parse(input.as_bytes())
78            .map(Uuid::from_bytes)
79            .map_err(InvalidUuid::into_err)
80    }
81
82    /// Parses a `Uuid` from a string of hexadecimal digits with optional
83    /// hyphens.
84    ///
85    /// This function is similar to [`parse_str`], in fact `parse_str` shares
86    /// the same underlying parser. The difference is that if `try_parse`
87    /// fails, it won't generate very useful error messages. The `parse_str`
88    /// function will eventually be deprecated in favor of `try_parse`.
89    ///
90    /// To parse a UUID from a byte stream instead of a UTF8 string, see
91    /// [`try_parse_ascii`].
92    ///
93    /// # Examples
94    ///
95    /// Parse a hyphenated UUID:
96    ///
97    /// ```
98    /// # use uuid::{Uuid, Version, Variant};
99    /// # fn main() -> Result<(), uuid::Error> {
100    /// let uuid = Uuid::try_parse("550e8400-e29b-41d4-a716-446655440000")?;
101    ///
102    /// assert_eq!(Some(Version::Random), uuid.get_version());
103    /// assert_eq!(Variant::RFC4122, uuid.get_variant());
104    /// # Ok(())
105    /// # }
106    /// ```
107    ///
108    /// [`parse_str`]: #method.parse_str
109    /// [`try_parse_ascii`]: #method.try_parse_ascii
110    pub const fn try_parse(input: &str) -> Result<Uuid, Error> {
111        Self::try_parse_ascii(input.as_bytes())
112    }
113
114    /// Parses a `Uuid` from a string of hexadecimal digits with optional
115    /// hyphens.
116    ///
117    /// The input is expected to be a string of ASCII characters. This method
118    /// can be more convenient than [`try_parse`] if the UUID is being
119    /// parsed from a byte stream instead of from a UTF8 string.
120    ///
121    /// # Examples
122    ///
123    /// Parse a hyphenated UUID:
124    ///
125    /// ```
126    /// # use uuid::{Uuid, Version, Variant};
127    /// # fn main() -> Result<(), uuid::Error> {
128    /// let uuid = Uuid::try_parse_ascii(b"550e8400-e29b-41d4-a716-446655440000")?;
129    ///
130    /// assert_eq!(Some(Version::Random), uuid.get_version());
131    /// assert_eq!(Variant::RFC4122, uuid.get_variant());
132    /// # Ok(())
133    /// # }
134    /// ```
135    ///
136    /// [`try_parse`]: #method.try_parse
137    pub const fn try_parse_ascii(input: &[u8]) -> Result<Uuid, Error> {
138        match try_parse(input) {
139            Ok(bytes) => Ok(Uuid::from_bytes(bytes)),
140            // If parsing fails then we don't know exactly what went wrong
141            // In this case, we just return a generic error
142            Err(_) => Err(Error(ErrorKind::ParseOther)),
143        }
144    }
145}
146
147const fn try_parse(input: &'_ [u8]) -> Result<[u8; 16], InvalidUuid<'_>> {
148    match (input.len(), input) {
149        // Inputs of 32 bytes must be a non-hyphenated UUID
150        (32, s) => parse_simple(s, true),
151        // Hyphenated UUIDs may be wrapped in various ways:
152        // - `{UUID}` for braced UUIDs
153        // - `urn:uuid:UUID` for URNs
154        // - `UUID` for a regular hyphenated UUID
155        (36, s)
156        | (38, [b'{', s @ .., b'}'])
157        | (45, [b'u', b'r', b'n', b':', b'u', b'u', b'i', b'd', b':', s @ ..]) => {
158            parse_hyphenated(s)
159        }
160        // Any other shaped input is immediately invalid
161        _ => Err(InvalidUuid(input, RequestedUuid::Any)),
162    }
163}
164
165#[inline]
166#[allow(dead_code)]
167pub(crate) const fn parse_braced(input: &'_ [u8]) -> Result<[u8; 16], InvalidUuid<'_>> {
168    if let (38, [b'{', s @ .., b'}']) = (input.len(), input) {
169        parse_hyphenated(s)
170    } else {
171        Err(InvalidUuid(input, RequestedUuid::Braced))
172    }
173}
174
175#[inline]
176#[allow(dead_code)]
177pub(crate) const fn parse_urn(input: &'_ [u8]) -> Result<[u8; 16], InvalidUuid<'_>> {
178    if let (45, [b'u', b'r', b'n', b':', b'u', b'u', b'i', b'd', b':', s @ ..]) =
179        (input.len(), input)
180    {
181        parse_hyphenated(s)
182    } else {
183        Err(InvalidUuid(input, RequestedUuid::Urn))
184    }
185}
186
187#[inline]
188pub(crate) const fn parse_simple(
189    s: &'_ [u8],
190    speculative: bool,
191) -> Result<[u8; 16], InvalidUuid<'_>> {
192    // This length check here removes all other bounds
193    // checks in this function
194    if s.len() != 32 {
195        return Err(InvalidUuid(
196            s,
197            if speculative {
198                RequestedUuid::Any
199            } else {
200                RequestedUuid::Simple
201            },
202        ));
203    }
204
205    // Copy the hex characters into a fixed-size array so the decoder's
206    // bounds are statically known.
207    let mut hex = [0u8; 32];
208    let mut i = 0;
209    while i < 32 {
210        hex[i] = s[i];
211        i += 1;
212    }
213
214    match decode_hex32(&hex) {
215        Some(buf) => Ok(buf),
216        None => Err(InvalidUuid(
217            s,
218            if speculative {
219                RequestedUuid::Any
220            } else {
221                RequestedUuid::Simple
222            },
223        )),
224    }
225}
226
227#[inline]
228pub(crate) const fn parse_hyphenated(s: &'_ [u8]) -> Result<[u8; 16], InvalidUuid<'_>> {
229    // This length check here removes all other bounds
230    // checks in this function
231    if s.len() != 36 {
232        return Err(InvalidUuid(s, RequestedUuid::Hyphenated));
233    }
234
235    // The hex characters are split into five groups separated by hyphens.
236    // The indexes we're interested in are:
237    //
238    // uuid     : 936da01f-9abd-4d9d-80c7-02af85c822a8
239    //            |   |   ||   ||   ||   ||   |   |
240    // hyphens  : |   |   8|  13|  18|  23|   |   |
241    // positions: 0   4    9   14   19   24  28  32
242
243    // First, ensure the hyphens appear in the right places
244    match [s[8], s[13], s[18], s[23]] {
245        [b'-', b'-', b'-', b'-'] => {}
246        _ => return Err(InvalidUuid(s, RequestedUuid::Hyphenated)),
247    }
248
249    // Gather the hex characters, skipping the four hyphens, so they're
250    // contiguous for the decoder.
251    let mut hex = [0u8; 32];
252    let mut i = 0;
253    while i < 8 {
254        hex[i] = s[i];
255        i += 1;
256    }
257    while i < 12 {
258        hex[i] = s[i + 1];
259        i += 1;
260    }
261    while i < 16 {
262        hex[i] = s[i + 2];
263        i += 1;
264    }
265    while i < 20 {
266        hex[i] = s[i + 3];
267        i += 1;
268    }
269    while i < 32 {
270        hex[i] = s[i + 4];
271        i += 1;
272    }
273
274    match decode_hex32(&hex) {
275        Some(buf) => Ok(buf),
276        None => Err(InvalidUuid(s, RequestedUuid::Hyphenated)),
277    }
278}
279
280/// Decodes 32 hexadecimal ASCII characters into 16 bytes, returning `None` if
281/// any character is not a valid hexadecimal digit.
282#[inline]
283const fn decode_hex32(hex: &[u8; 32]) -> Option<[u8; 16]> {
284    let mut nibbles = [0u8; 32];
285    let mut bad = 0u8;
286
287    let mut i = 0;
288    while i < 32 {
289        let c = hex[i];
290
291        // '0'..='9' map to 0..=9; 'a'..='f' and 'A'..='F' (via `| 0x20`) map to
292        // 0..=5, offset by 10 to land in 10..=15.
293        let digit = c.wrapping_sub(b'0');
294        let alpha = (c | 0x20).wrapping_sub(b'a');
295
296        let is_digit = digit < 10;
297        let is_alpha = alpha < 6;
298
299        nibbles[i] = if is_digit {
300            digit
301        } else {
302            alpha.wrapping_add(10)
303        };
304
305        bad |= if is_digit | is_alpha { 0 } else { 1 };
306
307        i += 1;
308    }
309
310    if bad != 0 {
311        return None;
312    }
313
314    let mut buf = [0u8; 16];
315    let mut j = 0;
316    while j < 16 {
317        buf[j] = (nibbles[j * 2] << 4) | nibbles[j * 2 + 1];
318        j += 1;
319    }
320
321    Some(buf)
322}
323
324#[cfg(test)]
325mod tests {
326    use super::*;
327    use crate::{
328        fmt::*,
329        std::{str::FromStr, string::ToString},
330        tests::some_uuid_iter,
331    };
332
333    #[test]
334    fn test_parse_valid() {
335        let from_hyphenated = Uuid::parse_str("67e55044-10b1-426f-9247-bb680e5fe0c8").unwrap();
336        let from_simple = Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c8").unwrap();
337        let from_urn = Uuid::parse_str("urn:uuid:67e55044-10b1-426f-9247-bb680e5fe0c8").unwrap();
338        let from_guid = Uuid::parse_str("{67e55044-10b1-426f-9247-bb680e5fe0c8}").unwrap();
339
340        assert_eq!(from_hyphenated, from_simple);
341        assert_eq!(from_hyphenated, from_urn);
342        assert_eq!(from_hyphenated, from_guid);
343
344        assert!(Uuid::parse_str("00000000000000000000000000000000").is_ok());
345        assert!(Uuid::parse_str("67e55044-10b1-426f-9247-bb680e5fe0c8").is_ok());
346        assert!(Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4").is_ok());
347        assert!(Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c8").is_ok());
348        assert!(Uuid::parse_str("01020304-1112-2122-3132-414243444546").is_ok());
349        assert!(Uuid::parse_str("urn:uuid:67e55044-10b1-426f-9247-bb680e5fe0c8").is_ok());
350        assert!(Uuid::parse_str("{6d93bade-bd9f-4e13-8914-9474e1e3567b}").is_ok());
351
352        // Nil
353        let nil = Uuid::nil();
354        assert_eq!(
355            Uuid::parse_str("00000000000000000000000000000000").unwrap(),
356            nil
357        );
358        assert_eq!(
359            Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap(),
360            nil
361        );
362    }
363
364    #[test]
365    fn test_parse_invalid() {
366        // Invalid
367        assert_eq!(
368            Uuid::parse_str(""),
369            Err(Error(ErrorKind::ParseLength { len: 0 }))
370        );
371
372        assert_eq!(
373            Uuid::parse_str("{}"),
374            Err(Error(ErrorKind::ParseGroupCount { count: 1 }))
375        );
376
377        assert_eq!(
378            Uuid::parse_str("!"),
379            Err(Error(ErrorKind::ParseChar {
380                character: '!',
381                index: 0,
382            }))
383        );
384
385        assert_eq!(
386            Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E45"),
387            Err(Error(ErrorKind::ParseGroupLength {
388                group: 4,
389                len: 13,
390                index: 25,
391            }))
392        );
393
394        assert_eq!(
395            Uuid::parse_str("F9168C5E-CEB2-4faa-BBF-329BF39FA1E4"),
396            Err(Error(ErrorKind::ParseGroupLength {
397                group: 3,
398                len: 3,
399                index: 20,
400            }))
401        );
402
403        assert_eq!(
404            Uuid::parse_str("F9168C5E-CEB2-4faa-BGBF-329BF39FA1E4"),
405            Err(Error(ErrorKind::ParseChar {
406                character: 'G',
407                index: 20,
408            }))
409        );
410
411        assert_eq!(
412            Uuid::parse_str("F9168C5E-CEB2F4faaFB6BFF329BF39FA1E4"),
413            Err(Error(ErrorKind::ParseGroupCount { count: 2 }))
414        );
415
416        assert_eq!(
417            Uuid::parse_str("F9168C5E-CEB2-4faaFB6BFF329BF39FA1E4"),
418            Err(Error(ErrorKind::ParseGroupCount { count: 3 }))
419        );
420
421        assert_eq!(
422            Uuid::parse_str("F9168C5E-CEB2-4faa-B6BFF329BF39FA1E4"),
423            Err(Error(ErrorKind::ParseGroupCount { count: 4 }))
424        );
425
426        assert_eq!(
427            Uuid::parse_str("F9168C5E-CEB2-4faa"),
428            Err(Error(ErrorKind::ParseGroupCount { count: 3 }))
429        );
430
431        assert_eq!(
432            Uuid::parse_str("F9168C5E-CEB2-4faaXB6BFF329BF39FA1E4"),
433            Err(Error(ErrorKind::ParseChar {
434                character: 'X',
435                index: 18,
436            }))
437        );
438
439        assert_eq!(
440            Uuid::parse_str("{F9168C5E-CEB2-4faa9B6BFF329BF39FA1E41"),
441            Err(Error(ErrorKind::ParseChar {
442                character: '{',
443                index: 0,
444            }))
445        );
446
447        assert_eq!(
448            Uuid::parse_str("{F9168C5E-CEB2-4faa9B6BFF329BF39FA1E41}"),
449            Err(Error(ErrorKind::ParseGroupCount { count: 3 }))
450        );
451
452        assert_eq!(
453            Uuid::parse_str("F9168C5E-CEB-24fa-eB6BFF32-BF39FA1E4"),
454            Err(Error(ErrorKind::ParseGroupLength {
455                group: 1,
456                len: 3,
457                index: 10,
458            }))
459        );
460
461        // // (group, found, expecting)
462        assert_eq!(
463            Uuid::parse_str("01020304-1112-2122-3132-41424344"),
464            Err(Error(ErrorKind::ParseGroupLength {
465                group: 4,
466                len: 8,
467                index: 25,
468            }))
469        );
470
471        assert_eq!(
472            Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c"),
473            Err(Error(ErrorKind::ParseLength { len: 31 }))
474        );
475
476        assert_eq!(
477            Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c88"),
478            Err(Error(ErrorKind::ParseLength { len: 33 }))
479        );
480
481        assert_eq!(
482            Uuid::parse_str("67e5504410b1426f9247bb680e5fe0cg8"),
483            Err(Error(ErrorKind::ParseChar {
484                character: 'g',
485                index: 31,
486            }))
487        );
488
489        assert_eq!(
490            Uuid::parse_str("67e5504410b1426%9247bb680e5fe0c8"),
491            Err(Error(ErrorKind::ParseChar {
492                character: '%',
493                index: 15,
494            }))
495        );
496
497        assert_eq!(
498            Uuid::parse_str("231231212212423424324323477343246663"),
499            Err(Error(ErrorKind::ParseGroupCount { count: 1 }))
500        );
501
502        assert_eq!(
503            Uuid::parse_str("{00000000000000000000000000000000}"),
504            Err(Error(ErrorKind::ParseGroupCount { count: 1 }))
505        );
506
507        assert_eq!(
508            Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c"),
509            Err(Error(ErrorKind::ParseLength { len: 31 }))
510        );
511
512        assert_eq!(
513            Uuid::parse_str("67e550X410b1426f9247bb680e5fe0cd"),
514            Err(Error(ErrorKind::ParseChar {
515                character: 'X',
516                index: 6,
517            }))
518        );
519
520        assert_eq!(
521            Uuid::parse_str("67e550-4105b1426f9247bb680e5fe0c"),
522            Err(Error(ErrorKind::ParseGroupCount { count: 2 }))
523        );
524
525        assert_eq!(
526            Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF1-02BF39FA1E4"),
527            Err(Error(ErrorKind::ParseGroupLength {
528                group: 3,
529                len: 5,
530                index: 20,
531            }))
532        );
533
534        assert_eq!(
535            Uuid::parse_str("\u{bcf3c}"),
536            Err(Error(ErrorKind::ParseChar {
537                character: '\u{bcf3c}',
538                index: 0,
539            }))
540        );
541
542        assert_eq!(
543            Err(Error(ErrorKind::ParseLength { len: 0 })),
544            Hyphenated::from_str("")
545        );
546
547        assert_eq!(
548            Err(Error(ErrorKind::ParseGroupCount { count: 1 })),
549            Hyphenated::from_str("550e8400e29b41d4a716446655440000")
550        );
551
552        assert_eq!(
553            Err(Error(ErrorKind::ParseChar {
554                character: '-',
555                index: 8
556            })),
557            Simple::from_str("550e8400-e29b-41d4-a716-446655440000")
558        );
559
560        assert_eq!(
561            Err(Error(ErrorKind::ParseChar {
562                character: '5',
563                index: 0
564            })),
565            Urn::from_str("550e8400-e29b-41d4-a716-446655440000")
566        );
567        assert_eq!(
568            Err(Error(ErrorKind::ParseChar {
569                character: ':',
570                index: 0
571            })),
572            Urn::from_str(":550e8400-e29b-41d4-a716-446655440000")
573        );
574
575        assert_eq!(
576            Err(Error(ErrorKind::ParseChar {
577                character: '5',
578                index: 0
579            })),
580            Braced::from_str("550e8400-e29b-41d4-a716-446655440000")
581        );
582        assert_eq!(
583            Err(Error(ErrorKind::ParseChar {
584                character: '{',
585                index: 1
586            })),
587            Braced::from_str("{{550e8400-e29b-41d4-a716-446655440000}}")
588        );
589
590        // Unicode
591        assert_eq!(
592            Uuid::from_str("{6e0----------9=4O-0e5\u{14}e0c4\u{ec2f}8}"),
593            Err(Error(ErrorKind::ParseChar {
594                character: '=',
595                index: 15,
596            }))
597        );
598
599        assert_eq!(
600            Uuid::from_str("urn:uuid:urae0c8"),
601            Err(Error(ErrorKind::ParseChar {
602                character: 'u',
603                index: 9,
604            }))
605        );
606    }
607
608    #[test]
609    fn test_roundtrip_default() {
610        for uuid_orig in some_uuid_iter() {
611            let orig_str = uuid_orig.to_string();
612            let uuid_out = Uuid::parse_str(&orig_str).unwrap();
613            assert_eq!(uuid_orig, uuid_out);
614        }
615    }
616
617    #[test]
618    fn test_roundtrip_hyphenated() {
619        for uuid_orig in some_uuid_iter() {
620            let orig_str = uuid_orig.hyphenated().to_string();
621            let uuid_out = Uuid::parse_str(&orig_str).unwrap();
622            assert_eq!(uuid_orig, uuid_out);
623        }
624    }
625
626    #[test]
627    fn test_roundtrip_simple() {
628        for uuid_orig in some_uuid_iter() {
629            let orig_str = uuid_orig.simple().to_string();
630            let uuid_out = Uuid::parse_str(&orig_str).unwrap();
631            assert_eq!(uuid_orig, uuid_out);
632        }
633    }
634
635    #[test]
636    fn test_roundtrip_urn() {
637        for uuid_orig in some_uuid_iter() {
638            let orig_str = uuid_orig.urn().to_string();
639            let uuid_out = Uuid::parse_str(&orig_str).unwrap();
640            assert_eq!(uuid_orig, uuid_out);
641        }
642    }
643
644    #[test]
645    fn test_roundtrip_braced() {
646        for uuid_orig in some_uuid_iter() {
647            let orig_str = uuid_orig.braced().to_string();
648            let uuid_out = Uuid::parse_str(&orig_str).unwrap();
649            assert_eq!(uuid_orig, uuid_out);
650        }
651    }
652
653    #[test]
654    fn test_roundtrip_parse_urn() {
655        for uuid_orig in some_uuid_iter() {
656            let orig_str = uuid_orig.urn().to_string();
657            let uuid_out = Uuid::from_bytes(parse_urn(orig_str.as_bytes()).unwrap());
658            assert_eq!(uuid_orig, uuid_out);
659        }
660    }
661
662    #[test]
663    fn test_roundtrip_parse_braced() {
664        for uuid_orig in some_uuid_iter() {
665            let orig_str = uuid_orig.braced().to_string();
666            let uuid_out = Uuid::from_bytes(parse_braced(orig_str.as_bytes()).unwrap());
667            assert_eq!(uuid_orig, uuid_out);
668        }
669    }
670
671    #[test]
672    fn test_try_parse_ascii_non_utf8() {
673        assert!(Uuid::try_parse_ascii(b"67e55044-10b1-426f-9247-bb680e5\0e0c8").is_err());
674    }
675}