// Copyright 2013-2014 The Rust Project Developers. | |
// Copyright 2018 The Uuid Project Developers. | |
// | |
// See the COPYRIGHT file at the top-level directory of this distribution. | |
// | |
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
// option. This file may not be copied, modified, or distributed | |
// except according to those terms. | |
//! [`Uuid`] parsing constructs and utilities. | |
//! | |
//! [`Uuid`]: ../struct.Uuid.html | |
pub(crate) mod error; | |
pub(crate) use self::error::Error; | |
use crate::{adapter, Uuid}; | |
/// Check if the length matches any of the given criteria lengths. | |
fn len_matches_any(len: usize, crits: &[usize]) -> bool { | |
for crit in crits { | |
if len == *crit { | |
return true; | |
} | |
} | |
false | |
} | |
/// Check if the length matches any criteria lengths in the given range | |
/// (inclusive). | |
#[allow(dead_code)] | |
fn len_matches_range(len: usize, min: usize, max: usize) -> bool { | |
for crit in min..=max { | |
if len == crit { | |
return true; | |
} | |
} | |
false | |
} | |
// Accumulated length of each hyphenated group in hex digits. | |
const ACC_GROUP_LENS: [usize; 5] = [8, 12, 16, 20, 32]; | |
// Length of each hyphenated group in hex digits. | |
const GROUP_LENS: [usize; 5] = [8, 4, 4, 4, 12]; | |
impl Uuid { | |
/// Parses a `Uuid` from a string of hexadecimal digits with optional | |
/// hyphens. | |
/// | |
/// Any of the formats generated by this module (simple, hyphenated, urn) | |
/// are supported by this parsing function. | |
pub fn parse_str(mut input: &str) -> Result<Uuid, crate::Error> { | |
// Ensure length is valid for any of the supported formats | |
let len = input.len(); | |
if len == adapter::Urn::LENGTH && input.starts_with("urn:uuid:") { | |
input = &input[9..]; | |
} else if !len_matches_any( | |
len, | |
&[adapter::Hyphenated::LENGTH, adapter::Simple::LENGTH], | |
) { | |
Err(Error::InvalidLength { | |
expected: error::ExpectedLength::Any(&[ | |
adapter::Hyphenated::LENGTH, | |
adapter::Simple::LENGTH, | |
]), | |
found: len, | |
})?; | |
} | |
// `digit` counts only hexadecimal digits, `i_char` counts all chars. | |
let mut digit = 0; | |
let mut group = 0; | |
let mut acc = 0; | |
let mut buffer = [0u8; 16]; | |
for (i_char, chr) in input.bytes().enumerate() { | |
if digit as usize >= adapter::Simple::LENGTH && group != 4 { | |
if group == 0 { | |
Err(Error::InvalidLength { | |
expected: error::ExpectedLength::Any(&[ | |
adapter::Hyphenated::LENGTH, | |
adapter::Simple::LENGTH, | |
]), | |
found: len, | |
})?; | |
} | |
Err(Error::InvalidGroupCount { | |
expected: error::ExpectedLength::Any(&[1, 5]), | |
found: group + 1, | |
})?; | |
} | |
if digit % 2 == 0 { | |
// First digit of the byte. | |
match chr { | |
// Calulate upper half. | |
b'0'..=b'9' => acc = chr - b'0', | |
b'a'..=b'f' => acc = chr - b'a' + 10, | |
b'A'..=b'F' => acc = chr - b'A' + 10, | |
// Found a group delimiter | |
b'-' => { | |
// TODO: remove the u8 cast | |
// BODY: this only needed until we switch to | |
// ParseError | |
if ACC_GROUP_LENS[group] as u8 != digit { | |
// Calculate how many digits this group consists of | |
// in the input. | |
let found = if group > 0 { | |
// TODO: remove the u8 cast | |
// BODY: this only needed until we switch to | |
// ParseError | |
digit - ACC_GROUP_LENS[group - 1] as u8 | |
} else { | |
digit | |
}; | |
Err(Error::InvalidGroupLength { | |
expected: error::ExpectedLength::Exact( | |
GROUP_LENS[group], | |
), | |
found: found as usize, | |
group, | |
})?; | |
} | |
// Next group, decrement digit, it is incremented again | |
// at the bottom. | |
group += 1; | |
digit -= 1; | |
} | |
_ => { | |
Err(Error::InvalidCharacter { | |
expected: "0123456789abcdefABCDEF-", | |
found: input[i_char..].chars().next().unwrap(), | |
index: i_char, | |
urn: error::UrnPrefix::Optional, | |
})?; | |
} | |
} | |
} else { | |
// Second digit of the byte, shift the upper half. | |
acc *= 16; | |
match chr { | |
b'0'..=b'9' => acc += chr - b'0', | |
b'a'..=b'f' => acc += chr - b'a' + 10, | |
b'A'..=b'F' => acc += chr - b'A' + 10, | |
b'-' => { | |
// The byte isn't complete yet. | |
let found = if group > 0 { | |
// TODO: remove the u8 cast | |
// BODY: this only needed until we switch to | |
// ParseError | |
digit - ACC_GROUP_LENS[group - 1] as u8 | |
} else { | |
digit | |
}; | |
Err(Error::InvalidGroupLength { | |
expected: error::ExpectedLength::Exact( | |
GROUP_LENS[group], | |
), | |
found: found as usize, | |
group, | |
})?; | |
} | |
_ => { | |
Err(Error::InvalidCharacter { | |
expected: "0123456789abcdefABCDEF-", | |
found: input[i_char..].chars().next().unwrap(), | |
index: i_char, | |
urn: error::UrnPrefix::Optional, | |
})?; | |
} | |
} | |
buffer[(digit / 2) as usize] = acc; | |
} | |
digit += 1; | |
} | |
// Now check the last group. | |
// TODO: remove the u8 cast | |
// BODY: this only needed until we switch to | |
// ParseError | |
if ACC_GROUP_LENS[4] as u8 != digit { | |
Err(Error::InvalidGroupLength { | |
expected: error::ExpectedLength::Exact(GROUP_LENS[4]), | |
found: (digit as usize - ACC_GROUP_LENS[3]), | |
group, | |
})?; | |
} | |
Ok(Uuid::from_bytes(buffer)) | |
} | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
use crate::{adapter, std::string::ToString, test_util}; | |
#[test] | |
fn test_parse_uuid_v4() { | |
const EXPECTED_UUID_LENGTHS: error::ExpectedLength = | |
error::ExpectedLength::Any(&[ | |
adapter::Hyphenated::LENGTH, | |
adapter::Simple::LENGTH, | |
]); | |
const EXPECTED_GROUP_COUNTS: error::ExpectedLength = | |
error::ExpectedLength::Any(&[1, 5]); | |
const EXPECTED_CHARS: &'static str = "0123456789abcdefABCDEF-"; | |
// Invalid | |
assert_eq!( | |
Uuid::parse_str("").map_err(crate::Error::expect_parser), | |
Err(Error::InvalidLength { | |
expected: EXPECTED_UUID_LENGTHS, | |
found: 0, | |
}) | |
); | |
assert_eq!( | |
Uuid::parse_str("!").map_err(crate::Error::expect_parser), | |
Err(Error::InvalidLength { | |
expected: EXPECTED_UUID_LENGTHS, | |
found: 1 | |
}) | |
); | |
assert_eq!( | |
Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E45") | |
.map_err(crate::Error::expect_parser), | |
Err(Error::InvalidLength { | |
expected: EXPECTED_UUID_LENGTHS, | |
found: 37, | |
}) | |
); | |
assert_eq!( | |
Uuid::parse_str("F9168C5E-CEB2-4faa-BBF-329BF39FA1E4") | |
.map_err(crate::Error::expect_parser), | |
Err(Error::InvalidLength { | |
expected: EXPECTED_UUID_LENGTHS, | |
found: 35 | |
}) | |
); | |
assert_eq!( | |
Uuid::parse_str("F9168C5E-CEB2-4faa-BGBF-329BF39FA1E4") | |
.map_err(crate::Error::expect_parser), | |
Err(Error::InvalidCharacter { | |
expected: EXPECTED_CHARS, | |
found: 'G', | |
index: 20, | |
urn: error::UrnPrefix::Optional, | |
}) | |
); | |
assert_eq!( | |
Uuid::parse_str("F9168C5E-CEB2F4faaFB6BFF329BF39FA1E4") | |
.map_err(crate::Error::expect_parser), | |
Err(Error::InvalidGroupCount { | |
expected: EXPECTED_GROUP_COUNTS, | |
found: 2 | |
}) | |
); | |
assert_eq!( | |
Uuid::parse_str("F9168C5E-CEB2-4faaFB6BFF329BF39FA1E4") | |
.map_err(crate::Error::expect_parser), | |
Err(Error::InvalidGroupCount { | |
expected: EXPECTED_GROUP_COUNTS, | |
found: 3, | |
}) | |
); | |
assert_eq!( | |
Uuid::parse_str("F9168C5E-CEB2-4faa-B6BFF329BF39FA1E4") | |
.map_err(crate::Error::expect_parser), | |
Err(Error::InvalidGroupCount { | |
expected: EXPECTED_GROUP_COUNTS, | |
found: 4, | |
}) | |
); | |
assert_eq!( | |
Uuid::parse_str("F9168C5E-CEB2-4faa") | |
.map_err(crate::Error::expect_parser), | |
Err(Error::InvalidLength { | |
expected: EXPECTED_UUID_LENGTHS, | |
found: 18, | |
}) | |
); | |
assert_eq!( | |
Uuid::parse_str("F9168C5E-CEB2-4faaXB6BFF329BF39FA1E4") | |
.map_err(crate::Error::expect_parser), | |
Err(Error::InvalidCharacter { | |
expected: EXPECTED_CHARS, | |
found: 'X', | |
index: 18, | |
urn: error::UrnPrefix::Optional, | |
}) | |
); | |
assert_eq!( | |
Uuid::parse_str("F9168C5E-CEB-24fa-eB6BFF32-BF39FA1E4") | |
.map_err(crate::Error::expect_parser), | |
Err(Error::InvalidGroupLength { | |
expected: error::ExpectedLength::Exact(4), | |
found: 3, | |
group: 1, | |
}) | |
); | |
// (group, found, expecting) | |
// | |
assert_eq!( | |
Uuid::parse_str("01020304-1112-2122-3132-41424344") | |
.map_err(crate::Error::expect_parser), | |
Err(Error::InvalidGroupLength { | |
expected: error::ExpectedLength::Exact(12), | |
found: 8, | |
group: 4, | |
}) | |
); | |
assert_eq!( | |
Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c") | |
.map_err(crate::Error::expect_parser), | |
Err(Error::InvalidLength { | |
expected: EXPECTED_UUID_LENGTHS, | |
found: 31, | |
}) | |
); | |
assert_eq!( | |
Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c88") | |
.map_err(crate::Error::expect_parser), | |
Err(Error::InvalidLength { | |
expected: EXPECTED_UUID_LENGTHS, | |
found: 33, | |
}) | |
); | |
assert_eq!( | |
Uuid::parse_str("67e5504410b1426f9247bb680e5fe0cg8") | |
.map_err(crate::Error::expect_parser), | |
Err(Error::InvalidLength { | |
expected: EXPECTED_UUID_LENGTHS, | |
found: 33, | |
}) | |
); | |
assert_eq!( | |
Uuid::parse_str("67e5504410b1426%9247bb680e5fe0c8") | |
.map_err(crate::Error::expect_parser), | |
Err(Error::InvalidCharacter { | |
expected: EXPECTED_CHARS, | |
found: '%', | |
index: 15, | |
urn: error::UrnPrefix::Optional, | |
}) | |
); | |
assert_eq!( | |
Uuid::parse_str("231231212212423424324323477343246663") | |
.map_err(crate::Error::expect_parser), | |
Err(Error::InvalidLength { | |
expected: EXPECTED_UUID_LENGTHS, | |
found: 36, | |
}) | |
); | |
// Valid | |
assert!(Uuid::parse_str("00000000000000000000000000000000").is_ok()); | |
assert!(Uuid::parse_str("67e55044-10b1-426f-9247-bb680e5fe0c8").is_ok()); | |
assert!(Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4").is_ok()); | |
assert!(Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c8").is_ok()); | |
assert!(Uuid::parse_str("01020304-1112-2122-3132-414243444546").is_ok()); | |
assert!(Uuid::parse_str( | |
"urn:uuid:67e55044-10b1-426f-9247-bb680e5fe0c8" | |
) | |
.is_ok()); | |
// Nil | |
let nil = Uuid::nil(); | |
assert_eq!( | |
Uuid::parse_str("00000000000000000000000000000000").unwrap(), | |
nil | |
); | |
assert_eq!( | |
Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap(), | |
nil | |
); | |
// Round-trip | |
let uuid_orig = test_util::new(); | |
let orig_str = uuid_orig.to_string(); | |
let uuid_out = Uuid::parse_str(&orig_str).unwrap(); | |
assert_eq!(uuid_orig, uuid_out); | |
// Test error reporting | |
assert_eq!( | |
Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c") | |
.map_err(crate::Error::expect_parser), | |
Err(Error::InvalidLength { | |
expected: EXPECTED_UUID_LENGTHS, | |
found: 31, | |
}) | |
); | |
assert_eq!( | |
Uuid::parse_str("67e550X410b1426f9247bb680e5fe0cd") | |
.map_err(crate::Error::expect_parser), | |
Err(Error::InvalidCharacter { | |
expected: EXPECTED_CHARS, | |
found: 'X', | |
index: 6, | |
urn: error::UrnPrefix::Optional, | |
}) | |
); | |
assert_eq!( | |
Uuid::parse_str("67e550-4105b1426f9247bb680e5fe0c") | |
.map_err(crate::Error::expect_parser), | |
Err(Error::InvalidGroupLength { | |
expected: error::ExpectedLength::Exact(8), | |
found: 6, | |
group: 0, | |
}) | |
); | |
assert_eq!( | |
Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF1-02BF39FA1E4") | |
.map_err(crate::Error::expect_parser), | |
Err(Error::InvalidGroupLength { | |
expected: error::ExpectedLength::Exact(4), | |
found: 5, | |
group: 3, | |
}) | |
); | |
} | |
} |