| /** |
| * 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 |
| * $(D 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> |
| * MYREF3 = <a href="#$2">$(D $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 |
| * $(D 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 in ubyte[16] uuidData) |
| { |
| data = uuidData; |
| } |
| /// ditto |
| @safe pure nothrow @nogc this(in 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; |
| |
| 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(in 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 in UUID s) const |
| { |
| return ulongs[0] == s.ulongs[0] && ulongs[1] == s.ulongs[1]; |
| } |
| |
| /** |
| * ditto |
| */ |
| @safe pure nothrow @nogc int opCmp(in UUID s) const |
| { |
| import std.algorithm.comparison : cmp; |
| return cmp(this.data[], s.data[]); |
| } |
| |
| /** |
| * ditto |
| */ |
| @safe pure nothrow @nogc int opCmp(ref in UUID s) const |
| { |
| import std.algorithm.comparison : cmp; |
| return cmp(this.data[], s.data[]); |
| } |
| |
| /** |
| * ditto |
| */ |
| @safe pure nothrow @nogc UUID opAssign(in UUID s) |
| { |
| ulongs[0] = s.ulongs[0]; |
| ulongs[1] = s.ulongs[1]; |
| return this; |
| } |
| |
| /** |
| * ditto |
| */ |
| @safe pure nothrow @nogc UUID opAssign(ref in 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); |
| } |
| foreach (i, c; result) |
| { |
| static if (__traits(compiles, put(sink, c))) |
| put(sink, c); |
| else |
| sink[i] = cast(typeof(sink[i]))c; |
| } |
| } |
| |
| /** |
| * 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; |
| 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(const(char)[] data) |
| { |
| buf ~= data; |
| } |
| u1.toString(&sink); |
| assert(buf == "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 $(D 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 $(D std.uuid) is compatible with Boost's implementation. |
| * $(D 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 $(D 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 $(D std.uuid) is compatible with Boost's implementation. |
| * $(D 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(in char[] name, const UUID namespace = UUID.init) |
| { |
| return sha1UUID(cast(const(ubyte[]))name, namespace); |
| } |
| |
| /// ditto |
| @safe pure nothrow @nogc UUID sha1UUID(in ubyte[] data, 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; |
| return randomUUID(rndGen); |
| } |
| |
| /// 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); |
| } |
| |
| /* |
| * Original boost.uuid used Mt19937, we don't want |
| * to use anything worse than that. If Random is changed |
| * to something else, this assert and the randomUUID function |
| * have to be updated. |
| */ |
| @safe unittest |
| { |
| import std.random : rndGen, Mt19937; |
| static assert(is(typeof(rndGen) == Mt19937)); |
| } |
| |
| @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 |
| && is(Unqual!(ElementType!Range) == dchar)) |
| { |
| 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)); |
| } |
| |
| 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")); |
| } |
| } |
| |
| /** |
| * 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); |
| } |