/** * 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 = $(TT $1)  * MYREF3 = `$1` */ /* 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)))); } /** * * 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!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); }