1 use crate::std::fmt; 2 3 /// A general error that can occur when working with UUIDs. 4 #[derive(Clone, Debug, Eq, Hash, PartialEq)] 5 pub struct Error(pub(crate) ErrorKind); 6 7 #[derive(Clone, Debug, Eq, Hash, PartialEq)] 8 pub(crate) enum ErrorKind { 9 /// Invalid character in the [`Uuid`] string. 10 /// 11 /// [`Uuid`]: ../struct.Uuid.html 12 Char { character: char, index: usize }, 13 /// A simple [`Uuid`] didn't contain 32 characters. 14 /// 15 /// [`Uuid`]: ../struct.Uuid.html 16 SimpleLength { len: usize }, 17 /// A byte array didn't contain 16 bytes 18 ByteLength { len: usize }, 19 /// A hyphenated [`Uuid`] didn't contain 5 groups 20 /// 21 /// [`Uuid`]: ../struct.Uuid.html 22 GroupCount { count: usize }, 23 /// A hyphenated [`Uuid`] had a group that wasn't the right length 24 /// 25 /// [`Uuid`]: ../struct.Uuid.html 26 GroupLength { 27 group: usize, 28 len: usize, 29 index: usize, 30 }, 31 /// The input was not a valid UTF8 string 32 InvalidUTF8, 33 /// Some other error occurred. 34 Other, 35 } 36 37 /// A string that is guaranteed to fail to parse to a [`Uuid`]. 38 /// 39 /// This type acts as a lightweight error indicator, suggesting 40 /// that the string cannot be parsed but offering no error 41 /// details. To get details, use `InvalidUuid::into_err`. 42 /// 43 /// [`Uuid`]: ../struct.Uuid.html 44 #[derive(Clone, Debug, Eq, Hash, PartialEq)] 45 pub struct InvalidUuid<'a>(pub(crate) &'a [u8]); 46 47 impl<'a> InvalidUuid<'a> { 48 /// Converts the lightweight error type into detailed diagnostics. into_err(self) -> Error49 pub fn into_err(self) -> Error { 50 // Check whether or not the input was ever actually a valid UTF8 string 51 let input_str = match std::str::from_utf8(self.0) { 52 Ok(s) => s, 53 Err(_) => return Error(ErrorKind::InvalidUTF8), 54 }; 55 56 let (uuid_str, offset, simple) = match input_str.as_bytes() { 57 [b'{', s @ .., b'}'] => (s, 1, false), 58 [b'u', b'r', b'n', b':', b'u', b'u', b'i', b'd', b':', s @ ..] => { 59 (s, "urn:uuid:".len(), false) 60 } 61 s => (s, 0, true), 62 }; 63 64 let mut hyphen_count = 0; 65 let mut group_bounds = [0; 4]; 66 67 // SAFETY: the byte array came from a valid utf8 string, 68 // and is aligned along char boundaries. 69 let uuid_str = unsafe { std::str::from_utf8_unchecked(uuid_str) }; 70 71 for (index, character) in uuid_str.char_indices() { 72 let byte = character as u8; 73 if character as u32 - byte as u32 > 0 { 74 // Multibyte char 75 return Error(ErrorKind::Char { 76 character, 77 index: index + offset + 1, 78 }); 79 } else if byte == b'-' { 80 // While we search, also count group breaks 81 if hyphen_count < 4 { 82 group_bounds[hyphen_count] = index; 83 } 84 hyphen_count += 1; 85 } else if !matches!(byte, b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F') { 86 // Non-hex char 87 return Error(ErrorKind::Char { 88 character: byte as char, 89 index: index + offset + 1, 90 }); 91 } 92 } 93 94 if hyphen_count == 0 && simple { 95 // This means that we tried and failed to parse a simple uuid. 96 // Since we verified that all the characters are valid, this means 97 // that it MUST have an invalid length. 98 Error(ErrorKind::SimpleLength { 99 len: input_str.len(), 100 }) 101 } else if hyphen_count != 4 { 102 // We tried to parse a hyphenated variant, but there weren't 103 // 5 groups (4 hyphen splits). 104 Error(ErrorKind::GroupCount { 105 count: hyphen_count + 1, 106 }) 107 } else { 108 // There are 5 groups, one of them has an incorrect length 109 const BLOCK_STARTS: [usize; 5] = [0, 9, 14, 19, 24]; 110 for i in 0..4 { 111 if group_bounds[i] != BLOCK_STARTS[i + 1] - 1 { 112 return Error(ErrorKind::GroupLength { 113 group: i, 114 len: group_bounds[i] - BLOCK_STARTS[i], 115 index: offset + BLOCK_STARTS[i] + 1, 116 }); 117 } 118 } 119 120 // The last group must be too long 121 Error(ErrorKind::GroupLength { 122 group: 4, 123 len: input_str.len() - BLOCK_STARTS[4], 124 index: offset + BLOCK_STARTS[4] + 1, 125 }) 126 } 127 } 128 } 129 130 // NOTE: This impl is part of the public API. Breaking changes to it should be carefully considered 131 impl fmt::Display for Error { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result132 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 133 match self.0 { 134 ErrorKind::Char { 135 character, index, .. 136 } => { 137 write!(f, "invalid character: expected an optional prefix of `urn:uuid:` followed by [0-9a-fA-F-], found `{}` at {}", character, index) 138 } 139 ErrorKind::SimpleLength { len } => { 140 write!( 141 f, 142 "invalid length: expected length 32 for simple format, found {}", 143 len 144 ) 145 } 146 ErrorKind::ByteLength { len } => { 147 write!(f, "invalid length: expected 16 bytes, found {}", len) 148 } 149 ErrorKind::GroupCount { count } => { 150 write!(f, "invalid group count: expected 5, found {}", count) 151 } 152 ErrorKind::GroupLength { group, len, .. } => { 153 let expected = [8, 4, 4, 4, 12][group]; 154 write!( 155 f, 156 "invalid group length in group {}: expected {}, found {}", 157 group, expected, len 158 ) 159 } 160 ErrorKind::InvalidUTF8 => write!(f, "non-UTF8 input"), 161 ErrorKind::Other => write!(f, "failed to parse a UUID"), 162 } 163 } 164 } 165 166 #[cfg(feature = "std")] 167 mod std_support { 168 use super::*; 169 use crate::std::error; 170 171 impl error::Error for Error {} 172 } 173