phobos/std/uuid.d
Nathan Sashihara a3c3ea74e3 Fix Issue 21201 - let std.uuid.parseUUID accept input ranges whose elements are char or wchar, not just dchar
All characters of interest to std.uuid.parseUUID are ASCII so there is
no reason it cannot work on a range of chars or wchars.
2020-08-27 16:47:53 +02:00

1758 lines
57 KiB
D

/**
* A $(LINK2 http://en.wikipedia.org/wiki/Universally_unique_identifier, UUID), or
* $(LINK2 http://en.wikipedia.org/wiki/Universally_unique_identifier, Universally unique identifier),
* is intended to uniquely identify information in a distributed environment
* without significant central coordination. It can be
* used to tag objects with very short lifetimes, or to reliably identify very
* persistent objects across a network.
*
$(SCRIPT inhibitQuickIndex = 1;)
$(DIVC quickindex,
$(BOOKTABLE ,
$(TR $(TH Category) $(TH Functions)
)
$(TR $(TDNW Parsing UUIDs)
$(TD $(MYREF parseUUID)
$(MYREF UUID)
$(MYREF UUIDParsingException)
$(MYREF uuidRegex)
)
)
$(TR $(TDNW Generating UUIDs)
$(TD $(MYREF sha1UUID)
$(MYREF randomUUID)
$(MYREF md5UUID)
)
)
$(TR $(TDNW Using UUIDs)
$(TD $(MYREF2 UUID.uuidVersion, uuidVersion)
$(MYREF2 UUID.variant, variant)
$(MYREF2 UUID.toString, toString)
$(MYREF2 UUID.data, data)
$(MYREF2 UUID.swap, swap)
$(MYREF2 UUID.opEquals, opEquals)
$(MYREF2 UUID.opCmp, opCmp)
$(MYREF2 UUID.toHash, toHash)
)
)
$(TR $(TDNW UUID namespaces)
$(TD $(MYREF dnsNamespace)
$(MYREF urlNamespace)
$(MYREF oidNamespace)
$(MYREF x500Namespace)
)
)
)
)
* UUIDs have many applications. Some examples follow: Databases may use UUIDs to identify
* rows or records in order to ensure that they are unique across different
* databases, or for publication/subscription services. Network messages may be
* identified with a UUID to ensure that different parts of a message are put back together
* again. Distributed computing may use UUIDs to identify a remote procedure call.
* Transactions and classes involved in serialization may be identified by UUIDs.
* Microsoft's component object model (COM) uses UUIDs to distinguish different software
* component interfaces. UUIDs are inserted into documents from Microsoft Office programs.
* UUIDs identify audio or video streams in the Advanced Systems Format (ASF). UUIDs are
* also a basis for OIDs (object identifiers), and URNs (uniform resource name).
*
* An attractive feature of UUIDs when compared to alternatives is their relative small size,
* of 128 bits, or 16 bytes. Another is that the creation of UUIDs does not require
* a centralized authority.
*
* When UUIDs are generated by one of the defined mechanisms, they are either guaranteed
* to be unique, different from all other generated UUIDs (that is, it has never been
* generated before and it will never be generated again), or it is extremely likely
* to be unique (depending on the mechanism).
*
* For efficiency, UUID is implemented as a struct. UUIDs are therefore empty if not explicitly
* initialized. An UUID is empty if $(MYREF3 UUID.empty, empty) is true. Empty UUIDs are equal to
* `UUID.init`, which is a UUID with all 16 bytes set to 0.
* Use UUID's constructors or the UUID generator functions to get an initialized UUID.
*
* This is a port of $(LINK2 http://www.boost.org/doc/libs/1_42_0/libs/uuid/uuid.html,
* boost.uuid) from the Boost project with some minor additions and API
* changes for a more D-like API.
*
* Standards:
* $(LINK2 http://www.ietf.org/rfc/rfc4122.txt, RFC 4122)
*
* See_Also:
* $(LINK http://en.wikipedia.org/wiki/Universally_unique_identifier)
*
* Copyright: Copyright Johannes Pfau 2011 - .
* License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
* Authors: Johannes Pfau
* Source: $(PHOBOSSRC std/uuid.d)
*
* Macros:
* MYREF2 = <a href="#$2">$(TT $1)</a>&nbsp;
* MYREF3 = <a href="#$2">`$1`</a>
*/
/* Copyright Johannes Pfau 2011 - 2012.
* Distributed under the Boost Software License, Version 1.0.
* (See accompanying file LICENSE_1_0.txt or copy at
* http://www.boost.org/LICENSE_1_0.txt)
*/
module std.uuid;
///
@safe unittest
{
import std.uuid;
UUID[] ids;
ids ~= randomUUID();
ids ~= md5UUID("test.name.123");
ids ~= sha1UUID("test.name.123");
foreach (entry; ids)
{
assert(entry.variant == UUID.Variant.rfc4122);
}
assert(ids[0].uuidVersion == UUID.Version.randomNumberBased);
assert(ids[1].toString() == "22390768-cced-325f-8f0f-cfeaa19d0ccd");
assert(ids[1].data == [34, 57, 7, 104, 204, 237, 50, 95, 143, 15, 207,
234, 161, 157, 12, 205]);
UUID id;
assert(id.empty);
}
import std.range.primitives;
import std.traits;
/**
*
*/
public struct UUID
{
import std.meta : AliasSeq, allSatisfy;
private:
alias skipSeq = AliasSeq!(8, 13, 18, 23);
alias byteSeq = AliasSeq!(0,2,4,6,9,11,14,16,19,21,24,26,28,30,32,34);
@safe pure nothrow @nogc Char toChar(Char)(size_t i) const
{
if (i <= 9)
return cast(Char)('0' + i);
else
return cast(Char)('a' + (i-10));
}
@safe pure nothrow unittest
{
assert(UUID(cast(ubyte[16])[138, 179, 6, 14, 44, 186, 79, 35, 183, 76, 181, 45,
179, 189, 251, 70]).toString() == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
}
// Reinterpret the UUID as an array of some other primitive.
@trusted ref T[16 / T.sizeof] asArrayOf(T)() return
if (isIntegral!T)
{
return *cast(typeof(return)*)&data;
}
public:
/**
* RFC 4122 defines different internal data layouts for UUIDs. These are
* the UUID formats supported by this module. It's
* possible to read, compare and use all these Variants, but
* UUIDs generated by this module will always be in rfc4122 format.
*
* Note: Do not confuse this with $(REF _Variant, std,_variant).
*/
enum Variant
{
ncs, /// NCS backward compatibility
rfc4122, /// Defined in RFC 4122 document
microsoft, /// Microsoft Corporation backward compatibility
future ///Reserved for future use
}
/**
* RFC 4122 defines different UUID versions. The version shows
* how a UUID was generated, e.g. a version 4 UUID was generated
* from a random number, a version 3 UUID from an MD5 hash of a name.
*
* Note:
* All of these UUID versions can be read and processed by
* `std.uuid`, but only version 3, 4 and 5 UUIDs can be generated.
*/
enum Version
{
///Unknown version
unknown = -1,
///Version 1
timeBased = 1,
///Version 2
dceSecurity = 2,
///Version 3 (Name based + MD5)
nameBasedMD5 = 3,
///Version 4 (Random)
randomNumberBased = 4,
///Version 5 (Name based + SHA-1)
nameBasedSHA1 = 5
}
union
{
/**
* It is sometimes useful to get or set the 16 bytes of a UUID
* directly.
*
* Note:
* UUID uses a 16-ubyte representation for the UUID data.
* RFC 4122 defines a UUID as a special structure in big-endian
* format. These 16-ubytes always equal the big-endian structure
* defined in RFC 4122.
*
* Example:
* -----------------------------------------------
* auto rawData = uuid.data; //get data
* rawData[0] = 1; //modify
* uuid.data = rawData; //set data
* uuid.data[1] = 2; //modify directly
* -----------------------------------------------
*/
ubyte[16] data;
private ulong[2] ulongs;
static if (size_t.sizeof == 4)
private uint[4] uints;
}
/*
* We could use a union here to also provide access to the
* fields specified in RFC 4122, but as we never have to access
* those (only necessary for version 1 (and maybe 2) UUIDs),
* that is not needed right now.
*/
@safe pure unittest
{
UUID tmp;
tmp.data = cast(ubyte[16])[0,1,2,3,4,5,6,7,8,9,10,11,12,
13,14,15];
assert(tmp.data == cast(ubyte[16])[0,1,2,3,4,5,6,7,8,9,10,11,
12,13,14,15]);
tmp.data[2] = 3;
assert(tmp.data == cast(ubyte[16])[0,1,3,3,4,5,6,7,8,9,10,11,
12,13,14,15]);
auto tmp2 = cast(immutable UUID) tmp;
assert(tmp2.data == cast(ubyte[16])[0,1,3,3,4,5,6,7,8,9,10,11,
12,13,14,15]);
}
/**
* Construct a UUID struct from the 16 byte representation
* of a UUID.
*/
@safe pure nothrow @nogc this(ref const scope ubyte[16] uuidData)
{
data = uuidData;
}
/// ditto
@safe pure nothrow @nogc this(const ubyte[16] uuidData)
{
data = uuidData;
}
///
@safe pure unittest
{
enum ubyte[16] data = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
auto uuid = UUID(data);
enum ctfe = UUID(data);
assert(uuid.data == data);
assert(ctfe.data == data);
}
/**
* Construct a UUID struct from the 16 byte representation
* of a UUID. Variadic constructor to allow a simpler syntax, see examples.
* You need to pass exactly 16 ubytes.
*/
@safe pure this(T...)(T uuidData)
if (uuidData.length == 16 && allSatisfy!(isIntegral, T))
{
import std.conv : to;
foreach (idx, it; uuidData)
{
this.data[idx] = to!ubyte(it);
}
}
///
@safe unittest
{
auto tmp = UUID(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15);
assert(tmp.data == cast(ubyte[16])[0,1,2,3,4,5,6,7,8,9,10,11,
12,13,14,15]);
}
@safe unittest
{
UUID tmp = UUID(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15);
assert(tmp.data == cast(ubyte[16])[0,1,2,3,4,5,6,7,8,9,10,11,
12,13,14,15]);
enum UUID ctfeID = UUID(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15);
assert(ctfeID == tmp);
//Too few arguments
assert(!__traits(compiles, typeof(UUID(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14))));
//Too many arguments
assert(!__traits(compiles, typeof(UUID(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,1))));
}
/**
* <a name="UUID(string)"></a>
* Parse a UUID from its canonical string form. An UUID in its
* canonical form looks like this: 8ab3060e-2cba-4f23-b74c-b52db3bdfb46
*
* Throws:
* $(LREF UUIDParsingException) if the input is invalid
*
* CTFE:
* This function is supported in CTFE code. Note that error messages
* caused by a malformed UUID parsed at compile time can be cryptic,
* but errors are detected and reported at
* compile time.
*
* Note:
* This is a strict parser. It only accepts the pattern above.
* It doesn't support any leading or trailing characters. It only
* accepts characters used for hex numbers and the string must have
* hyphens exactly like above.
*
* For a less strict parser, see $(LREF parseUUID)
*/
this(T)(in T[] uuid) if (isSomeChar!(Unqual!T))
{
import std.conv : to, parse;
if (uuid.length < 36)
{
throw new UUIDParsingException(to!string(uuid), 0,
UUIDParsingException.Reason.tooLittle, "Insufficient Input");
}
if (uuid.length > 36)
{
throw new UUIDParsingException(to!string(uuid), 35, UUIDParsingException.Reason.tooMuch,
"Input is too long, need exactly 36 characters");
}
static immutable skipInd = [skipSeq];
foreach (pos; skipInd)
if (uuid[pos] != '-')
throw new UUIDParsingException(to!string(uuid), pos,
UUIDParsingException.Reason.invalidChar, "Expected '-'");
ubyte[16] data2; //ctfe bug
uint pos = void;
foreach (i, p; byteSeq)
{
enum uint s = 'a'-10-'0';
uint h = uuid[p];
uint l = uuid[p+1];
pos = p;
if (h < '0') goto Lerr;
if (l < '0') goto Lerr;
if (h > '9')
{
h |= 0x20; //poorman's tolower
if (h < 'a') goto Lerr;
if (h > 'f') goto Lerr;
h -= s;
}
if (l > '9')
{
l |= 0x20; //poorman's tolower
if (l < 'a') goto Lerr;
if (l > 'f') goto Lerr;
l -= s;
}
h -= '0';
l -= '0';
data2[i] = cast(ubyte)((h << 4) ^ l);
}
this.data = data2;
return;
Lerr: throw new UUIDParsingException(to!string(uuid), pos,
UUIDParsingException.Reason.invalidChar, "Couldn't parse ubyte");
}
///
@safe pure unittest
{
auto id = UUID("8AB3060E-2cba-4f23-b74c-b52db3bdfb46");
assert(id.data == [138, 179, 6, 14, 44, 186, 79, 35, 183, 76,
181, 45, 179, 189, 251, 70]);
assert(id.toString() == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
//Can also be used in CTFE, for example as UUID literals:
enum ctfeID = UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
//here parsing is done at compile time, no runtime overhead!
}
@safe pure unittest
{
import std.conv : to;
import std.exception;
import std.meta : AliasSeq;
static foreach (S; AliasSeq!(char[], const(char)[], immutable(char)[],
wchar[], const(wchar)[], immutable(wchar)[],
dchar[], const(dchar)[], immutable(dchar)[],
immutable(char[]), immutable(wchar[]), immutable(dchar[])))
{{
//Test valid, working cases
assert(UUID(to!S("00000000-0000-0000-0000-000000000000")).empty);
auto id = UUID(to!S("8AB3060E-2cba-4f23-b74c-b52db3bdfb46"));
assert(id.data == [138, 179, 6, 14, 44, 186, 79, 35, 183, 76,
181, 45, 179, 189, 251, 70]);
assert(id.toString() == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
enum UUID ctfe = UUID(to!S("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"));
assert(ctfe == id);
assert(UUID(to!S("5668122d-9df0-49a4-ad0b-b9b0a57f886a")).data
== [86, 104, 18, 45, 157, 240, 73, 164, 173, 11, 185, 176, 165, 127, 136, 106]);
//Test too short UUIDS
auto except = collectException!UUIDParsingException(
UUID(to!S("5668122d-9df0-49a4-ad0b-b9b0a57f886")));
assert(except && except.reason == UUIDParsingException.Reason.tooLittle);
//Test too long UUIDS
except = collectException!UUIDParsingException(
UUID(to!S("5668122d-9df0-49a4-ad0b-b9b0a57f886aa")));
assert(except && except.reason == UUIDParsingException.Reason.tooMuch);
//Test dashes
except = collectException!UUIDParsingException(
UUID(to!S("8ab3060e2cba-4f23-b74c-b52db3bdfb-46")));
assert(except && except.reason == UUIDParsingException.Reason.invalidChar);
//Test dashes 2
except = collectException!UUIDParsingException(
UUID(to!S("8ab3-060e2cba-4f23-b74c-b52db3bdfb46")));
assert(except && except.reason == UUIDParsingException.Reason.invalidChar);
//Test invalid characters
//make sure 36 characters in total or we'll get a 'tooMuch' reason
except = collectException!UUIDParsingException(
UUID(to!S("{8ab3060e-2cba-4f23-b74c-b52db3bdf6}")));
assert(except && except.reason == UUIDParsingException.Reason.invalidChar);
//Boost test
assert(UUID(to!S("01234567-89ab-cdef-0123-456789ABCDEF"))
== UUID(cast(ubyte[16])[0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,0x01,
0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]));
}
}}
/**
* Returns true if and only if the UUID is equal
* to {00000000-0000-0000-0000-000000000000}
*/
@trusted pure nothrow @nogc @property bool empty() const
{
if (__ctfe)
return data == (ubyte[16]).init;
auto p = cast(const(size_t*))data.ptr;
static if (size_t.sizeof == 4)
return p[0] == 0 && p[1] == 0 && p[2] == 0 && p[3] == 0;
else static if (size_t.sizeof == 8)
return p[0] == 0 && p[1] == 0;
else
static assert(false, "nonsense, it's not 32 or 64 bit");
}
///
@safe pure unittest
{
UUID id;
assert(id.empty);
id = UUID("00000000-0000-0000-0000-000000000001");
assert(!id.empty);
}
@safe pure unittest
{
ubyte[16] getData(size_t i)
{
ubyte[16] data;
data[i] = 1;
return data;
}
for (size_t i = 0; i < 16; i++)
{
assert(!UUID(getData(i)).empty);
}
enum ctfeEmpty = UUID.init.empty;
assert(ctfeEmpty);
bool ctfeTest()
{
for (size_t i = 0; i < 16; i++)
{
auto ctfeEmpty2 = UUID(getData(i)).empty;
assert(!ctfeEmpty2);
}
return true;
}
enum res = ctfeTest();
}
/**
* RFC 4122 defines different internal data layouts for UUIDs.
* Returns the format used by this UUID.
*
* Note: Do not confuse this with $(REF _Variant, std,_variant).
* The type of this property is $(MYREF3 std.uuid.UUID.Variant, _Variant).
*
* See_Also:
* $(MYREF3 UUID.Variant, Variant)
*/
@safe pure nothrow @nogc @property Variant variant() const
{
//variant is stored in octet 7
//which is index 8, since indexes count backwards
immutable octet7 = data[8]; //octet 7 is array index 8
if ((octet7 & 0x80) == 0x00) //0b0xxxxxxx
return Variant.ncs;
else if ((octet7 & 0xC0) == 0x80) //0b10xxxxxx
return Variant.rfc4122;
else if ((octet7 & 0xE0) == 0xC0) //0b110xxxxx
return Variant.microsoft;
else
{
//assert((octet7 & 0xE0) == 0xE0, "Unknown UUID variant!") //0b111xxxx
return Variant.future;
}
}
///
@safe pure unittest
{
assert(UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46").variant
== UUID.Variant.rfc4122);
}
@system pure unittest
{
// @system due to Variant
Variant[ubyte] tests = cast(Variant[ubyte])[0x00 : Variant.ncs,
0x10 : Variant.ncs,
0x20 : Variant.ncs,
0x30 : Variant.ncs,
0x40 : Variant.ncs,
0x50 : Variant.ncs,
0x60 : Variant.ncs,
0x70 : Variant.ncs,
0x80 : Variant.rfc4122,
0x90 : Variant.rfc4122,
0xa0 : Variant.rfc4122,
0xb0 : Variant.rfc4122,
0xc0 : Variant.microsoft,
0xd0 : Variant.microsoft,
0xe0 : Variant.future,
0xf0 : Variant.future];
foreach (key, value; tests)
{
UUID u;
u.data[8] = key;
assert(u.variant == value);
}
}
/**
* RFC 4122 defines different UUID versions. The version shows
* how a UUID was generated, e.g. a version 4 UUID was generated
* from a random number, a version 3 UUID from an MD5 hash of a name.
* Returns the version used by this UUID.
*
* See_Also:
* $(MYREF3 UUID.Version, Version)
*/
@safe pure nothrow @nogc @property Version uuidVersion() const
{
//version is stored in octet 9
//which is index 6, since indexes count backwards
immutable octet9 = data[6];
if ((octet9 & 0xF0) == 0x10)
return Version.timeBased;
else if ((octet9 & 0xF0) == 0x20)
return Version.dceSecurity;
else if ((octet9 & 0xF0) == 0x30)
return Version.nameBasedMD5;
else if ((octet9 & 0xF0) == 0x40)
return Version.randomNumberBased;
else if ((octet9 & 0xF0) == 0x50)
return Version.nameBasedSHA1;
else
return Version.unknown;
}
///
@safe unittest
{
assert(UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46").uuidVersion
== UUID.Version.randomNumberBased);
}
@system unittest
{
// @system due to cast
Version[ubyte] tests = cast(Version[ubyte]) [
0x00 : UUID.Version.unknown,
0x10 : UUID.Version.timeBased,
0x20 : UUID.Version.dceSecurity,
0x30 : UUID.Version.nameBasedMD5,
0x40 : UUID.Version.randomNumberBased,
0x50 : UUID.Version.nameBasedSHA1,
0x60 : UUID.Version.unknown,
0x70 : UUID.Version.unknown,
0x80 : UUID.Version.unknown,
0x90 : UUID.Version.unknown,
0xa0 : UUID.Version.unknown,
0xb0 : UUID.Version.unknown,
0xc0 : UUID.Version.unknown,
0xd0 : UUID.Version.unknown,
0xe0 : UUID.Version.unknown,
0xf0 : UUID.Version.unknown];
foreach (key, value; tests)
{
UUID u;
u.data[6] = key;
assert(u.uuidVersion == value);
}
}
/**
* Swap the data of this UUID with the data of rhs.
*/
@safe pure nothrow @nogc void swap(ref UUID rhs)
{
immutable bck = data;
data = rhs.data;
rhs.data = bck;
}
///
@safe unittest
{
immutable ubyte[16] data = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
UUID u1;
UUID u2 = UUID(data);
u1.swap(u2);
assert(u1 == UUID(data));
assert(u2 == UUID.init);
}
/**
* All of the standard numeric operators are defined for
* the UUID struct.
*/
@safe pure nothrow @nogc bool opEquals(const UUID s) const
{
return ulongs[0] == s.ulongs[0] && ulongs[1] == s.ulongs[1];
}
///
@safe pure unittest
{
//compare UUIDs
assert(UUID("00000000-0000-0000-0000-000000000000") == UUID.init);
//UUIDs in associative arrays:
int[UUID] test = [UUID("8a94f585-d180-44f7-8929-6fca0189c7d0") : 1,
UUID("7c351fd4-b860-4ee3-bbdc-7f79f3dfb00a") : 2,
UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1") : 3];
assert(test[UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1")] == 3);
//UUIDS can be sorted:
import std.algorithm;
UUID[] ids = [UUID("8a94f585-d180-44f7-8929-6fca0189c7d0"),
UUID("7c351fd4-b860-4ee3-bbdc-7f79f3dfb00a"),
UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1")];
sort(ids);
}
/**
* ditto
*/
@safe pure nothrow @nogc bool opEquals(ref const scope UUID s) const
{
return ulongs[0] == s.ulongs[0] && ulongs[1] == s.ulongs[1];
}
/**
* ditto
*/
@safe pure nothrow @nogc int opCmp(const UUID s) const
{
import std.algorithm.comparison : cmp;
return cmp(this.data[], s.data[]);
}
/**
* ditto
*/
@safe pure nothrow @nogc int opCmp(ref const scope UUID s) const
{
import std.algorithm.comparison : cmp;
return cmp(this.data[], s.data[]);
}
/**
* ditto
*/
@safe pure nothrow @nogc UUID opAssign(const UUID s)
{
ulongs[0] = s.ulongs[0];
ulongs[1] = s.ulongs[1];
return this;
}
/**
* ditto
*/
@safe pure nothrow @nogc UUID opAssign(ref const scope UUID s)
{
ulongs[0] = s.ulongs[0];
ulongs[1] = s.ulongs[1];
return this;
}
/**
* ditto
*/
//MurmurHash2
@safe pure nothrow @nogc size_t toHash() const
{
static if (size_t.sizeof == 4)
{
enum uint m = 0x5bd1e995;
enum uint n = 16;
enum uint r = 24;
uint h = n;
uint k = uints[0];
k *= m;
k ^= k >> r;
k *= m;
h ^= k;
h *= m;
k = uints[1];
k *= m;
k ^= k >> r;
k *= m;
h ^= k;
h *= m;
k = uints[2];
k *= m;
k ^= k >> r;
k *= m;
h ^= k;
h *= m;
k = uints[3];
k *= m;
k ^= k >> r;
k *= m;
h ^= k;
h *= m;
}
else
{
enum ulong m = 0xc6a4a7935bd1e995UL;
enum ulong n = m * 16;
enum uint r = 47;
ulong h = n;
ulong k = ulongs[0];
k *= m;
k ^= k >> r;
k *= m;
h ^= k;
h *= m;
k = ulongs[1];
k *= m;
k ^= k >> r;
k *= m;
h ^= k;
h *= m;
}
return h;
}
@safe unittest
{
assert(UUID("00000000-0000-0000-0000-000000000000") == UUID.init);
int[UUID] test = [UUID("8a94f585-d180-44f7-8929-6fca0189c7d0") : 1,
UUID("7c351fd4-b860-4ee3-bbdc-7f79f3dfb00a") : 2,
UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1") : 3];
assert(test[UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1")] == 3);
import std.algorithm;
UUID[] ids = [UUID("8a94f585-d180-44f7-8929-6fca0189c7d0"),
UUID("7c351fd4-b860-4ee3-bbdc-7f79f3dfb00a"),
UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1")];
sort(ids);
auto id2 = ids.dup;
ids = [UUID("7c351fd4-b860-4ee3-bbdc-7f79f3dfb00a"),
UUID("8a94f585-d180-44f7-8929-6fca0189c7d0"),
UUID("9ac0a4e5-10ee-493a-86fc-d29eeb82ecc1")];
sort(ids);
assert(ids == id2);
//test comparsion
UUID u1;
UUID u2 = UUID(cast(ubyte[16])[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]);
UUID u3 = UUID(cast(ubyte[16])[255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255]);
assert(u1 == u1);
assert(u1 != u2);
assert(u1 < u2);
assert(u2 < u3);
assert(u1 <= u1);
assert(u1 <= u2);
assert(u2 <= u3);
assert(u2 >= u2);
assert(u3 >= u2);
assert(u3 >= u3);
assert(u2 >= u1);
assert(u3 >= u1);
// test hash
assert(u1.toHash() != u2.toHash());
assert(u2.toHash() != u3.toHash());
assert(u3.toHash() != u1.toHash());
}
/**
* Write the UUID into `sink` as an ASCII string in the canonical form,
* which is 36 characters in the form "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
* Params:
* sink = OutputRange or writeable array at least 36 entries long
*/
void toString(Writer)(scope Writer sink) const
{
char[36] result = void;
foreach (pos; skipSeq)
result[pos] = '-';
foreach (i, pos; byteSeq)
{
const uint entry = this.data[i];
const uint hi = entry >> 4;
result[pos ] = toChar!char(hi);
const uint lo = (entry) & 0x0F;
result[pos+1] = toChar!char(lo);
}
static if (!__traits(compiles, put(sink, result[])) || isSomeString!Writer)
{
foreach (i, c; result)
sink[i] = cast(typeof(sink[i]))c;
}
else
{
put(sink, result[]);
}
}
/**
* Return the UUID as a string in the canonical form.
*/
@trusted pure nothrow string toString() const
{
import std.exception : assumeUnique;
auto result = new char[36];
toString(result);
return result.assumeUnique;
}
///
@safe pure unittest
{
immutable str = "8ab3060e-2cba-4f23-b74c-b52db3bdfb46";
auto id = UUID(str);
assert(id.toString() == str);
}
@safe pure nothrow @nogc unittest
{
import std.meta : AliasSeq;
static foreach (Char; AliasSeq!(char, wchar, dchar))
{{
alias String = immutable(Char)[];
//CTFE
enum String s = "8ab3060e-2cba-4f23-b74c-b52db3bdfb46";
enum id = UUID(s);
static if (is(Char == char))
{
enum p = id.toString();
static assert(s == p);
}
//nogc
Char[36] str;
id.toString(str[]);
assert(str == s);
}}
}
@system pure nothrow @nogc unittest
{
// @system due to cast
import std.encoding : Char = AsciiChar;
enum utfstr = "8ab3060e-2cba-4f23-b74c-b52db3bdfb46";
alias String = immutable(Char)[];
enum String s = cast(String) utfstr;
enum id = UUID(utfstr);
//nogc
Char[36] str;
id.toString(str[]);
assert(str == s);
}
@safe unittest
{
auto u1 = UUID(cast(ubyte[16])[138, 179, 6, 14, 44, 186, 79,
35, 183, 76, 181, 45, 179, 189, 251, 70]);
assert(u1.toString() == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
u1 = UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
assert(u1.toString() == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
char[] buf;
void sink(scope const(char)[] data)
{
buf ~= data;
}
u1.toString(&sink);
assert(buf == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
}
}
///
@safe unittest
{
UUID id;
assert(id.empty);
id = randomUUID;
assert(!id.empty);
id = UUID(cast(ubyte[16]) [138, 179, 6, 14, 44, 186, 79,
35, 183, 76, 181, 45, 179, 189, 251, 70]);
assert(id.toString() == "8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
}
/**
* This function generates a name based (Version 3) UUID from a namespace UUID and a name.
* If no namespace UUID was passed, the empty UUID `UUID.init` is used.
*
* Note:
* The default namespaces ($(LREF dnsNamespace), ...) defined by
* this module should be used when appropriate.
*
* RFC 4122 recommends to use Version 5 UUIDs (SHA-1) instead of Version 3
* UUIDs (MD5) for new applications.
*
* CTFE:
* CTFE is not supported.
*
* Note:
* RFC 4122 isn't very clear on how UUIDs should be generated from names.
* It is possible that different implementations return different UUIDs
* for the same input, so be warned. The implementation for UTF-8 strings
* and byte arrays used by `std.uuid` is compatible with Boost's implementation.
* `std.uuid` guarantees that the same input to this function will generate
* the same output at any time, on any system (this especially means endianness
* doesn't matter).
*
* Note:
* This function does not provide overloads for wstring and dstring, as
* there's no clear answer on how that should be implemented. It could be
* argued, that string, wstring and dstring input should have the same output,
* but that wouldn't be compatible with Boost, which generates different output
* for strings and wstrings. It's always possible to pass wstrings and dstrings
* by using the ubyte[] function overload (but be aware of endianness issues!).
*/
@safe pure nothrow @nogc UUID md5UUID(const(char[]) name, const UUID namespace = UUID.init)
{
return md5UUID(cast(const(ubyte[]))name, namespace);
}
/// ditto
@safe pure nothrow @nogc UUID md5UUID(const(ubyte[]) data, const UUID namespace = UUID.init)
{
import std.digest.md : MD5;
MD5 hash;
hash.start();
/*
* NOTE: RFC 4122 says namespace should be converted to big-endian.
* We always keep the UUID data in big-endian representation, so
* that's fine
*/
hash.put(namespace.data[]);
hash.put(data[]);
UUID u;
u.data = hash.finish();
//set variant
//must be 0b10xxxxxx
u.data[8] &= 0b10111111;
u.data[8] |= 0b10000000;
//set version
//must be 0b0011xxxx
u.data[6] &= 0b00111111;
u.data[6] |= 0b00110000;
return u;
}
///
@safe unittest
{
//Use default UUID.init namespace
auto simpleID = md5UUID("test.uuid.any.string");
//use a name-based id as namespace
auto namespace = md5UUID("my.app");
auto id = md5UUID("some-description", namespace);
}
@safe pure unittest
{
auto simpleID = md5UUID("test.uuid.any.string");
assert(simpleID.data == cast(ubyte[16])[126, 206, 86, 72, 29, 233, 62, 213, 178, 139, 198, 136,
188, 135, 153, 123]);
auto namespace = md5UUID("my.app");
auto id = md5UUID("some-description", namespace);
assert(id.data == cast(ubyte[16])[166, 138, 167, 79, 48, 219, 55, 166, 170, 103, 39, 73, 216,
150, 144, 164]);
auto constTest = md5UUID(cast(const(char)[])"test");
constTest = md5UUID(cast(const(char[]))"test");
char[] mutable = "test".dup;
id = md5UUID(mutable, namespace);
const(ubyte)[] data = cast(ubyte[])[0,1,2,244,165,222];
id = md5UUID(data);
assert(id.data == cast(ubyte[16])[16, 50, 29, 247, 243, 185, 61, 178, 157, 100, 253, 236, 73,
76, 51, 47]);
assert(id.variant == UUID.Variant.rfc4122);
assert(id.uuidVersion == UUID.Version.nameBasedMD5);
auto correct = UUID("3d813cbb-47fb-32ba-91df-831e1593ac29");
auto u = md5UUID("www.widgets.com", dnsNamespace);
//enum ctfeId = md5UUID("www.widgets.com", dnsNamespace);
//assert(ctfeId == u);
assert(u == correct);
assert(u.variant == UUID.Variant.rfc4122);
assert(u.uuidVersion == UUID.Version.nameBasedMD5);
}
/**
* This function generates a name based (Version 5) UUID from a namespace
* UUID and a name.
* If no namespace UUID was passed, the empty UUID `UUID.init` is used.
*
* Note:
* The default namespaces ($(LREF dnsNamespace), ...) defined by
* this module should be used when appropriate.
*
* CTFE:
* CTFE is not supported.
*
* Note:
* RFC 4122 isn't very clear on how UUIDs should be generated from names.
* It is possible that different implementations return different UUIDs
* for the same input, so be warned. The implementation for UTF-8 strings
* and byte arrays used by `std.uuid` is compatible with Boost's implementation.
* `std.uuid` guarantees that the same input to this function will generate
* the same output at any time, on any system (this especially means endianness
* doesn't matter).
*
* Note:
* This function does not provide overloads for wstring and dstring, as
* there's no clear answer on how that should be implemented. It could be
* argued, that string, wstring and dstring input should have the same output,
* but that wouldn't be compatible with Boost, which generates different output
* for strings and wstrings. It's always possible to pass wstrings and dstrings
* by using the ubyte[] function overload (but be aware of endianness issues!).
*/
@safe pure nothrow @nogc UUID sha1UUID(scope const(char)[] name, scope const UUID namespace = UUID.init)
{
return sha1UUID(cast(const(ubyte[]))name, namespace);
}
/// ditto
@safe pure nothrow @nogc UUID sha1UUID(scope const(ubyte)[] data, scope const UUID namespace = UUID.init)
{
import std.digest.sha : SHA1;
SHA1 sha;
sha.start();
/*
* NOTE: RFC 4122 says namespace should be converted to big-endian.
* We always keep the UUID data in big-endian representation, so
* that's fine
*/
sha.put(namespace.data[]);
sha.put(data[]);
auto hash = sha.finish();
auto u = UUID();
u.data[] = hash[0 .. 16];
//set variant
//must be 0b10xxxxxx
u.data[8] &= 0b10111111;
u.data[8] |= 0b10000000;
//set version
//must be 0b0101xxxx
u.data[6] &= 0b01011111;
u.data[6] |= 0b01010000;
return u;
}
///
@safe unittest
{
//Use default UUID.init namespace
auto simpleID = sha1UUID("test.uuid.any.string");
//use a name-based id as namespace
auto namespace = sha1UUID("my.app");
auto id = sha1UUID("some-description", namespace);
}
@safe pure unittest
{
auto simpleID = sha1UUID("test.uuid.any.string");
assert(simpleID.data == cast(ubyte[16])[16, 209, 239, 61, 99, 12, 94, 70, 159, 79, 255, 250,
131, 79, 14, 147]);
auto namespace = sha1UUID("my.app");
auto id = sha1UUID("some-description", namespace);
assert(id.data == cast(ubyte[16])[225, 94, 195, 219, 126, 75, 83, 71, 157, 52, 247, 43, 238, 248,
148, 46]);
auto constTest = sha1UUID(cast(const(char)[])"test");
constTest = sha1UUID(cast(const(char[]))"test");
char[] mutable = "test".dup;
id = sha1UUID(mutable, namespace);
const(ubyte)[] data = cast(ubyte[])[0,1,2,244,165,222];
id = sha1UUID(data);
assert(id.data == cast(ubyte[16])[60, 65, 92, 240, 96, 46, 95, 238, 149, 100, 12, 64, 199, 194,
243, 12]);
auto correct = UUID("21f7f8de-8051-5b89-8680-0195ef798b6a");
auto u = sha1UUID("www.widgets.com", dnsNamespace);
assert(u == correct);
assert(u.variant == UUID.Variant.rfc4122);
assert(u.uuidVersion == UUID.Version.nameBasedSHA1);
}
/**
* This function generates a random number based UUID from a random
* number generator.
*
* This function is not supported at compile time.
*
* Params:
* randomGen = uniform RNG
* See_Also: $(REF isUniformRNG, std,random)
*/
@safe UUID randomUUID()
{
import std.random : rndGen;
// A PRNG with fewer than `n` bytes of state cannot produce
// every distinct `n` byte sequence.
static if (typeof(rndGen).sizeof >= UUID.sizeof)
{
return randomUUID(rndGen);
}
else
{
import std.random : unpredictableSeed, Xorshift192;
static assert(Xorshift192.sizeof >= UUID.sizeof);
static Xorshift192 rng;
static bool initialized;
if (!initialized)
{
rng.seed(unpredictableSeed);
initialized = true;
}
return randomUUID(rng);
}
}
/// ditto
UUID randomUUID(RNG)(ref RNG randomGen)
if (isInputRange!RNG && isIntegral!(ElementType!RNG))
{
import std.random : isUniformRNG;
static assert(isUniformRNG!RNG, "randomGen must be a uniform RNG");
alias E = ElementEncodingType!RNG;
enum size_t elemSize = E.sizeof;
static assert(elemSize <= 16);
static assert(16 % elemSize == 0);
UUID u;
foreach (ref E e ; u.asArrayOf!E())
{
e = randomGen.front;
randomGen.popFront();
}
//set variant
//must be 0b10xxxxxx
u.data[8] &= 0b10111111;
u.data[8] |= 0b10000000;
//set version
//must be 0b0100xxxx
u.data[6] &= 0b01001111;
u.data[6] |= 0b01000000;
return u;
}
///
@safe unittest
{
import std.random : Xorshift192, unpredictableSeed;
//simple call
auto uuid = randomUUID();
//provide a custom RNG. Must be seeded manually.
Xorshift192 gen;
gen.seed(unpredictableSeed);
auto uuid3 = randomUUID(gen);
}
@safe unittest
{
import std.random : Xorshift192, unpredictableSeed;
//simple call
auto uuid = randomUUID();
//provide a custom RNG. Must be seeded manually.
Xorshift192 gen;
gen.seed(unpredictableSeed);
auto uuid3 = randomUUID(gen);
auto u1 = randomUUID();
auto u2 = randomUUID();
assert(u1 != u2);
assert(u1.variant == UUID.Variant.rfc4122);
assert(u1.uuidVersion == UUID.Version.randomNumberBased);
}
/**
* This is a less strict parser compared to the parser used in the
* UUID constructor. It enforces the following rules:
*
* $(UL
* $(LI hex numbers are always two hexdigits([0-9a-fA-F]))
* $(LI there must be exactly 16 such pairs in the input, not less, not more)
* $(LI there can be exactly one dash between two hex-pairs, but not more)
* $(LI there can be multiple characters enclosing the 16 hex pairs,
* as long as these characters do not contain [0-9a-fA-F])
* )
*
* Note:
* Like most parsers, it consumes its argument. This means:
* -------------------------
* string s = "8AB3060E-2CBA-4F23-b74c-B52Db3BDFB46";
* parseUUID(s);
* assert(s == "");
* -------------------------
*
* Throws:
* $(LREF UUIDParsingException) if the input is invalid
*
* CTFE:
* This function is supported in CTFE code. Note that error messages
* caused by a malformed UUID parsed at compile time can be cryptic,
* but errors are detected and reported at compile time.
*/
UUID parseUUID(T)(T uuidString)
if (isSomeString!T)
{
return parseUUID(uuidString);
}
///ditto
UUID parseUUID(Range)(ref Range uuidRange)
if (isInputRange!Range && isSomeChar!(ElementType!Range))
{
import std.ascii : isHexDigit;
import std.conv : ConvException, parse;
static if (isForwardRange!Range)
auto errorCopy = uuidRange.save;
void parserError()(size_t pos, UUIDParsingException.Reason reason, string message, Throwable next = null,
string file = __FILE__, size_t line = __LINE__)
{
static if (isForwardRange!Range)
{
import std.conv : to;
static if (isInfinite!Range)
{
throw new UUIDParsingException(to!string(take(errorCopy, pos)), pos, reason, message,
next, file, line);
}
else
{
throw new UUIDParsingException(to!string(errorCopy), pos, reason, message, next, file,
line);
}
}
else
{
throw new UUIDParsingException("", pos, reason, message, next, file, line);
}
}
static if (hasLength!Range)
{
import std.conv : to;
if (uuidRange.length < 32)
{
throw new UUIDParsingException(to!string(uuidRange), 0, UUIDParsingException.Reason.tooLittle,
"Insufficient Input");
}
}
UUID result;
size_t consumed;
size_t element = 0;
//skip garbage
size_t skip()()
{
size_t skipped;
while (!uuidRange.empty && !isHexDigit(uuidRange.front))
{
skipped++;
uuidRange.popFront();
}
return skipped;
}
consumed += skip();
if (uuidRange.empty)
parserError(consumed, UUIDParsingException.Reason.tooLittle, "Insufficient Input");
bool dashAllowed = false;
parseLoop: while (!uuidRange.empty)
{
immutable character = uuidRange.front;
if (character == '-')
{
if (!dashAllowed)
parserError(consumed, UUIDParsingException.Reason.invalidChar, "Unexpected '-'");
else
dashAllowed = false;
consumed++;
}
else if (!isHexDigit(character))
{
parserError(consumed, UUIDParsingException.Reason.invalidChar,
"Unexpected character (wanted a hexDigit)");
}
else
{
try
{
consumed += 2;
static if (isSomeString!Range)
{
if (uuidRange.length < 2)
{
parserError(consumed, UUIDParsingException.Reason.tooLittle,
"Insufficient Input");
}
auto part = uuidRange[0 .. 2];
result.data[element++] = parse!ubyte(part, 16);
uuidRange.popFront();
}
else
{
dchar[2] copyBuf;
copyBuf[0] = character;
uuidRange.popFront();
if (uuidRange.empty)
{
parserError(consumed, UUIDParsingException.Reason.tooLittle,
"Insufficient Input");
}
copyBuf[1] = uuidRange.front;
auto part = copyBuf[];
result.data[element++] = parse!ubyte(part, 16);
}
if (element == 16)
{
uuidRange.popFront();
break parseLoop;
}
dashAllowed = true;
}
catch (ConvException e)
{
parserError(consumed, UUIDParsingException.Reason.invalidChar,
"Couldn't parse ubyte", e);
}
}
uuidRange.popFront();
}
assert(element <= 16);
if (element < 16)
parserError(consumed, UUIDParsingException.Reason.tooLittle, "Insufficient Input");
consumed += skip();
if (!uuidRange.empty)
parserError(consumed, UUIDParsingException.Reason.invalidChar, "Unexpected character");
return result;
}
///
@safe unittest
{
auto id = parseUUID("8AB3060E-2CBA-4F23-b74c-B52Db3BDFB46");
//no dashes
id = parseUUID("8ab3060e2cba4f23b74cb52db3bdfb46");
//dashes at different positions
id = parseUUID("8a-b3-06-0e2cba4f23b74c-b52db3bdfb-46");
//leading / trailing characters
id = parseUUID("{8ab3060e-2cba-4f23-b74c-b52db3bdfb46}");
//unicode
id = parseUUID("ü8ab3060e2cba4f23b74cb52db3bdfb46ü");
//multiple trailing/leading characters
id = parseUUID("///8ab3060e2cba4f23b74cb52db3bdfb46||");
//Can also be used in CTFE, for example as UUID literals:
enum ctfeID = parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
//here parsing is done at compile time, no runtime overhead!
}
@safe pure unittest
{
import std.conv : to;
import std.exception;
import std.meta;
struct TestRange(bool forward)
{
dstring input;
@property dchar front()
{
return input.front;
}
void popFront()
{
input.popFront();
}
@property bool empty()
{
return input.empty;
}
static if (forward)
{
@property TestRange!true save()
{
return this;
}
}
}
alias TestInputRange = TestRange!false;
alias TestForwardRange = TestRange!true;
assert(isInputRange!TestInputRange);
assert(is(ElementType!TestInputRange == dchar));
assert(isInputRange!TestForwardRange);
assert(isForwardRange!TestForwardRange);
assert(is(ElementType!TestForwardRange == dchar));
//Helper function for unittests - Need to pass ranges by ref
UUID parseHelper(T)(string input)
{
static if (is(T == TestInputRange) || is(T == TestForwardRange))
{
T range = T(to!dstring(input));
return parseUUID(range);
}
else
return parseUUID(to!T(input));
}
static foreach (S; AliasSeq!(char[], const(char)[], immutable(char)[],
wchar[], const(wchar)[], immutable(wchar)[],
dchar[], const(dchar)[], immutable(dchar)[],
immutable(char[]), immutable(wchar[]), immutable(dchar[]),
TestForwardRange, TestInputRange))
{{
//Verify examples.
auto id = parseHelper!S("8AB3060E-2CBA-4F23-b74c-B52Db3BDFB46");
//no dashes
id = parseHelper!S("8ab3060e2cba4f23b74cb52db3bdfb46");
//dashes at different positions
id = parseHelper!S("8a-b3-06-0e2cba4f23b74c-b52db3bdfb-46");
//leading / trailing characters
id = parseHelper!S("{8ab3060e-2cba-4f23-b74c-b52db3bdfb46}");
//unicode
id = parseHelper!S("ü8ab3060e2cba4f23b74cb52db3bdfb46ü");
//multiple trailing/leading characters
id = parseHelper!S("///8ab3060e2cba4f23b74cb52db3bdfb46||");
enum ctfeId = parseHelper!S("8ab3060e-2cba-4f23-b74c-b52db3bdfb46");
assert(parseHelper!S("8AB3060E-2cba-4f23-b74c-b52db3bdfb46") == ctfeId);
//Test valid, working cases
assert(parseHelper!S("00000000-0000-0000-0000-000000000000").empty);
assert(parseHelper!S("8AB3060E-2CBA-4F23-b74c-B52Db3BDFB46").data
== [138, 179, 6, 14, 44, 186, 79, 35, 183, 76, 181, 45, 179, 189, 251, 70]);
assert(parseHelper!S("5668122d-9df0-49a4-ad0b-b9b0a57f886a").data
== [86, 104, 18, 45, 157, 240, 73, 164, 173, 11, 185, 176, 165, 127, 136, 106]);
//wstring / dstring
assert(parseHelper!S("5668122d-9df0-49a4-ad0b-b9b0a57f886a").data
== [86, 104, 18, 45, 157, 240, 73, 164, 173, 11, 185, 176, 165, 127, 136, 106]);
assert(parseHelper!S("5668122d-9df0-49a4-ad0b-b9b0a57f886a").data
== [86, 104, 18, 45, 157, 240, 73, 164, 173, 11, 185, 176, 165, 127, 136, 106]);
//Test too short UUIDS
auto except = collectException!UUIDParsingException(
parseHelper!S("5668122d-9df0-49a4-ad0b-b9b0a57f886"));
assert(except && except.reason == UUIDParsingException.Reason.tooLittle);
//Test too long UUIDS
except = collectException!UUIDParsingException(
parseHelper!S("5668122d-9df0-49a4-ad0b-b9b0a57f886aa"));
assert(except && except.reason == UUIDParsingException.Reason.invalidChar);
//Test too long UUIDS 2
except = collectException!UUIDParsingException(
parseHelper!S("5668122d-9df0-49a4-ad0b-b9b0a57f886a-aa"));
assert(except && except.reason == UUIDParsingException.Reason.invalidChar);
//Test dashes
assert(parseHelper!S("8ab3060e2cba-4f23-b74c-b52db3bdfb46")
== parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"));
assert(parseHelper!S("8ab3-060e2cba-4f23-b74c-b52db3bdfb46")
== parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"));
assert(parseHelper!S("8ab3060e2cba4f23b74cb52db3bdfb46")
== parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"));
except = collectException!UUIDParsingException(
parseHelper!S("8-ab3060e2cba-4f23-b74c-b52db3bdfb46"));
assert(except && except.reason == UUIDParsingException.Reason.invalidChar);
//Test leading/trailing characters
assert(parseHelper!S("{8ab3060e-2cba-4f23-b74c-b52db3bdfb46}")
== parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"));
assert(parseHelper!S("{8ab3060e2cba4f23b74cb52db3bdfb46}")
== parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"));
//Boost test
auto u_increasing = UUID(cast(ubyte[16])[0x01, 0x23, 0x45, 0x67, 0x89, 0xab,
0xcd, 0xef,0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]);
assert(parseHelper!S("0123456789abcdef0123456789ABCDEF") == UUID(cast(ubyte[16])[0x01,
0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]));
//unicode
assert(parseHelper!S("ü8ab3060e2cba4f23b74cb52db3bdfb46ü")
== parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"));
//multiple trailing/leading characters
assert(parseHelper!S("///8ab3060e2cba4f23b74cb52db3bdfb46||")
== parseUUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"));
}}
// Test input range with non-dchar element type.
{
import std.utf : byCodeUnit;
auto range = "8AB3060E-2CBA-4F23-b74c-B52Db3BDFB46".byCodeUnit;
assert(parseUUID(range).data == [138, 179, 6, 14, 44, 186, 79, 35, 183, 76, 181, 45, 179, 189, 251, 70]);
}
}
/**
* Default namespace from RFC 4122
*
* Name string is a fully-qualified domain name
*/
enum dnsNamespace = UUID("6ba7b810-9dad-11d1-80b4-00c04fd430c8");
/**
* Default namespace from RFC 4122
*
* Name string is a URL
*/
enum urlNamespace = UUID("6ba7b811-9dad-11d1-80b4-00c04fd430c8");
/**
* Default namespace from RFC 4122
*
* Name string is an ISO OID
*/
enum oidNamespace = UUID("6ba7b812-9dad-11d1-80b4-00c04fd430c8");
/**
* Default namespace from RFC 4122
*
* Name string is an X.500 DN (in DER or a text output format)
*/
enum x500Namespace = UUID("6ba7b814-9dad-11d1-80b4-00c04fd430c8");
/**
* Regex string to extract UUIDs from text.
*/
enum uuidRegex = "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}"~
"-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}";
///
@safe unittest
{
import std.algorithm;
import std.regex;
string test = "Lorem ipsum dolor sit amet, consetetur "~
"6ba7b814-9dad-11d1-80b4-00c04fd430c8 sadipscing \n"~
"elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore \r\n"~
"magna aliquyam erat, sed diam voluptua. "~
"8ab3060e-2cba-4f23-b74c-b52db3bdfb46 At vero eos et accusam et "~
"justo duo dolores et ea rebum.";
auto r = regex(uuidRegex, "g");
UUID[] found;
foreach (c; match(test, r))
{
found ~= UUID(c.hit);
}
assert(found == [
UUID("6ba7b814-9dad-11d1-80b4-00c04fd430c8"),
UUID("8ab3060e-2cba-4f23-b74c-b52db3bdfb46"),
]);
}
/**
* This exception is thrown if an error occurs when parsing a UUID
* from a string.
*/
public class UUIDParsingException : Exception
{
/**
* The reason why parsing the UUID string failed (if known)
*/
enum Reason
{
unknown, ///
tooLittle, ///The passed in input was correct, but more input was expected.
tooMuch, ///The input data is too long (There's no guarantee the first part of the data is valid)
invalidChar, ///Encountered an invalid character
}
///ditto
Reason reason;
///The original input string which should have been parsed.
string input;
///The position in the input string where the error occurred.
size_t position;
private this(string input, size_t pos, Reason why = Reason.unknown, string msg = "",
Throwable next = null, string file = __FILE__, size_t line = __LINE__) pure @trusted
{
import std.array : replace;
import std.format : format;
this.input = input;
this.position = pos;
this.reason = why;
string message = format("An error occured in the UUID parser: %s\n" ~
" * Input:\t'%s'\n * Position:\t%s", msg, replace(replace(input,
"\r", "\\r"), "\n", "\\n"), pos);
super(message, file, line, next);
}
}
///
@safe unittest
{
import std.exception : collectException;
const inputUUID = "this-is-an-invalid-uuid";
auto ex = collectException!UUIDParsingException(UUID(inputUUID));
assert(ex !is null); // check that exception was thrown
assert(ex.input == inputUUID);
assert(ex.position == 0);
assert(ex.reason == UUIDParsingException.Reason.tooLittle);
}
@safe unittest
{
auto ex = new UUIDParsingException("foo", 10, UUIDParsingException.Reason.tooMuch);
assert(ex.input == "foo");
assert(ex.position == 10);
assert(ex.reason == UUIDParsingException.Reason.tooMuch);
}