///|
/// A UUID represented as two unsigned 64-bit words in network byte order.
///
/// `high` stores octets 0..7 and `low` stores octets 8..15.
pub struct Uuid {
high : UInt64
low : UInt64
} derive(Eq, Compare, Hash)
///|
pub extend Uuid with Eq::{not_equal, equal}
///|
pub extend Uuid with Compare::{op_lt, op_le, op_ge, compare, op_gt}
///|
pub extend Uuid with Hash::{hash, hash_combine}
///|
/// UUID layout variant as defined by RFC 9562 section 4.1.
pub(all) enum Variant {
Ncs
Rfc9562
Microsoft
Future
} derive(Eq)
///|
pub extend Variant with Eq::{not_equal, equal}
///|
/// Error returned while parsing UUID text.
pub(all) enum ParseError {
InvalidLength(Int)
InvalidHyphen(Int)
InvalidHexDigit(Int)
InvalidWrapper
} derive(Eq)
///|
pub extend ParseError with Eq::{not_equal, equal}
///|
/// Error returned while converting binary UUID data.
pub(all) enum BytesError {
InvalidByteLength(Int)
} derive(Eq)
///|
pub extend BytesError with Eq::{not_equal, equal}
///|
/// Construct a UUID directly from its high and low 64-bit words.
pub fn from_u64s(high : UInt64, low : UInt64) -> Uuid {
{ high, low, }
}
///|
/// Return the most-significant 64 bits.
pub fn high(uuid : Uuid) -> UInt64 {
uuid.high
}
///|
/// Return the least-significant 64 bits.
pub fn low(uuid : Uuid) -> UInt64 {
uuid.low
}
///|
/// RFC 9562 Nil UUID: all 128 bits are zero.
pub fn nil() -> Uuid {
{ high: 0UL, low: 0UL, }
}
///|
/// RFC 9562 Max UUID: all 128 bits are one.
pub fn max() -> Uuid {
{ high: 0xFFFFFFFFFFFFFFFFUL, low: 0xFFFFFFFFFFFFFFFFUL, }
}
///|
/// Return true when this is the RFC 9562 Nil UUID.
pub fn is_nil(uuid : Uuid) -> Bool {
uuid.high == 0UL && uuid.low == 0UL
}
///|
/// Return true when this is the RFC 9562 Max UUID.
pub fn is_max(uuid : Uuid) -> Bool {
uuid.high == 0xFFFFFFFFFFFFFFFFUL && uuid.low == 0xFFFFFFFFFFFFFFFFUL
}
///|
fn hex_value(code_unit : UInt16) -> Int? {
let value = code_unit.to_int()
if value >= 48 && value <= 57 {
Some(value - 48)
} else if value >= 65 && value <= 70 {
Some(value - 65 + 10)
} else if value >= 97 && value <= 102 {
Some(value - 97 + 10)
} else {
None
}
}
///|
fn parse_canonical_at(text : String, start : Int) -> Result[Uuid, ParseError] {
let mut high = 0UL
let mut low = 0UL
let mut digit_index = 0
for offset = 0; offset < 36; offset = offset + 1 {
let index = start + offset
let code_unit = text.code_unit_at(index)
if offset == 8 || offset == 13 || offset == 18 || offset == 23 {
if code_unit.to_int() != 45 {
return Err(InvalidHyphen(index))
}
} else {
match hex_value(code_unit) {
None => return Err(InvalidHexDigit(index))
Some(nibble) => {
if digit_index < 16 {
high = (high << 4) | nibble.to_uint64()
} else {
low = (low << 4) | nibble.to_uint64()
}
digit_index = digit_index + 1
}
}
}
}
Ok({ high, low, })
}
///|
fn parse_compact_at(text : String, start : Int) -> Result[Uuid, ParseError] {
let mut high = 0UL
let mut low = 0UL
for offset = 0; offset < 32; offset = offset + 1 {
let index = start + offset
match hex_value(text.code_unit_at(index)) {
None => return Err(InvalidHexDigit(index))
Some(nibble) =>
if offset < 16 {
high = (high << 4) | nibble.to_uint64()
} else {
low = (low << 4) | nibble.to_uint64()
}
}
}
Ok({ high, low, })
}
///|
fn ascii_eq_ignore_case(actual : UInt16, lowercase_ascii : Int) -> Bool {
let value = actual.to_int()
let folded = if value >= 65 && value <= 90 { value + 32 } else { value }
folded == lowercase_ascii
}
///|
fn has_urn_uuid_prefix(text : String) -> Bool {
text.length() >= 9 &&
ascii_eq_ignore_case(text.code_unit_at(0), 117) &&
ascii_eq_ignore_case(text.code_unit_at(1), 114) &&
ascii_eq_ignore_case(text.code_unit_at(2), 110) &&
text.code_unit_at(3).to_int() == 58 &&
ascii_eq_ignore_case(text.code_unit_at(4), 117) &&
ascii_eq_ignore_case(text.code_unit_at(5), 117) &&
ascii_eq_ignore_case(text.code_unit_at(6), 105) &&
ascii_eq_ignore_case(text.code_unit_at(7), 100) &&
text.code_unit_at(8).to_int() == 58
}
///|
/// Parse only the canonical UUID text form: `8-4-4-4-12`.
///
/// Both uppercase and lowercase hexadecimal digits are accepted.
/// Other common wrappers and compact text are intentionally rejected here.
/// Use `parse_permissive` when interoperability with those forms is required.
pub fn parse(text : String) -> Result[Uuid, ParseError] {
if text.length() != 36 {
return Err(InvalidLength(text.length()))
}
parse_canonical_at(text, 0)
}
///|
/// Parse common interoperable UUID text forms.
///
/// Accepted forms:
/// - canonical: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
/// - compact: `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`
/// - URN: `urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
/// - braced: `{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}`
pub fn parse_permissive(text : String) -> Result[Uuid, ParseError] {
match text.length() {
36 => parse_canonical_at(text, 0)
32 => parse_compact_at(text, 0)
38 =>
if text.code_unit_at(0).to_int() == 123 &&
text.code_unit_at(37).to_int() == 125 {
parse_canonical_at(text, 1)
} else {
Err(InvalidWrapper)
}
45 =>
if has_urn_uuid_prefix(text) {
parse_canonical_at(text, 9)
} else {
Err(InvalidWrapper)
}
length => Err(InvalidLength(length))
}
}
///|
/// Return true if `text` is a canonical UUID string accepted by `parse`.
pub fn is_valid(text : String) -> Bool {
match parse(text) {
Ok(_) => true
Err(_) => false
}
}
///|
/// Return true if `text` is accepted by `parse_permissive`.
pub fn is_valid_permissive(text : String) -> Bool {
match parse_permissive(text) {
Ok(_) => true
Err(_) => false
}
}
///|
fn hex_char(nibble : Int) -> Char {
if nibble < 10 {
(48 + nibble).unsafe_to_char()
} else {
(87 + nibble).unsafe_to_char()
}
}
///|
/// Format a UUID as lowercase canonical `8-4-4-4-12` text.
pub fn to_string(uuid : Uuid) -> String {
let builder = StringBuilder()
for i = 0; i < 32; i = i + 1 {
if i == 8 || i == 12 || i == 16 || i == 20 {
builder.write_char('-')
}
let nibble = if i < 16 {
let shift = (15 - i) * 4
((uuid.high >> shift) & 0xFUL).to_int()
} else {
let shift = (31 - i) * 4
((uuid.low >> shift) & 0xFUL).to_int()
}
builder.write_char(hex_char(nibble))
}
builder.to_string()
}
///|
/// Format a UUID as an RFC UUID URN.
pub fn to_urn(uuid : Uuid) -> String {
"urn:uuid:" + to_string(uuid)
}
///|
/// Format a UUID using the common braced canonical form.
pub fn to_braced_string(uuid : Uuid) -> String {
"{" + to_string(uuid) + "}"
}
///|
fn uuid_byte(uuid : Uuid, index : Int) -> Byte {
if index < 8 {
let shift = (7 - index) * 8
((uuid.high >> shift) & 0xFFUL).to_byte()
} else {
let shift = (15 - index) * 8
((uuid.low >> shift) & 0xFFUL).to_byte()
}
}
///|
/// Encode the UUID as exactly 16 bytes in RFC network byte order.
pub fn to_bytes(uuid : Uuid) -> Bytes {
let result : Bytes = [ for i in 0..<16 => uuid_byte(uuid, i) ]
result
}
///|
/// Decode exactly 16 bytes in RFC network byte order into a UUID.
pub fn from_bytes(data : Bytes) -> Result[Uuid, BytesError] {
if data.length() != 16 {
return Err(InvalidByteLength(data.length()))
}
let mut high = 0UL
let mut low = 0UL
for i = 0; i < 8; i = i + 1 {
high = (high << 8) | data[i].to_int().to_uint64()
}
for i = 8; i < 16; i = i + 1 {
low = (low << 8) | data[i].to_int().to_uint64()
}
Ok({ high, low, })
}
///|
/// Return the UUID layout variant from the most-significant bits of octet 8.
pub fn variant(uuid : Uuid) -> Variant {
let octet = ((uuid.low >> 56) & 0xFFUL).to_int()
if (octet & 0x80) == 0 {
Ncs
} else if (octet & 0xC0) == 0x80 {
Rfc9562
} else if (octet & 0xE0) == 0xC0 {
Microsoft
} else {
Future
}
}
///|
/// Return the 4-bit UUID version for RFC 9562-layout UUIDs.
///
/// Non-RFC variants do not share the RFC version-field semantics and return None.
pub fn version(uuid : Uuid) -> Int? {
if variant(uuid) == Rfc9562 {
Some(((uuid.high >> 12) & 0xFUL).to_int())
} else {
None
}
}