From 281861195d103ad7ef4e3b7458a29113c6f1f2ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Ludwig?= Date: Fri, 16 Feb 2024 22:27:10 +0100 Subject: [PATCH 1/2] Forward vibe:data to vibe-serialization. The code is now in the vibe-serialization repository. --- data/dub.sdl | 6 +- data/vibe/data/bson.d | 1974 ------------------ data/vibe/data/internal/conv.d | 32 - data/vibe/data/json.d | 2958 --------------------------- data/vibe/data/serialization.d | 2263 -------------------- data/vibe/internal/meta/codegen.d | 409 ---- data/vibe/internal/meta/funcattr.d | 917 --------- data/vibe/internal/meta/traits.d | 472 ----- data/vibe/internal/meta/typetuple.d | 123 -- data/vibe/internal/meta/uda.d | 239 --- data/vibe/internal/rangeutil.d | 32 - run-ci.sh | 1 - 12 files changed, 2 insertions(+), 9424 deletions(-) delete mode 100644 data/vibe/data/bson.d delete mode 100644 data/vibe/data/internal/conv.d delete mode 100644 data/vibe/data/json.d delete mode 100644 data/vibe/data/serialization.d delete mode 100644 data/vibe/internal/meta/codegen.d delete mode 100644 data/vibe/internal/meta/funcattr.d delete mode 100644 data/vibe/internal/meta/traits.d delete mode 100644 data/vibe/internal/meta/typetuple.d delete mode 100644 data/vibe/internal/meta/uda.d delete mode 100644 data/vibe/internal/rangeutil.d diff --git a/data/dub.sdl b/data/dub.sdl index c8727f73ae..d523b1c337 100644 --- a/data/dub.sdl +++ b/data/dub.sdl @@ -1,6 +1,4 @@ name "data" description "Data format and serialization support" -dependency "vibe-container" version=">=1.2.2 <2.0.0-0" -targetType "library" -sourcePaths "." -importPaths "." +dependency "vibe-serialization" version=">=1.0.0-0 <2.0.0-0" +targetType "none" diff --git a/data/vibe/data/bson.d b/data/vibe/data/bson.d deleted file mode 100644 index c946fb0b90..0000000000 --- a/data/vibe/data/bson.d +++ /dev/null @@ -1,1974 +0,0 @@ -/** - BSON serialization and value handling. - - Copyright: © 2012-2015 Sönke Ludwig - License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. - Authors: Sönke Ludwig -*/ -module vibe.data.bson; - -/// -unittest { - void manipulateBson(Bson b) - { - import std.stdio; - - // retrieving the values is done using get() - assert(b["name"].get!string == "Example"); - assert(b["id"].get!int == 1); - - // semantic conversions can be done using to() - assert(b["id"].to!string == "1"); - - // prints: - // name: "Example" - // id: 1 - foreach (string key, value; b) - writefln("%s: %s", key, value); - - // print out with JSON syntax: {"name": "Example", "id": 1} - writefln("BSON: %s", b.toString()); - - // DEPRECATED: object members can be accessed using member syntax, just like in JavaScript - //j = Bson.emptyObject; - //j.name = "Example"; - //j.id = 1; - } -} - -/// Constructing `Bson` objects -unittest { - // construct a BSON object {"field1": "foo", "field2": 42, "field3": true} - - // using the constructor - Bson b1 = Bson(["field1": Bson("foo"), "field2": Bson(42), "field3": Bson(true)]); - - // using piecewise construction - Bson b2 = Bson.emptyObject; - b2["field1"] = "foo"; - b2["field2"] = 42; - b2["field3"] = true; - - // using serialization - struct S { - string field1; - int field2; - bool field3; - } - Bson b3 = S("foo", 42, true).serializeToBson(); -} - - -public import vibe.data.json; - -import vibe.data.internal.conv : enumToString; - -import std.algorithm; -import std.array; -import std.base64; -import std.bitmanip; -import std.conv; -import std.datetime; -import std.uuid: UUID; -import std.exception; -import std.range; -import std.traits; -import std.typecons : Tuple, tuple; - - -alias bdata_t = immutable(ubyte)[]; - -/** - Represents a BSON value. - - -*/ -struct Bson { -@safe: - - /// Represents the type of a BSON value - enum Type : ubyte { - end = 0x00, /// End marker - should never occur explicitly - double_ = 0x01, /// A 64-bit floating point value - string = 0x02, /// A UTF-8 string - object = 0x03, /// An object aka. dictionary of string to Bson - array = 0x04, /// An array of BSON values - binData = 0x05, /// Raw binary data (ubyte[]) - undefined = 0x06, /// Deprecated - objectID = 0x07, /// BSON Object ID (96-bit) - bool_ = 0x08, /// Boolean value - date = 0x09, /// Date value (UTC) - null_ = 0x0A, /// Null value - regex = 0x0B, /// Regular expression - dbRef = 0x0C, /// Deprecated - code = 0x0D, /// JaveScript code - symbol = 0x0E, /// Symbol/variable name - codeWScope = 0x0F, /// JavaScript code with scope - int_ = 0x10, /// 32-bit integer - timestamp = 0x11, /// Timestamp value - long_ = 0x12, /// 64-bit integer - minKey = 0xff, /// Internal value - maxKey = 0x7f, /// Internal value - - deprecated("Use `end` instead.") End = end, /// Compatibility alias - deprecated("Use `double_` instead.") Double = double_, /// Compatibility alias - deprecated("Use `string` instead.") String = string, /// Compatibility alias - deprecated("Use `object` instead.") Object = object, /// Compatibility alias - deprecated("Use `array` instead.") Array = array, /// Compatibility alias - deprecated("Use `binData` instead.") BinData = binData, /// Compatibility alias - deprecated("Use `undefined` instead.") Undefined = undefined, /// Compatibility alias - deprecated("Use `objectID` instead.") ObjectID = objectID, /// Compatibility alias - deprecated("Use `bool_` instead.") Bool = bool_, /// Compatibility alias - deprecated("Use `date` instead.") Date = date, /// Compatibility alias - deprecated("Use `null_` instead.") Null = null_, /// Compatibility alias - deprecated("Use `regex` instead.") Regex = regex, /// Compatibility alias - deprecated("Use `dBRef` instead.") DBRef = dbRef, /// Compatibility alias - deprecated("Use `code` instead.") Code = code, /// Compatibility alias - deprecated("Use `symbol` instead.") Symbol = symbol, /// Compatibility alias - deprecated("Use `codeWScope` instead.") CodeWScope = codeWScope, /// Compatibility alias - deprecated("Use `int_` instead.") Int = int_, /// Compatibility alias - deprecated("Use `timestamp` instead.") Timestamp = timestamp, /// Compatibility alias - deprecated("Use `long_` instead.") Long = long_, /// Compatibility alias - deprecated("Use `minKey` instead.") MinKey = minKey, /// Compatibility alias - deprecated("Use `maxKey` instead.") MaxKey = maxKey /// Compatibility alias - } - - // length + 0 byte end for empty lists (map, array) - private static immutable ubyte[] emptyListBytes = [5, 0, 0, 0, 0]; - - /// Returns a new, empty Bson value of type Object. - static @property Bson emptyObject() - { - Bson ret; - ret.m_type = Type.object; - ret.m_data = emptyListBytes; - return ret; - } - - /// Returns a new, empty Bson value of type Array. - static @property Bson emptyArray() - { - Bson ret; - ret.m_type = Type.array; - ret.m_data = emptyListBytes; - return ret; - } - - private { - Type m_type = Type.undefined; - bdata_t m_data; - } - - /** - Creates a new BSON value using raw data. - - A slice of the first bytes of `data` is stored, containg the data related to the value. An - exception is thrown if `data` is too short. - */ - this(Type type, bdata_t data) - { - m_type = type; - m_data = data; - final switch(type){ - case Type.end: m_data = null; break; - case Type.double_: m_data = m_data[0 .. 8]; break; - case Type.string: m_data = m_data[0 .. 4 + fromBsonData!int(m_data)]; break; - case Type.object: m_data = m_data[0 .. fromBsonData!int(m_data)]; break; - case Type.array: m_data = m_data[0 .. fromBsonData!int(m_data)]; break; - case Type.binData: m_data = m_data[0 .. 5 + fromBsonData!int(m_data)]; break; - case Type.undefined: m_data = null; break; - case Type.objectID: m_data = m_data[0 .. 12]; break; - case Type.bool_: m_data = m_data[0 .. 1]; break; - case Type.date: m_data = m_data[0 .. 8]; break; - case Type.null_: m_data = null; break; - case Type.regex: - auto tmp = m_data; - tmp.skipCString(); - tmp.skipCString(); - m_data = m_data[0 .. $ - tmp.length]; - break; - case Type.dbRef: m_data = m_data[0 .. 0]; assert(false, "Not implemented."); - case Type.code: m_data = m_data[0 .. 4 + fromBsonData!int(m_data)]; break; - case Type.symbol: m_data = m_data[0 .. 4 + fromBsonData!int(m_data)]; break; - case Type.codeWScope: m_data = m_data[0 .. 0]; assert(false, "Not implemented."); - case Type.int_: m_data = m_data[0 .. 4]; break; - case Type.timestamp: m_data = m_data[0 .. 8]; break; - case Type.long_: m_data = m_data[0 .. 8]; break; - case Type.minKey: m_data = null; break; - case Type.maxKey: m_data = null; break; - } - } - - /** - Initializes a new BSON value from the given D type. - */ - this(double value) { opAssign(value); } - /// ditto - this(scope const(char)[] value, Type type = Type.string) - { - assert(type == Type.string || type == Type.code || type == Type.symbol); - opAssign(value); - m_type = type; - } - /// ditto - this(in Bson[string] value) { opAssign(value); } - /// ditto - this(in Bson[] value) { opAssign(value); } - /// ditto - this(in BsonBinData value) { opAssign(value); } - /// ditto - this(in BsonObjectID value) { opAssign(value); } - /// ditto - this(bool value) { opAssign(value); } - /// ditto - this(in BsonDate value) { opAssign(value); } - /// ditto - this(typeof(null)) { opAssign(null); } - /// ditto - this(in BsonRegex value) { opAssign(value); } - /// ditto - this(int value) { opAssign(value); } - /// ditto - this(in BsonTimestamp value) { opAssign(value); } - /// ditto - this(long value) { opAssign(value); } - /// ditto - this(in Json value) { opAssign(value); } - /// ditto - this(in UUID value) { opAssign(value); } - - /** - Assigns a D type to a BSON value. - */ - void opAssign(const Bson other) - { - m_data = other.m_data; - m_type = other.m_type; - } - /// ditto - void opAssign(double value) - { - m_data = toBsonData(value).idup; - m_type = Type.double_; - } - /// ditto - void opAssign(scope const(char)[] value) - { - import std.utf; - import std.string : representation; - debug std.utf.validate(value); - auto app = appender!bdata_t(); - app.put(toBsonData(cast(int)value.length+1)); - app.put(value.representation); - app.put(cast(ubyte)0); - m_data = app.data; - m_type = Type.string; - } - /// ditto - void opAssign(in Bson[string] value) - { - auto app = appender!bdata_t(); - foreach( k, ref v; value ){ - app.put(cast(ubyte)v.type); - putCString(app, k); - app.put(v.data); - } - - auto dapp = appender!bdata_t(); - dapp.put(toBsonData(cast(int)app.data.length+5)); - dapp.put(app.data); - dapp.put(cast(ubyte)0); - m_data = dapp.data; - m_type = Type.object; - } - /// ditto - void opAssign(in Bson[] value) - { - auto app = appender!bdata_t(); - foreach( i, ref v; value ){ - app.put(v.type); - putCString(app, .to!string(i)); - app.put(v.data); - } - - auto dapp = appender!bdata_t(); - dapp.put(toBsonData(cast(int)app.data.length+5)); - dapp.put(app.data); - dapp.put(cast(ubyte)0); - m_data = dapp.data; - m_type = Type.array; - } - /// ditto - void opAssign(in BsonBinData value) - { - auto app = appender!bdata_t(); - app.put(toBsonData(cast(int)value.rawData.length)); - app.put(value.type); - app.put(value.rawData); - - m_data = app.data; - m_type = Type.binData; - } - /// ditto - void opAssign(in BsonObjectID value) - { - m_data = value.m_bytes.idup; - m_type = Type.objectID; - } - /// ditto - void opAssign(bool value) - { - m_data = [value ? 0x01 : 0x00]; - m_type = Type.bool_; - } - /// ditto - void opAssign(in BsonDate value) - { - m_data = toBsonData(value.m_time).idup; - m_type = Type.date; - } - /// ditto - void opAssign(typeof(null)) - { - m_data = null; - m_type = Type.null_; - } - /// ditto - void opAssign(in BsonRegex value) - { - auto app = appender!bdata_t(); - putCString(app, value.expression); - putCString(app, value.options); - m_data = app.data; - m_type = type.regex; - } - /// ditto - void opAssign(int value) - { - m_data = toBsonData(value).idup; - m_type = Type.int_; - } - /// ditto - void opAssign(in BsonTimestamp value) - { - m_data = toBsonData(value.m_time).idup; - m_type = Type.timestamp; - } - /// ditto - void opAssign(long value) - { - m_data = toBsonData(value).idup; - m_type = Type.long_; - } - /// ditto - void opAssign(in Json value) - @trusted { - auto app = appender!bdata_t(); - m_type = writeBson(app, value); - m_data = app.data; - } - /// ditto - void opAssign(in UUID value) - { - opAssign(BsonBinData(BsonBinData.Type.uuid, value.data.idup)); - } - - /** - Returns the BSON type of this value. - */ - @property Type type() const { return m_type; } - - bool isNull() const { return m_type == Type.null_; } - - /** - Returns the raw data representing this BSON value (not including the field name and type). - */ - @property bdata_t data() const { return m_data; } - - /** - Converts the BSON value to a D value. - - If the BSON type of the value does not match the D type, an exception is thrown. - - See_Also: `deserializeBson`, `opt`, `to` - */ - T opCast(T)() const { return get!T(); } - /// ditto - @property T get(T)() - const { - static if( is(T == double) ){ checkType(Type.double_); return fromBsonData!double(m_data); } - else static if( is(T == string) ){ - checkType(Type.string, Type.code, Type.symbol); - return cast(string)m_data[4 .. 4+fromBsonData!int(m_data)-1]; - } - else static if( is(Unqual!T == Bson[string]) || is(Unqual!T == const(Bson)[string]) ){ - checkType(Type.object); - Bson[string] ret; - auto d = m_data[4 .. $]; - while( d.length > 0 ){ - auto tp = cast(Type)d[0]; - if( tp == Type.end ) break; - d = d[1 .. $]; - auto key = skipCString(d); - auto value = Bson(tp, d); - d = d[value.data.length .. $]; - - ret[key] = value; - } - return cast(T)ret; - } - else static if( is(Unqual!T == Bson[]) || is(Unqual!T == const(Bson)[]) ){ - checkType(Type.array); - Bson[] ret; - auto d = m_data[4 .. $]; - while( d.length > 0 ){ - auto tp = cast(Type)d[0]; - if( tp == Type.end ) break; - /*auto key = */skipCString(d); // should be '0', '1', ... - auto value = Bson(tp, d); - d = d[value.data.length .. $]; - - ret ~= value; - } - return cast(T)ret; - } - else static if( is(T == BsonBinData) ){ - checkType(Type.binData); - auto size = fromBsonData!int(m_data); - auto type = cast(BsonBinData.Type)m_data[4]; - return BsonBinData(type, m_data[5 .. 5+size]); - } - else static if( is(T == BsonObjectID) ){ checkType(Type.objectID); return BsonObjectID(m_data[0 .. 12]); } - else static if( is(T == bool) ){ checkType(Type.bool_); return m_data[0] != 0; } - else static if( is(T == BsonDate) ){ checkType(Type.date); return BsonDate(fromBsonData!long(m_data)); } - else static if( is(T == SysTime) ){ - checkType(Type.date); - string data = cast(string)m_data[4 .. 4+fromBsonData!int(m_data)-1]; - return SysTime.fromISOString(data); - } - else static if( is(T == BsonRegex) ){ - checkType(Type.regex); - auto d = m_data[0 .. $]; - auto expr = skipCString(d); - auto options = skipCString(d); - return BsonRegex(expr, options); - } - else static if( is(T == int) ){ checkType(Type.int_); return fromBsonData!int(m_data); } - else static if( is(T == BsonTimestamp) ){ checkType(Type.timestamp); return BsonTimestamp(fromBsonData!long(m_data)); } - else static if( is(T == long) ){ checkType(Type.long_); return fromBsonData!long(m_data); } - else static if( is(T == Json) ){ - pragma(msg, "Bson.get!Json() and Bson.opCast!Json() will soon be removed. Please use Bson.toJson() instead."); - return this.toJson(); - } - else static if( is(T == UUID) ){ - checkType(Type.binData); - auto bbd = this.get!BsonBinData(); - enforce(bbd.type == BsonBinData.Type.uuid, "BsonBinData value is type '"~bbd.type.enumToString~"', expected to be uuid"); - const ubyte[16] b = bbd.rawData; - return UUID(b); - } - else static if( is(T == SysTime) ) { - checkType(Type.date); - return BsonDate(fromBsonData!long(m_data)).toSysTime(); - } - else static assert(false, "Cannot cast "~typeof(this).stringof~" to '"~T.stringof~"'."); - } - - /** - Converts the BSON value to a D value. - - In addition to working like `get!T`, the following conversions are done: - - `to!double` - casts int and long to double - - `to!long` - casts int to long - - `to!string` - returns the same as toString - - See_Also: `deserializeBson`, `opt`, `get` - */ - @property T to(T)() - const { - static if( is(T == double) ){ - checkType(Type.double_, Type.long_, Type.int_); - if (m_type == Type.double_) - return fromBsonData!double(m_data); - else if (m_type == Type.int_) - return cast(double)fromBsonData!int(m_data); - else if (m_type == Type.long_) - return cast(double)fromBsonData!long(m_data); - else - assert(false); - } - else static if( is(T == string) ){ - return toString(); - } - else static if( is(Unqual!T == Bson[string]) || is(Unqual!T == const(Bson)[string]) ){ - checkType(Type.object); - Bson[string] ret; - auto d = m_data[4 .. $]; - while( d.length > 0 ){ - auto tp = cast(Type)d[0]; - if( tp == Type.end ) break; - d = d[1 .. $]; - auto key = skipCString(d); - auto value = Bson(tp, d); - d = d[value.data.length .. $]; - - ret[key] = value; - } - return cast(T)ret; - } - else static if( is(Unqual!T == Bson[]) || is(Unqual!T == const(Bson)[]) ){ - checkType(Type.array); - Bson[] ret; - auto d = m_data[4 .. $]; - while( d.length > 0 ){ - auto tp = cast(Type)d[0]; - if( tp == Type.end ) break; - /*auto key = */skipCString(d); // should be '0', '1', ... - auto value = Bson(tp, d); - d = d[value.data.length .. $]; - - ret ~= value; - } - return cast(T)ret; - } - else static if( is(T == BsonBinData) ){ - checkType(Type.binData); - auto size = fromBsonData!int(m_data); - auto type = cast(BsonBinData.Type)m_data[4]; - return BsonBinData(type, m_data[5 .. 5+size]); - } - else static if( is(T == BsonObjectID) ){ checkType(Type.objectID); return BsonObjectID(m_data[0 .. 12]); } - else static if( is(T == bool) ){ checkType(Type.bool_); return m_data[0] != 0; } - else static if( is(T == BsonDate) ){ checkType(Type.date); return BsonDate(fromBsonData!long(m_data)); } - else static if( is(T == SysTime) ){ - checkType(Type.date); - string data = cast(string)m_data[4 .. 4+fromBsonData!int(m_data)-1]; - return SysTime.fromISOString(data); - } - else static if( is(T == BsonRegex) ){ - checkType(Type.regex); - auto d = m_data[0 .. $]; - auto expr = skipCString(d); - auto options = skipCString(d); - return BsonRegex(expr, options); - } - else static if( is(T == int) ){ checkType(Type.int_); return fromBsonData!int(m_data); } - else static if( is(T == BsonTimestamp) ){ checkType(Type.timestamp); return BsonTimestamp(fromBsonData!long(m_data)); } - else static if( is(T == long) ){ - checkType(Type.long_, Type.int_); - if (m_type == Type.int_) - return cast(long)fromBsonData!int(m_data); - else if (m_type == Type.long_) - return fromBsonData!long(m_data); - else - assert(false); - } - else static if( is(T == Json) ){ - return this.toJson(); - } - else static if( is(T == UUID) ){ - checkType(Type.binData); - auto bbd = this.get!BsonBinData(); - enforce(bbd.type == BsonBinData.Type.uuid, "BsonBinData value is type '"~bbd.type.enumToString~"', expected to be uuid"); - const ubyte[16] b = bbd.rawData; - return UUID(b); - } - else static if( is(T == SysTime) ) { - checkType(Type.date); - return BsonDate(fromBsonData!long(m_data)).toSysTime(); - } - else static assert(false, "Cannot convert "~typeof(this).stringof~" to '"~T.stringof~"'."); - } - - /** Returns the native type for this BSON if it matches the current runtime type. - - If the runtime type does not match the given native type, the 'def' parameter is returned - instead. - */ - T opt(T)(T def = T.init) - { - if (isNull()) return def; - try return cast(T)this; - catch (Exception e) return def; - } - /// ditto - const(T) opt(T)(const(T) def = const(T).init) - const { - if (isNull()) return def; - try return cast(T)this; - catch (Exception e) return def; - } - - /** Returns the length of a BSON value of type String, Array, Object or BinData. - */ - @property size_t length() const { - switch( m_type ){ - default: enforce(false, "Bson objects of type "~m_type.enumToString~" do not have a length field."); break; - case Type.string, Type.code, Type.symbol: return (cast(string)this).length; - case Type.array: return byValue.walkLength; - case Type.object: return byValue.walkLength; - case Type.binData: assert(false); //return (cast(BsonBinData)this).length; break; - } - assert(false); - } - - /** Converts a given JSON value to the corresponding BSON value. - */ - static Bson fromJson(in Json value) - @trusted { - auto app = appender!bdata_t(); - auto tp = writeBson(app, value); - return Bson(tp, app.data); - } - - /** Converts a BSON value to a JSON value. - - All BSON types that cannot be exactly represented as JSON, will - be converted to a string. - */ - Json toJson() - const { - switch( this.type ){ - default: assert(false); - case Bson.Type.double_: return Json(get!double()); - case Bson.Type.string: return Json(get!string()); - case Bson.Type.object: - Json[string] ret; - foreach (k, v; this.byKeyValue) - ret[k] = v.toJson(); - return Json(ret); - case Bson.Type.array: - auto ret = new Json[this.length]; - foreach (i, v; this.byIndexValue) - ret[i] = v.toJson(); - return Json(ret); - case Bson.Type.binData: return Json(() @trusted { return cast(string)Base64.encode(get!BsonBinData.rawData); } ()); - case Bson.Type.objectID: return Json(get!BsonObjectID().toString()); - case Bson.Type.bool_: return Json(get!bool()); - case Bson.Type.date: return Json(get!BsonDate.toString()); - case Bson.Type.null_: return Json(null); - case Bson.Type.regex: assert(false, "TODO"); - case Bson.Type.dbRef: assert(false, "TODO"); - case Bson.Type.code: return Json(get!string()); - case Bson.Type.symbol: return Json(get!string()); - case Bson.Type.codeWScope: assert(false, "TODO"); - case Bson.Type.int_: return Json(get!int()); - case Bson.Type.timestamp: return Json(get!BsonTimestamp().m_time); - case Bson.Type.long_: return Json(get!long()); - case Bson.Type.undefined: return Json(); - } - } - - /** Returns a string representation of this BSON value in JSON format. - */ - string toString() - const { - auto ret = appender!string; - toString(ret); - return ret.data; - } - - void toString(R)(ref R range) - const { - switch (type) - { - case Bson.Type.objectID: - range.put("ObjectID("); - range.put(get!BsonObjectID().toString()); - range.put(")"); - break; - case Bson.Type.object: - // keep ordering of objects - range.put("{"); - bool first = true; - foreach (k, v; this.byKeyValue) - { - if (!first) range.put(","); - first = false; - range.put(Json(k).toString()); - range.put(":"); - v.toString(range); - } - range.put("}"); - break; - case Bson.Type.array: - range.put("["); - foreach (i, v; this.byIndexValue) - { - if (i != 0) range.put(","); - v.toString(range); - } - range.put("]"); - break; - default: - range.put(toJson().toString()); - break; - } - } - - import std.typecons : Nullable; - - /** - Check whether the BSON object contains the given key. - */ - Nullable!Bson tryIndex(string key) const { - checkType(Type.object); - foreach (string idx, v; this.byKeyValue) - if(idx == key) - return Nullable!Bson(v); - return Nullable!Bson.init; - } - - /** Allows accessing fields of a BSON object using `[]`. - - Returns a null value if the specified field does not exist. - */ - inout(Bson) opIndex(string idx) inout { - foreach (string key, v; this.byKeyValue) - if( key == idx ) - return v; - return Bson(null); - } - /// ditto - void opIndexAssign(T)(in T value, string idx){ - // WARNING: it is ABSOLUTELY ESSENTIAL that ordering is not changed!!! - // MongoDB depends on ordering of the Bson maps. - - auto newcont = appender!bdata_t(); - checkType(Type.object); - auto d = m_data[4 .. $]; - while( d.length > 0 ){ - auto tp = cast(Type)d[0]; - if( tp == Type.end ) break; - d = d[1 .. $]; - auto key = skipCString(d); - auto val = Bson(tp, d); - d = d[val.data.length .. $]; - - if( key != idx ){ - // copy to new array - newcont.put(cast(ubyte)tp); - putCString(newcont, key); - newcont.put(val.data); - } - } - - static if( is(T == Bson) ) - alias bval = value; - else - auto bval = Bson(value); - - newcont.put(cast(ubyte)bval.type); - putCString(newcont, idx); - newcont.put(bval.data); - - auto newdata = appender!bdata_t(); - newdata.put(toBsonData(cast(uint)(newcont.data.length + 5))); - newdata.put(newcont.data); - newdata.put(cast(ubyte)0); - m_data = newdata.data; - } - - /// - unittest { - Bson value = Bson.emptyObject; - value["a"] = 1; - value["b"] = true; - value["c"] = "foo"; - assert(value["a"] == Bson(1)); - assert(value["b"] == Bson(true)); - assert(value["c"] == Bson("foo")); - } - - /// - unittest { - auto srcUuid = UUID("00010203-0405-0607-0809-0a0b0c0d0e0f"); - - Bson b = srcUuid; - auto u = b.get!UUID(); - - assert(b.type == Bson.Type.binData); - assert(b.get!BsonBinData().type == BsonBinData.Type.uuid); - assert(u == srcUuid); - } - - /** Allows index based access of a BSON array value. - - Returns a null value if the index is out of bounds. - */ - inout(Bson) opIndex(size_t idx) inout { - foreach (size_t i, v; this.byIndexValue) - if (i == idx) - return v; - return Bson(null); - } - - /// - unittest { - Bson[] entries; - entries ~= Bson(1); - entries ~= Bson(true); - entries ~= Bson("foo"); - - Bson value = Bson(entries); - assert(value[0] == Bson(1)); - assert(value[1] == Bson(true)); - assert(value[2] == Bson("foo")); - } - - /** Removes an entry from a BSON obect. - - If the key doesn't exit, this function will be a no-op. - */ - void remove(string key) - { - checkType(Type.object); - auto d = m_data[4 .. $]; - while (d.length > 0) { - size_t start_remainder = d.length; - auto tp = cast(Type)d[0]; - if (tp == Type.end) break; - d = d[1 .. $]; - auto ekey = skipCString(d); - auto evalue = Bson(tp, d); - d = d[evalue.data.length .. $]; - - if (ekey == key) { - m_data = m_data[0 .. $-start_remainder] ~ d; - break; - } - } - } - - unittest { - auto o = Bson.emptyObject; - o["a"] = Bson(1); - o["b"] = Bson(2); - o["c"] = Bson(3); - assert(o.length == 3); - o.remove("b"); - assert(o.length == 2); - assert(o["a"] == Bson(1)); - assert(o["c"] == Bson(3)); - o.remove("c"); - assert(o.length == 1); - assert(o["a"] == Bson(1)); - o.remove("c"); - assert(o.length == 1); - assert(o["a"] == Bson(1)); - o.remove("a"); - assert(o.length == 0); - } - - /** - Allows foreach iterating over BSON objects and arrays. - */ - int opApply(scope int delegate(Bson obj) del) - const @system { - foreach (value; byValue) - if (auto ret = del(value)) - return ret; - return 0; - } - /// ditto - int opApply(scope int delegate(size_t idx, Bson obj) del) - const @system { - foreach (index, value; byIndexValue) - if (auto ret = del(index, value)) - return ret; - return 0; - } - /// ditto - int opApply(scope int delegate(string idx, Bson obj) del) - const @system { - foreach (key, value; byKeyValue) - if (auto ret = del(key, value)) - return ret; - return 0; - } - - /// Iterates over all values of an object or array. - auto byValue() const { checkType(Type.array, Type.object); return byKeyValueImpl().map!(t => t[1]); } - /// Iterates over all index/value pairs of an array. - auto byIndexValue() const { checkType(Type.array); return byKeyValueImpl().map!(t => Tuple!(size_t, "key", Bson, "value")(t[0].to!size_t, t[1])); } - /// Iterates over all key/value pairs of an object. - auto byKeyValue() const { checkType(Type.object); return byKeyValueImpl(); } - - private auto byKeyValueImpl() - const { - checkType(Type.object, Type.array); - - alias T = Tuple!(string, "key", Bson, "value"); - - static struct Rng { - private { - immutable(ubyte)[] data; - string key; - Bson value; - } - - @property bool empty() const { return data.length == 0; } - @property T front() { return T(key, value); } - @property Rng save() const { return this; } - - void popFront() - { - auto tp = cast(Type)data[0]; - data = data[1 .. $]; - if (tp == Type.end) return; - key = skipCString(data); - value = Bson(tp, data); - data = data[value.data.length .. $]; - } - } - - auto ret = Rng(m_data[4 .. $]); - ret.popFront(); - return ret; - } - - /// - bool opEquals(ref const Bson other) const { - if( m_type != other.m_type ) return false; - if (m_type != Type.object) - return m_data == other.m_data; - - if (m_data == other.m_data) - return true; - // Similar objects can have a different key order, but they must have a same length - if (m_data.length != other.m_data.length) - return false; - - foreach (k, ref v; this.byKeyValue) - { - if (other[k] != v) - return false; - } - - return true; - } - /// ditto - bool opEquals(const Bson other) const { - if( m_type != other.m_type ) return false; - - return opEquals(other); - } - - private void checkType(in Type[] valid_types...) - const { - foreach( t; valid_types ) - if( m_type == t ) - return; - throw new Exception("BSON value is type '"~m_type.enumToString~"', expected to be one of "~valid_types.map!(t => t.enumToString).join(", ")); - } -} - - -/** - Represents a BSON binary data value (Bson.Type.binData). -*/ -struct BsonBinData { -@safe: - - enum Type : ubyte { - generic = 0x00, - function_ = 0x01, - binaryOld = 0x02, - uuid = 0x03, - md5 = 0x05, - userDefined = 0x80, - - deprecated("Use `generic` instead") Generic = generic, /// Compatibility alias - deprecated("Use `function_` instead") Function = function_, /// Compatibility alias - deprecated("Use `binaryOld` instead") BinaryOld = binaryOld, /// Compatibility alias - deprecated("Use `uuid` instead") UUID = uuid, /// Compatibility alias - deprecated("Use `md5` instead") MD5 = md5, /// Compatibility alias - deprecated("Use `userDefined` instead") UserDefined = userDefined, /// Compatibility alias - } - - private { - Type m_type; - bdata_t m_data; - } - - this(Type type, immutable(ubyte)[] data) - { - m_type = type; - m_data = data; - } - - @property Type type() const { return m_type; } - @property bdata_t rawData() const { return m_data; } -} - - -/** - Represents a BSON object id (Bson.Type.binData). -*/ -struct BsonObjectID { -@safe: - - private { - ubyte[12] m_bytes; - static immutable uint MACHINE_ID; - static immutable int ms_pid; - static uint ms_inc = 0; - } - - shared static this() - { - import std.process; - import std.random; - MACHINE_ID = uniform(0, 0xffffff); - ms_pid = thisProcessID; - } - - static this() - { - import std.random; - ms_inc = uniform(0, 0xffffff); - } - - /** Constructs a new object ID from the given raw byte array. - */ - this(in ubyte[] bytes) - { - assert(bytes.length == 12); - m_bytes[] = bytes[]; - } - - /** Creates an on object ID from a string in standard hexa-decimal form. - */ - static BsonObjectID fromString(string str) - { - if (str.length != 24) throwStaticConvEx!"BSON Object ID string must be 24 characters."; - - BsonObjectID ret = void; - uint b = 0; - foreach( i, ch; str ){ - ubyte n; - if( ch >= '0' && ch <= '9' ) n = cast(ubyte)(ch - '0'); - else if( ch >= 'a' && ch <= 'f' ) n = cast(ubyte)(ch - 'a' + 10); - else if( ch >= 'A' && ch <= 'F' ) n = cast(ubyte)(ch - 'F' + 10); - else throwStaticConvEx!"Not a valid hex string."; - b <<= 4; - b += n; - if( i % 8 == 7 ){ - auto j = i / 8; - ret.m_bytes[j*4 .. (j+1)*4] = toBigEndianData(b)[]; - b = 0; - } - } - return ret; - } - /// ditto - alias fromHexString = fromString; - - private static void throwStaticConvEx(string msg)() - @trusted { - import std.conv : ConvException; - static __gshared ex = new ConvException(msg); - throw ex; - } - - /** Generates a unique object ID. - * - * By default it will use `Clock.currTime(UTC())` as the timestamp - * which guarantees that `BsonObjectID`s are chronologically - * sorted. - */ - static BsonObjectID generate(in SysTime time = Clock.currTime(UTC())) - { - import std.datetime; - - BsonObjectID ret = void; - ret.m_bytes[0 .. 4] = toBigEndianData(cast(uint)time.toUnixTime())[]; - ret.m_bytes[4 .. 7] = toBsonData(MACHINE_ID)[0 .. 3]; - ret.m_bytes[7 .. 9] = toBsonData(cast(ushort)ms_pid)[]; - ret.m_bytes[9 .. 12] = toBigEndianData(ms_inc++)[1 .. 4]; - return ret; - } - - /** Creates a pseudo object ID that matches the given date. - - This kind of ID can be useful to query a database for items in a certain - date interval using their ID. This works using the property of standard BSON - object IDs that they store their creation date as part of the ID. Note that - this date part is only 32-bit wide and is limited to the same timespan as a - 32-bit Unix timestamp. - */ - static BsonObjectID createDateID(in SysTime time) - { - BsonObjectID ret; - ret.m_bytes[0 .. 4] = toBigEndianData(cast(uint)time.toUnixTime())[]; - return ret; - } - - /** Returns true for any non-zero ID. - */ - @property bool valid() const { - foreach( b; m_bytes ) - if( b != 0 ) - return true; - return false; - } - - /** Extracts the time/date portion of the object ID. - - For IDs created using the standard generation algorithm or using createDateID - this will return the associated time stamp. - */ - @property SysTime timeStamp() - const { - ubyte[4] tm = m_bytes[0 .. 4]; - return SysTime(unixTimeToStdTime(bigEndianToNative!uint(tm))); - } - - /** Allows for relational comparison of different IDs. - */ - int opCmp(ref const BsonObjectID other) - const { - import core.stdc.string; - return () @trusted { return memcmp(m_bytes.ptr, other.m_bytes.ptr, m_bytes.length); } (); - } - - /** Converts the ID to its standard hexa-decimal string representation. - */ - string toString() const pure { - enum hexdigits = "0123456789abcdef"; - auto ret = new char[24]; - foreach( i, b; m_bytes ){ - ret[i*2+0] = hexdigits[(b >> 4) & 0x0F]; - ret[i*2+1] = hexdigits[b & 0x0F]; - } - return ret; - } - - inout(ubyte)[] opCast() inout return { return m_bytes; } -} - -unittest { - auto t0 = SysTime(Clock.currTime(UTC()).toUnixTime.unixTimeToStdTime); - auto id = BsonObjectID.generate(); - auto t1 = SysTime(Clock.currTime(UTC()).toUnixTime.unixTimeToStdTime); - assert(t0 <= id.timeStamp); - assert(id.timeStamp <= t1); - - id = BsonObjectID.generate(t0); - assert(id.timeStamp == t0); - - id = BsonObjectID.generate(t1); - assert(id.timeStamp == t1); - - immutable dt = DateTime(2014, 07, 31, 19, 14, 55); - id = BsonObjectID.generate(SysTime(dt, UTC())); - assert(id.timeStamp == SysTime(dt, UTC())); -} - -unittest { - auto b = Bson(true); - assert(b.opt!bool(false) == true); - assert(b.opt!int(12) == 12); - assert(b.opt!(Bson[])(null).length == 0); - - const c = b; - assert(c.opt!bool(false) == true); - assert(c.opt!int(12) == 12); - assert(c.opt!(Bson[])(null).length == 0); -} - - -/** - Represents a BSON date value (`Bson.Type.date`). - - BSON date values are stored in UNIX time format, counting the number of - milliseconds from 1970/01/01. -*/ -struct BsonDate { -@safe: - - private long m_time; // milliseconds since UTC unix epoch - - /** Constructs a BsonDate from the given date value. - - The time-zone independent Date and DateTime types are assumed to be in - the local time zone and converted to UTC if tz is left to null. - */ - this(in Date date, immutable TimeZone tz = null) { this(SysTime(date, tz)); } - /// ditto - this(in DateTime date, immutable TimeZone tz = null) { this(SysTime(date, tz)); } - /// ditto - this(in SysTime date) { this(fromStdTime(date.stdTime()).m_time); } - - /** Constructs a BsonDate from the given UNIX time. - - unix_time needs to be given in milliseconds from 1970/01/01. This is - the native storage format for BsonDate. - */ - this(long unix_time) - { - m_time = unix_time; - } - - /** Constructs a BsonDate from the given date/time string in ISO extended format. - */ - static BsonDate fromString(string iso_ext_string) { return BsonDate(SysTime.fromISOExtString(iso_ext_string)); } - - /** Constructs a BsonDate from the given date/time in standard time as defined in `std.datetime`. - */ - static BsonDate fromStdTime(long std_time) - { - enum zero = unixTimeToStdTime(0); - return BsonDate((std_time - zero) / 10_000L); - } - - /** The raw unix time value. - - This is the native storage/transfer format of a BsonDate. - */ - @property long value() const { return m_time; } - /// ditto - @property void value(long v) { m_time = v; } - - /** Returns the date formatted as ISO extended format. - */ - string toString() const { return toSysTime().toISOExtString(); } - - /* Converts to a SysTime using UTC timezone. - */ - SysTime toSysTime() const { - return toSysTime(UTC()); - } - - /* Converts to a SysTime with a given timezone. - */ - SysTime toSysTime(immutable TimeZone tz) const { - auto zero = unixTimeToStdTime(0); - return SysTime(zero + m_time * 10_000L, tz); - } - - /** Allows relational and equality comparisons. - */ - bool opEquals(ref const BsonDate other) const { return m_time == other.m_time; } - /// ditto - int opCmp(ref const BsonDate other) const { - if( m_time == other.m_time ) return 0; - if( m_time < other.m_time ) return -1; - else return 1; - } -} - - -/** - Represents a BSON timestamp value `(Bson.Type.timestamp)`. -*/ -struct BsonTimestamp { -@safe: - - private long m_time; - - this( long time ){ - m_time = time; - } -} - - -/** - Represents a BSON regular expression value `(Bson.Type.regex)`. -*/ -struct BsonRegex { -@safe: - - private { - string m_expr; - string m_options; - } - - this(string expr, string options) - { - m_expr = expr; - m_options = options; - } - - @property string expression() const { return m_expr; } - @property string options() const { return m_options; } -} - - -/** - Serializes the given value to BSON. - - The following types of values are supported: - - $(DL - $(DT `Bson`) $(DD Used as-is) - $(DT `Json`) $(DD Converted to BSON) - $(DT `BsonBinData`) $(DD Converted to `Bson.Type.binData`) - $(DT `BsonObjectID`) $(DD Converted to `Bson.Type.objectID`) - $(DT `BsonDate`) $(DD Converted to `Bson.Type.date`) - $(DT `BsonTimestamp`) $(DD Converted to `Bson.Type.timestamp`) - $(DT `BsonRegex`) $(DD Converted to `Bson.Type.regex`) - $(DT `null`) $(DD Converted to `Bson.Type.null_`) - $(DT `bool`) $(DD Converted to `Bson.Type.bool_`) - $(DT `float`, `double`) $(DD Converted to `Bson.Type.double_`) - $(DT `short`, `ushort`, `int`, `uint`, `long`, `ulong`) $(DD Converted to `Bson.Type.long_`) - $(DT `string`) $(DD Converted to `Bson.Type.string`) - $(DT `ubyte[]`) $(DD Converted to `Bson.Type.binData`) - $(DT `T[]`) $(DD Converted to `Bson.Type.array`) - $(DT `T[string]`) $(DD Converted to `Bson.Type.object`) - $(DT `struct`) $(DD Converted to `Bson.Type.object`) - $(DT `class`) $(DD Converted to `Bson.Type.object` or `Bson.Type.null_`) - ) - - All entries of an array or an associative array, as well as all R/W properties and - all fields of a struct/class are recursively serialized using the same rules. - - Fields ending with an underscore will have the last underscore stripped in the - serialized output. This makes it possible to use fields with D keywords as their name - by simply appending an underscore. - - The following methods can be used to customize the serialization of structs/classes: - - --- - Bson toBson() const; - static T fromBson(Bson src); - - Json toJson() const; - static T fromJson(Json src); - - string toString() const; - static T fromString(string src); - --- - - The methods will have to be defined in pairs. The first pair that is implemented by - the type will be used for serialization (i.e. `toBson` overrides `toJson`). - - See_Also: `deserializeBson` -*/ -Bson serializeToBson(T)(auto ref T value, ubyte[] buffer = null) -{ - return serialize!BsonSerializer(value, buffer); -} - - -template deserializeBson(T) -{ - /** - Deserializes a BSON value into the destination variable. - - The same types as for `serializeToBson()` are supported and handled inversely. - - See_Also: `serializeToBson` - */ - void deserializeBson(ref T dst, Bson src) - { - dst = deserializeBson!T(src); - } - /// ditto - T deserializeBson(Bson src) - { - return deserialize!(BsonSerializer, T)(src); - } -} - -unittest { - import std.stdio; - enum Foo : string { k = "test" } - enum Boo : int { l = 5 } - static struct S { float a; double b; bool c; int d; string e; byte f; ubyte g; long h; ulong i; float[] j; Foo k; Boo l;} - immutable S t = {1.5, -3.0, true, int.min, "Test", -128, 255, long.min, ulong.max, [1.1, 1.2, 1.3], Foo.k, Boo.l,}; - S u; - deserializeBson(u, serializeToBson(t)); - assert(t.a == u.a); - assert(t.b == u.b); - assert(t.c == u.c); - assert(t.d == u.d); - assert(t.e == u.e); - assert(t.f == u.f); - assert(t.g == u.g); - assert(t.h == u.h); - assert(t.i == u.i); - assert(t.j == u.j); - assert(t.k == u.k); - assert(t.l == u.l); -} - -unittest -{ - assert(uint.max == serializeToBson(uint.max).deserializeBson!uint); - assert(ulong.max == serializeToBson(ulong.max).deserializeBson!ulong); -} - -unittest { - assert(deserializeBson!SysTime(serializeToBson(SysTime(0))) == SysTime(0)); - assert(deserializeBson!SysTime(serializeToBson(SysTime(0, UTC()))) == SysTime(0, UTC())); - assert(deserializeBson!Date(serializeToBson(Date.init)) == Date.init); - assert(deserializeBson!Date(serializeToBson(Date(2001, 1, 1))) == Date(2001, 1, 1)); -} - -@safe unittest { - static struct A { int value; static A fromJson(Json val) @safe { return A(val.get!int); } Json toJson() const @safe { return Json(value); } Bson toBson() { return Bson(); } } - static assert(!isStringSerializable!A && isJsonSerializable!A && !isBsonSerializable!A); - static assert(!isStringSerializable!(const(A)) && isJsonSerializable!(const(A)) && !isBsonSerializable!(const(A))); -// assert(serializeToBson(const A(123)) == Bson(123)); -// assert(serializeToBson(A(123)) == Bson(123)); - - static struct B { int value; static B fromBson(Bson val) @safe { return B(val.get!int); } Bson toBson() const @safe { return Bson(value); } Json toJson() { return Json(); } } - static assert(!isStringSerializable!B && !isJsonSerializable!B && isBsonSerializable!B); - static assert(!isStringSerializable!(const(B)) && !isJsonSerializable!(const(B)) && isBsonSerializable!(const(B))); - assert(serializeToBson(const B(123)) == Bson(123)); - assert(serializeToBson(B(123)) == Bson(123)); - - static struct C { int value; static C fromString(string val) @safe { return C(val.to!int); } string toString() const @safe { return value.to!string; } Json toJson() { return Json(); } } - static assert(isStringSerializable!C && !isJsonSerializable!C && !isBsonSerializable!C); - static assert(isStringSerializable!(const(C)) && !isJsonSerializable!(const(C)) && !isBsonSerializable!(const(C))); - assert(serializeToBson(const C(123)) == Bson("123")); - assert(serializeToBson(C(123)) == Bson("123")); - - static struct D { int value; string toString() const { return ""; } } - static assert(!isStringSerializable!D && !isJsonSerializable!D && !isBsonSerializable!D); - static assert(!isStringSerializable!(const(D)) && !isJsonSerializable!(const(D)) && !isBsonSerializable!(const(D))); - assert(serializeToBson(const D(123)) == serializeToBson(["value": 123])); - assert(serializeToBson(D(123)) == serializeToBson(["value": 123])); - - // test if const(class) is serializable - static class E { int value; this(int v) @safe { value = v; } static E fromBson(Bson val) @safe { return new E(val.get!int); } Bson toBson() const @safe { return Bson(value); } Json toJson() { return Json(); } } - static assert(!isStringSerializable!E && !isJsonSerializable!E && isBsonSerializable!E); - static assert(!isStringSerializable!(const(E)) && !isJsonSerializable!(const(E)) && isBsonSerializable!(const(E))); - assert(serializeToBson(new const E(123)) == Bson(123)); - assert(serializeToBson(new E(123)) == Bson(123)); -} - -@safe unittest { - static struct E { ubyte[4] bytes; ubyte[] more; } - auto e = E([1, 2, 3, 4], [5, 6]); - auto eb = serializeToBson(e); - assert(eb["bytes"].type == Bson.Type.binData); - assert(eb["more"].type == Bson.Type.binData); - assert(e == deserializeBson!E(eb)); -} - -@safe unittest { - static class C { - @safe: - int a; - private int _b; - @property int b() const { return _b; } - @property void b(int v) { _b = v; } - - @property int test() const @safe { return 10; } - - void test2() {} - } - C c = new C; - c.a = 1; - c.b = 2; - - C d; - deserializeBson(d, serializeToBson(c)); - assert(c.a == d.a); - assert(c.b == d.b); - - const(C) e = c; // serialize const class instances (issue #653) - deserializeBson(d, serializeToBson(e)); - assert(e.a == d.a); - assert(e.b == d.b); -} - -unittest { - static struct C { @safe: int value; static C fromString(string val) { return C(val.to!int); } string toString() const { return value.to!string; } } - enum Color { Red, Green, Blue } - { - static class T { - @safe: - string[Color] enumIndexedMap; - string[C] stringableIndexedMap; - this() { - enumIndexedMap = [ Color.Red : "magenta", Color.Blue : "deep blue" ]; - stringableIndexedMap = [ C(42) : "forty-two" ]; - } - } - - T original = new T; - original.enumIndexedMap[Color.Green] = "olive"; - T other; - deserializeBson(other, serializeToBson(original)); - assert(serializeToBson(other) == serializeToBson(original)); - } - { - static struct S { - string[Color] enumIndexedMap; - string[C] stringableIndexedMap; - } - - S original; - original.enumIndexedMap = [ Color.Red : "magenta", Color.Blue : "deep blue" ]; - original.enumIndexedMap[Color.Green] = "olive"; - original.stringableIndexedMap = [ C(42) : "forty-two" ]; - S other; - deserializeBson(other, serializeToBson(original)); - assert(serializeToBson(other) == serializeToBson(original)); - } -} - -unittest { - ubyte[] data = [1, 2, 3]; - auto bson = serializeToBson(data); - assert(bson.type == Bson.Type.binData); - assert(deserializeBson!(ubyte[])(bson) == data); -} - -unittest { // issue #709 - ulong[] data = [2354877787627192443, 1, 2354877787627192442]; - auto bson = Bson.fromJson(serializeToBson(data).toJson); - assert(deserializeBson!(ulong[])(bson) == data); -} - -unittest { // issue #709 - uint[] data = [1, 2, 3, 4]; - auto bson = Bson.fromJson(serializeToBson(data).toJson); -// assert(deserializeBson!(uint[])(bson) == data); - assert(deserializeBson!(ulong[])(bson).equal(data)); -} - -unittest { - import std.typecons; - Nullable!bool x; - auto bson = serializeToBson(x); - assert(bson.type == Bson.Type.null_); - deserializeBson(x, bson); - assert(x.isNull); - x = true; - bson = serializeToBson(x); - assert(bson.type == Bson.Type.bool_ && bson.get!bool == true); - deserializeBson(x, bson); - assert(x == true); -} - -unittest { // issue #793 - char[] test = "test".dup; - auto bson = serializeToBson(test); - //assert(bson.type == Bson.Type.string); - //assert(bson.get!string == "test"); - assert(bson.type == Bson.Type.array); - assert(bson[0].type == Bson.Type.string && bson[0].get!string == "t"); -} - -@safe unittest { // issue #2212 - auto bsonRegex = Bson(BsonRegex(".*", "i")); - auto parsedRegex = bsonRegex.get!BsonRegex; - assert(bsonRegex.type == Bson.Type.regex); - assert(parsedRegex.expression == ".*"); - assert(parsedRegex.options == "i"); -} - -unittest -{ - UUID uuid = UUID("35399104-fbc9-4c08-bbaf-65a5efe6f5f2"); - - auto bson = Bson(uuid); - assert(bson.get!UUID == uuid); - assert(bson.deserializeBson!UUID == uuid); - - bson = Bson([Bson(uuid)]); - assert(bson.deserializeBson!(UUID[]) == [uuid]); - - bson = [uuid].serializeToBson(); - assert(bson.deserializeBson!(UUID[]) == [uuid]); -} - -/** - Serializes to an in-memory BSON representation. - - See_Also: `vibe.data.serialization.serialize`, `vibe.data.serialization.deserialize`, `serializeToBson`, `deserializeBson` -*/ -struct BsonSerializer { - import vibe.container.internal.appender : AllocAppender; - - private { - AllocAppender!(ubyte[]) m_dst; - size_t[] m_compositeStack; - Bson.Type m_type = Bson.Type.null_; - Bson m_inputData; - string m_entryName; - size_t m_entryIndex = size_t.max; - } - - this(Bson input) - @safe { - m_inputData = input; - } - - this(ubyte[] buffer) - @safe { - import vibe.container.internal.utilallocator; - m_dst = () @trusted { return AllocAppender!(ubyte[])(vibeThreadAllocator(), buffer); } (); - } - - @disable this(this); - - template isSupportedValueType(T) { enum isSupportedValueType = is(typeof(getBsonTypeID(T.init))); } - - // - // serialization - // - Bson getSerializedResult() - @safe { - auto ret = Bson(m_type, () @trusted { return cast(immutable)m_dst.data; } ()); - () @trusted { m_dst.reset(); } (); - m_type = Bson.Type.null_; - return ret; - } - - void beginWriteDictionary(Traits)() - { - writeCompositeEntryHeader(Bson.Type.object); - m_compositeStack ~= m_dst.data.length; - m_dst.put(toBsonData(cast(int)0)); - } - void endWriteDictionary(Traits)() - { - m_dst.put(Bson.Type.end); - auto sh = m_compositeStack[$-1]; - m_compositeStack.length--; - m_dst.data[sh .. sh + 4] = toBsonData(cast(uint)(m_dst.data.length - sh))[]; - } - void beginWriteDictionaryEntry(Traits)(string name) { m_entryName = name; } - void endWriteDictionaryEntry(Traits)(string name) {} - - void beginWriteArray(Traits)(size_t) - { - writeCompositeEntryHeader(Bson.Type.array); - m_compositeStack ~= m_dst.data.length; - m_dst.put(toBsonData(cast(int)0)); - } - void endWriteArray(Traits)() { endWriteDictionary!Traits(); } - void beginWriteArrayEntry(Traits)(size_t idx) { m_entryIndex = idx; } - void endWriteArrayEntry(Traits)(size_t idx) {} - - void writeValue(Traits, T)(auto ref T value) { writeValueH!(T, true)(value); } - - private void writeValueH(T, bool write_header)(auto ref T value) - { - alias UT = Unqual!T; - static if (write_header) writeCompositeEntryHeader(getBsonTypeID(value)); - - static if (is(UT == Bson)) { m_dst.put(value.data); } - else static if (is(UT == Json)) { m_dst.put(Bson(value).data); } // FIXME: use .writeBsonValue - else static if (is(UT == typeof(null))) {} - else static if (is(UT == string)) { m_dst.put(toBsonData(cast(uint)value.length+1)); m_dst.putCString(value); } - else static if (is(UT == BsonBinData)) { m_dst.put(toBsonData(cast(int)value.rawData.length)); m_dst.put(value.type); m_dst.put(value.rawData); } - else static if (is(UT == BsonObjectID)) { m_dst.put(value.m_bytes[]); } - else static if (is(UT == BsonDate)) { m_dst.put(toBsonData(value.m_time)); } - else static if (is(UT == SysTime)) { m_dst.put(toBsonData(BsonDate(value).m_time)); } - else static if (is(UT == BsonRegex)) { m_dst.putCString(value.expression); m_dst.putCString(value.options); } - else static if (is(UT == BsonTimestamp)) { m_dst.put(toBsonData(value.m_time)); } - else static if (is(UT == bool)) { m_dst.put(cast(ubyte)(value ? 0x01 : 0x00)); } - else static if (is(UT : int) && isIntegral!UT) { m_dst.put(toBsonData(cast(int)value)); } - else static if (is(UT : long) && isIntegral!UT) { m_dst.put(toBsonData(value)); } - else static if (is(UT : double) && isFloatingPoint!UT) { m_dst.put(toBsonData(cast(double)value)); } - else static if (is(UT == UUID)) { m_dst.put(Bson(value).data); } - else static if (isBsonSerializable!UT) { - static if (!__traits(compiles, () @safe { return value.toBson(); } ())) - pragma(msg, "Non-@safe toBson/fromBson methods are deprecated - annotate "~T.stringof~".toBson() with @safe."); - m_dst.put(() @trusted { return value.toBson(); } ().data); - } else static if (isJsonSerializable!UT) { - static if (!__traits(compiles, () @safe { return value.toJson(); } ())) - pragma(msg, "Non-@safe toJson/fromJson methods are deprecated - annotate "~UT.stringof~".toJson() with @safe."); - m_dst.put(Bson(() @trusted { return value.toJson(); } ()).data); - } else static if (is(UT : const(ubyte)[])) { writeValueH!(BsonBinData, false)(BsonBinData(BsonBinData.Type.generic, value.idup)); } - else static assert(false, "Unsupported type: " ~ UT.stringof); - } - - private void writeCompositeEntryHeader(Bson.Type tp) - @safe { - if (!m_compositeStack.length) { - assert(m_type == Bson.Type.null_, "Overwriting root item."); - m_type = tp; - } - - if (m_entryName !is null) { - m_dst.put(tp); - m_dst.putCString(m_entryName); - m_entryName = null; - } else if (m_entryIndex != size_t.max) { - import std.format; - m_dst.put(tp); - static struct Wrapper { - @trusted: - AllocAppender!(ubyte[])* app; - void put(char ch) { (*app).put(ch); } - void put(in char[] str) { (*app).put(cast(const(ubyte)[])str); } - } - auto wr = Wrapper(&m_dst); - wr.formattedWrite("%d\0", m_entryIndex); - m_entryIndex = size_t.max; - } - } - - // - // deserialization - // - void readDictionary(Traits)(scope void delegate(string) @safe entry_callback) - { - enforce(m_inputData.type == Bson.Type.object, "Expected object instead of "~m_inputData.type.enumToString); - auto old = m_inputData; - foreach (string name, value; old.byKeyValue) { - m_inputData = value; - entry_callback(name); - } - m_inputData = old; - } - - void beginReadDictionaryEntry(Traits)(string name) {} - void endReadDictionaryEntry(Traits)(string name) {} - - void readArray(Traits)(scope void delegate(size_t) @safe size_callback, scope void delegate() @safe entry_callback) - { - enforce(m_inputData.type == Bson.Type.array, "Expected array instead of "~m_inputData.type.enumToString); - auto old = m_inputData; - foreach (value; old.byValue) { - m_inputData = value; - entry_callback(); - } - m_inputData = old; - } - - void beginReadArrayEntry(Traits)(size_t index) {} - void endReadArrayEntry(Traits)(size_t index) {} - - T readValue(Traits, T)() - { - static if (is(T == Bson)) return m_inputData; - else static if (is(T == Json)) return m_inputData.toJson(); - else static if (is(T == bool)) return m_inputData.get!bool(); - else static if (is(T == uint)) return cast(T)m_inputData.get!int(); - else static if (is(T : int)) { - if(m_inputData.type == Bson.Type.long_) { - enforce((m_inputData.get!long() >= int.min) && (m_inputData.get!long() <= int.max), "Long out of range while attempting to deserialize to int: " ~ m_inputData.get!long.to!string); - return cast(T)m_inputData.get!long(); - } - else return m_inputData.get!int().to!T; - } - else static if (is(T : long)) { - if(m_inputData.type == Bson.Type.int_) return cast(T)m_inputData.get!int(); - else return cast(T)m_inputData.get!long(); - } - else static if (is(T : double)) return cast(T)m_inputData.get!double(); - else static if (is(T == SysTime)) { - // support legacy behavior to serialize as string - if (m_inputData.type == Bson.Type.string) return SysTime.fromISOExtString(m_inputData.get!string); - else return m_inputData.get!BsonDate().toSysTime(); - } - else static if (isBsonSerializable!T) { - static if (!__traits(compiles, () @safe { return T.fromBson(Bson.init); } ())) - pragma(msg, "Non-@safe toBson/fromBson methods are deprecated - annotate "~T.stringof~".fromBson() with @safe."); - auto bval = readValue!(Traits, Bson); - return () @trusted { return T.fromBson(bval); } (); - } else static if (isJsonSerializable!T) { - static if (!__traits(compiles, () @safe { return T.fromJson(Json.init); } ())) - pragma(msg, "Non-@safe toJson/fromJson methods are deprecated - annotate "~T.stringof~".fromJson() with @safe."); - auto jval = readValue!(Traits, Bson).toJson(); - return () @trusted { return T.fromJson(jval); } (); - } else static if (is(T : const(ubyte)[])) { - auto ret = m_inputData.get!BsonBinData.rawData; - static if (isStaticArray!T) return cast(T)ret[0 .. T.length]; - else static if (is(T : immutable(ubyte)[])) return ret; - else return cast(T)ret.dup; - } else return m_inputData.get!T(); - } - - bool tryReadNull(Traits)() - { - if (m_inputData.type == Bson.Type.null_) return true; - return false; - } - - private static Bson.Type getBsonTypeID(T, bool accept_ao = false)(auto ref T value) - @safe { - alias UT = Unqual!T; - Bson.Type tp; - static if (is(T == Bson)) tp = value.type; - else static if (is(UT == Json)) tp = jsonTypeToBsonType(value.type); - else static if (is(UT == typeof(null))) tp = Bson.Type.null_; - else static if (is(UT == string)) tp = Bson.Type.string; - else static if (is(UT == BsonBinData)) tp = Bson.Type.binData; - else static if (is(UT == BsonObjectID)) tp = Bson.Type.objectID; - else static if (is(UT == BsonDate)) tp = Bson.Type.date; - else static if (is(UT == SysTime)) tp = Bson.Type.date; - else static if (is(UT == BsonRegex)) tp = Bson.Type.regex; - else static if (is(UT == BsonTimestamp)) tp = Bson.Type.timestamp; - else static if (is(UT == bool)) tp = Bson.Type.bool_; - else static if (isIntegral!UT && is(UT : int)) tp = Bson.Type.int_; - else static if (isIntegral!UT && is(UT : long)) tp = Bson.Type.long_; - else static if (isFloatingPoint!UT && is(UT : double)) tp = Bson.Type.double_; - else static if (isBsonSerializable!UT) tp = value.toBson().type; // FIXME: this is highly inefficient - else static if (isJsonSerializable!UT) tp = jsonTypeToBsonType(value.toJson().type); // FIXME: this is highly inefficient - else static if (is(UT == UUID)) tp = Bson.Type.binData; - else static if (is(UT : const(ubyte)[])) tp = Bson.Type.binData; - else static if (accept_ao && isArray!UT) tp = Bson.Type.array; - else static if (accept_ao && isAssociativeArray!UT) tp = Bson.Type.object; - else static if (accept_ao && (is(UT == class) || is(UT == struct))) tp = Bson.Type.object; - else static assert(false, "Unsupported type: " ~ UT.stringof); - return tp; - } -} - -private Bson.Type jsonTypeToBsonType(Json.Type tp) -@safe { - static immutable Bson.Type[Json.Type.max+1] JsonIDToBsonID = [ - Bson.Type.undefined, - Bson.Type.null_, - Bson.Type.bool_, - Bson.Type.long_, - Bson.Type.long_, - Bson.Type.double_, - Bson.Type.string, - Bson.Type.array, - Bson.Type.object - ]; - return JsonIDToBsonID[tp]; -} - -private Bson.Type writeBson(R)(ref R dst, in Json value) - if( isOutputRange!(R, ubyte) ) -{ - final switch(value.type){ - case Json.Type.undefined: - return Bson.Type.undefined; - case Json.Type.null_: - return Bson.Type.null_; - case Json.Type.bool_: - dst.put(cast(ubyte)(cast(bool)value ? 0x01 : 0x00)); - return Bson.Type.bool_; - case Json.Type.int_: - dst.put(toBsonData(cast(long)value)); - return Bson.Type.long_; - case Json.Type.bigInt: - dst.put(toBsonData(cast(long)value)); - return Bson.Type.long_; - case Json.Type.float_: - dst.put(toBsonData(cast(double)value)); - return Bson.Type.double_; - case Json.Type.string: - dst.put(toBsonData(cast(uint)value.length+1)); - dst.put(cast(bdata_t)cast(string)value); - dst.put(cast(ubyte)0); - return Bson.Type.string; - case Json.Type.array: - auto app = appender!bdata_t(); - foreach( size_t i, ref const Json v; value ){ - app.put(cast(ubyte)(jsonTypeToBsonType(v.type))); - putCString(app, to!string(i)); - writeBson(app, v); - } - - dst.put(toBsonData(cast(int)(app.data.length + int.sizeof + 1))); - dst.put(app.data); - dst.put(cast(ubyte)0); - return Bson.Type.array; - case Json.Type.object: - auto app = appender!bdata_t(); - foreach( string k, ref const Json v; value ){ - app.put(cast(ubyte)(jsonTypeToBsonType(v.type))); - putCString(app, k); - writeBson(app, v); - } - - dst.put(toBsonData(cast(int)(app.data.length + int.sizeof + 1))); - dst.put(app.data); - dst.put(cast(ubyte)0); - return Bson.Type.object; - } -} - -unittest -{ - Json jsvalue = parseJsonString("{\"key\" : \"Value\"}"); - assert(serializeToBson(jsvalue).toJson() == jsvalue); - - jsvalue = parseJsonString("{\"key\" : [{\"key\" : \"Value\"}, {\"key2\" : \"Value2\"}] }"); - assert(serializeToBson(jsvalue).toJson() == jsvalue); - - jsvalue = parseJsonString("[ 1 , 2 , 3]"); - assert(serializeToBson(jsvalue).toJson() == jsvalue); -} - -unittest -{ - static struct Pipeline(ARGS...) { @asArray ARGS pipeline; } - auto getPipeline(ARGS...)(ARGS args) { return Pipeline!ARGS(args); } - - string[string] a = ["foo":"bar"]; - int b = 42; - - auto fields = getPipeline(a, b).serializeToBson()["pipeline"].get!(Bson[]); - assert(fields[0]["foo"].get!string == "bar"); - assert(fields[1].get!int == 42); -} - -@safe unittest { - struct S { - immutable(ubyte)[][] arrays; - } - - S s = S([[1, 2, 3], [4, 5, 6]]); - S t; - deserializeBson(t, serializeToBson(s)); - assert(t == s); -} - -private string skipCString(ref bdata_t data) -@safe { - auto idx = data.countUntil(0); - enforce(idx >= 0, "Unterminated BSON C-string."); - auto ret = data[0 .. idx]; - data = data[idx+1 .. $]; - return cast(string)ret; -} - -private void putCString(R)(ref R dst, string str) -{ - dst.put(cast(bdata_t)str); - dst.put(cast(ubyte)0); -} - -ubyte[] toBsonData(T)(T v) -{ - /*static T tmp; - tmp = nativeToLittleEndian(v); - return cast(ubyte[])((&tmp)[0 .. 1]);*/ - if (__ctfe) return nativeToLittleEndian(v).dup; - else { - static ubyte[T.sizeof] ret; - ret = nativeToLittleEndian(v); - return ret; - } -} - -T fromBsonData(T)(in ubyte[] v) -{ - assert(v.length >= T.sizeof); - //return (cast(T[])v[0 .. T.sizeof])[0]; - ubyte[T.sizeof] vu = v[0 .. T.sizeof]; - return littleEndianToNative!T(vu); -} - -ubyte[] toBigEndianData(T)(T v) -{ - if (__ctfe) return nativeToBigEndian(v).dup; - else { - static ubyte[T.sizeof] ret; - ret = nativeToBigEndian(v); - return ret; - } -} - -private string underscoreStrip(string field_name) -pure @safe { - if( field_name.length < 1 || field_name[$-1] != '_' ) return field_name; - else return field_name[0 .. $-1]; -} - -/// private -package template isBsonSerializable(T) { enum isBsonSerializable = is(typeof(T.init.toBson()) : Bson) && is(typeof(T.fromBson(Bson())) : T); } diff --git a/data/vibe/data/internal/conv.d b/data/vibe/data/internal/conv.d deleted file mode 100644 index c330bb7b41..0000000000 --- a/data/vibe/data/internal/conv.d +++ /dev/null @@ -1,32 +0,0 @@ -module vibe.data.internal.conv; - -import std.traits : OriginalType; - -string enumToString(E)(E value) -{ - import std.conv : to; - - switch (value) { - default: return "cast("~E.stringof~")"~(cast(OriginalType!E)value).to!string; - static foreach (m; __traits(allMembers, E)) { - static if (!isDeprecated!(E, m)) { - case __traits(getMember, E, m): return m; - } - } - } -} - -// wraps formattedWrite in a way that allows using a `scope` range without -// deprecation warnings -void formattedWriteFixed(size_t MAX_BYTES, R, ARGS...)(ref R sink, string format, ARGS args) -@safe { - import std.format : formattedWrite; - import vibe.container.internal.appender : FixedAppender; - - FixedAppender!(char[], MAX_BYTES) app; - app.formattedWrite(format, args); - sink.put(app.data); -} - -private enum isDeprecated(alias parent, string symbol) - = __traits(isDeprecated, __traits(getMember, parent, symbol)); diff --git a/data/vibe/data/json.d b/data/vibe/data/json.d deleted file mode 100644 index 3209934fe7..0000000000 --- a/data/vibe/data/json.d +++ /dev/null @@ -1,2958 +0,0 @@ -/** - JSON serialization and value handling. - - This module provides the Json struct for reading, writing and manipulating - JSON values. De(serialization) of arbitrary D types is also supported and - is recommended for handling JSON in performance sensitive applications. - - Copyright: © 2012-2015 Sönke Ludwig - License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. - Authors: Sönke Ludwig -*/ -module vibe.data.json; - -/// -@safe unittest { - void manipulateJson(Json j) - { - import std.stdio; - - // retrieving the values is done using get() - assert(j["name"].get!string == "Example"); - assert(j["id"].get!int == 1); - - // semantic conversions can be done using to() - assert(j["id"].to!string == "1"); - - // prints: - // name: "Example" - // id: 1 - foreach (key, value; j.byKeyValue) - writefln("%s: %s", key, value); - - // print out as JSON: {"name": "Example", "id": 1} - writefln("JSON: %s", j.toString()); - - // DEPRECATED: object members can be accessed using member syntax, just like in JavaScript - //j = Json.emptyObject; - //j.name = "Example"; - //j.id = 1; - } -} - -/// Constructing `Json` objects -@safe unittest { - // construct a JSON object {"field1": "foo", "field2": 42, "field3": true} - - // using the constructor - Json j1 = Json(["field1": Json("foo"), "field2": Json(42), "field3": Json(true)]); - - // using piecewise construction - Json j2 = Json.emptyObject; - j2["field1"] = "foo"; - j2["field2"] = 42.0; - j2["field3"] = true; - - // using serialization - struct S { - string field1; - double field2; - bool field3; - } - Json j3 = S("foo", 42, true).serializeToJson(); - - // using serialization, converting directly to a JSON string - string j4 = S("foo", 32, true).serializeToJsonString(); -} - - -public import vibe.data.serialization; -public import std.json : JSONException; - -import vibe.data.internal.conv : enumToString, formattedWriteFixed; - -import std.algorithm; -import std.array; -import std.bigint; -import std.conv; -import std.datetime; -import std.exception; -import std.format : FormatSpec, format; -import std.json : JSONValue, JSONType; -import std.range; -import std.string; -import std.traits; -import std.typecons : Tuple; -import std.uuid; - -/******************************************************************************/ -/* public types */ -/******************************************************************************/ - -/** - Represents a single JSON value. - - Json values can have one of the types defined in the Json.Type enum. They - behave mostly like values in ECMA script in the way that you can - transparently perform operations on them. However, strict typechecking is - done, so that operations between differently typed JSON values will throw - a JSONException. Additionally, an explicit cast or using get!() or to!() is - required to convert a JSON value to the corresponding static D type. -*/ -struct Json { -@safe: - - static assert(!hasElaborateDestructor!BigInt && !hasElaborateCopyConstructor!BigInt, - "struct Json is missing required ~this and/or this(this) members for BigInt."); - - private { - union { - BigInt m_bigInt; - long m_int; - double m_float; - bool m_bool; - string m_string; - Json[string] m_object; - Json[] m_array; - FillerType m_filler; - } - - Type m_type = Type.undefined; - - version (VibeJsonFieldNames) { - string m_name; - } - } - - // we should just use ubyte[exactSize] in the union, - // but using a static array there breaks ctfe. - // so just replace it with a struct with enough fields to cover every other type. - // Note that this will round Json's size to the nearest word-size, - // but that's okay because we want to be word aligned anyways. - private struct FillerType - { - static import std.meta; - - enum exactSize = Largest!(BigInt, long, double, bool, string, Json[string], Json[]).sizeof; - enum numFields = (exactSize + size_t.sizeof - 1) / size_t.sizeof; - - std.meta.Repeat!(numFields, size_t) fields; - } - - /** Represents the run time type of a JSON value. - */ - enum Type { - undefined, /// A non-existent value in a JSON object - null_, /// Null value - bool_, /// Boolean value - int_, /// 64-bit integer value - bigInt, /// BigInt values - float_, /// 64-bit floating point value - string, /// UTF-8 string - array, /// Array of JSON values - object, /// JSON object aka. dictionary from string to Json - - deprecated("Use `undefined` instead.") Undefined = undefined, /// Compatibility alias - deprecated("Use `null_` instead.") Null = null_, /// Compatibility alias - deprecated("Use `bool_` instead.") Bool = bool_, /// Compatibility alias - deprecated("Use `int_` instead.") Int = int_, /// Compatibility alias - deprecated("Use `float_` instead.") Float = float_, /// Compatibility alias - deprecated("Use `string` instead.") String = string, /// Compatibility alias - deprecated("Use `array` instead.") Array = array, /// Compatibility alias - deprecated("Use `object` instead.") Object = object /// Compatibility alias - } - - /// New JSON value of Type.Undefined - static @property Json undefined() nothrow { return Json(); } - - /// New JSON value of Type.Object - static @property Json emptyObject() nothrow { return Json(cast(Json[string])null); } - - /// New JSON value of Type.Array - static @property Json emptyArray() nothrow { return Json(cast(Json[])null); } - - version(JsonLineNumbers) int line; - - /** - Constructor for a JSON object. - */ - this(typeof(null)) nothrow { zeroFields; m_type = Type.null_; } - /// ditto - this(bool v) nothrow { zeroFields; m_type = Type.bool_; m_bool = v; } - /// ditto - this(byte v) nothrow { this(cast(long)v); } - /// ditto - this(ubyte v) nothrow { this(cast(long)v); } - /// ditto - this(short v) nothrow { this(cast(long)v); } - /// ditto - this(ushort v) nothrow { this(cast(long)v); } - /// ditto - this(int v) nothrow { this(cast(long)v); } - /// ditto - this(uint v) nothrow { this(cast(long)v); } - /// ditto - this(long v) nothrow { zeroFields; m_type = Type.int_; m_int = v; } - /// ditto - this(BigInt v) nothrow @trusted { zeroFields; m_type = Type.bigInt; initBigInt(); m_bigInt = v; } - /// ditto - this(double v) nothrow { zeroFields; m_type = Type.float_; m_float = v; } - /// ditto - this(string v) nothrow @trusted { zeroFields; m_type = Type.string; m_string = v; } - /// ditto - this(Json[] v) nothrow @trusted { zeroFields; m_type = Type.array; m_array = v; } - /// ditto - this(Json[string] v) nothrow @trusted { zeroFields; m_type = Type.object; m_object = v; } - - // used internally for UUID serialization support - private this(UUID v) nothrow { this(v.toString()); } - - // ensure that stale pointers in unused union memory don't cause leaks - private void zeroFields() nothrow { m_filler = FillerType.init; } - - /** - Converts a std.json.JSONValue object to a vibe Json object. - */ - this(in JSONValue value) { - final switch (value.type) { - case JSONType.null_: this = null; break; - case JSONType.object: - this = emptyObject; - () @trusted { - foreach (string k, ref const JSONValue v; value.object) - this[k] = Json(v); - } (); - break; - case JSONType.array: - this = (() @trusted => Json(value.array.map!(a => Json(a)).array))(); - break; - case JSONType.string: this = value.str; break; - case JSONType.integer: this = value.integer; break; - case JSONType.uinteger: this = BigInt(value.uinteger); break; - case JSONType.float_: this = value.floating; break; - case JSONType.true_: this = true; break; - case JSONType.false_: this = false; break; - } - } - - - /** - Allows assignment of D values to a JSON value. - */ - ref Json opAssign(Json v) return @trusted - nothrow { - if (v.type != Type.bigInt) - runDestructors(); - auto old_type = m_type; - m_type = v.m_type; - final switch(m_type){ - case Type.undefined: m_string = null; break; - case Type.null_: m_string = null; break; - case Type.bool_: m_bool = v.m_bool; break; - case Type.int_: m_int = v.m_int; break; - case Type.bigInt: - if (old_type != Type.bigInt) - initBigInt(); - m_bigInt = v.m_bigInt; - break; - case Type.float_: m_float = v.m_float; break; - case Type.string: m_string = v.m_string; break; - case Type.array: opAssign(v.m_array); break; - case Type.object: opAssign(v.m_object); break; - } - return this; - } - /// ditto - void opAssign(typeof(null)) nothrow @trusted { runDestructors(); m_type = Type.null_; m_string = null; } - /// ditto - bool opAssign(bool v) nothrow { runDestructors(); m_type = Type.bool_; m_bool = v; return v; } - /// ditto - int opAssign(int v) nothrow { runDestructors(); m_type = Type.int_; m_int = v; return v; } - /// ditto - long opAssign(long v) nothrow { runDestructors(); m_type = Type.int_; m_int = v; return v; } - /// ditto - BigInt opAssign(BigInt v) - nothrow @trusted { - if (m_type != Type.bigInt) - initBigInt(); - m_type = Type.bigInt; - m_bigInt = v; - return v; - } - /// ditto - double opAssign(double v) nothrow { runDestructors(); m_type = Type.float_; m_float = v; return v; } - /// ditto - string opAssign(string v) nothrow @trusted { runDestructors(); m_type = Type.string; m_string = v; return v; } - /// ditto - Json[] opAssign(Json[] v) - nothrow @trusted { - runDestructors(); - m_type = Type.array; - m_array = v; - version (VibeJsonFieldNames) { - try { - foreach (idx, ref av; m_array) - av.m_name = format("%s[%s]", m_name, idx); - } catch (Exception e) assert(false, e.msg); - } - return v; - } - /// ditto - Json[string] opAssign(Json[string] v) - nothrow @trusted { - runDestructors(); - m_type = Type.object; - m_object = v; - version (VibeJsonFieldNames) { - try { - foreach (key, ref av; m_object) - av.m_name = format("%s.%s", m_name, key); - } catch (Exception e) assert(false, e.msg); - } - return v; - } - - // used internally for UUID serialization support - private UUID opAssign(UUID v) nothrow { opAssign(v.toString()); return v; } - - /** - Allows removal of values from Type.Object Json objects. - */ - void remove(string item) @trusted { checkType!(Json[string])(); m_object.remove(item); } - - /** - The current type id of this JSON object. - */ - @property Type type() const nothrow { return m_type; } - - /** - Clones a JSON value recursively. - */ - Json clone() - const nothrow @trusted { - final switch (m_type) { - case Type.undefined: return Json.undefined; - case Type.null_: return Json(null); - case Type.bool_: return Json(m_bool); - case Type.int_: return Json(m_int); - case Type.bigInt: return Json(m_bigInt); - case Type.float_: return Json(m_float); - case Type.string: return Json(m_string); - case Type.array: - Json[] ret; - foreach (v; m_array) ret ~= v.clone(); - return Json(ret); - case Type.object: - Json[string] ret; - foreach (kv; m_object.byKeyValue) - ret[kv.key] = kv.value.clone(); - return Json(ret); - } - } - - /** - Allows direct indexing of array typed JSON values. - */ - ref inout(Json) opIndex(size_t idx) inout @trusted { checkType!(Json[])(); return m_array[idx]; } - - /// - @safe unittest { - Json value = Json.emptyArray; - value ~= 1; - value ~= true; - value ~= "foo"; - assert(value[0] == 1); - assert(value[1] == true); - assert(value[2] == "foo"); - } - - - /** - Allows direct indexing of object typed JSON values using a string as - the key. - - Returns an object of `Type.undefined` if the key was not found. - */ - const(Json) opIndex(string key) - const @trusted { - checkType!(Json[string])(); - if( auto pv = key in m_object ) return *pv; - Json ret = Json.undefined; - ret.m_string = key; - version (VibeJsonFieldNames) ret.m_name = format("%s.%s", m_name, key); - return ret; - } - /// ditto - ref Json opIndex(string key) - @trusted { - checkType!(Json[string])(); - if( auto pv = key in m_object ) - return *pv; - if (m_object is null) { - m_object = ["": Json.init]; - m_object.remove(""); - } - m_object[key] = Json.init; - auto nv = key in m_object; - assert(m_object !is null); - assert(nv !is null, "Failed to insert key '"~key~"' into AA!?"); - nv.m_type = Type.undefined; - assert(nv.type == Type.undefined); - nv.m_string = key; - version (VibeJsonFieldNames) nv.m_name = format("%s.%s", m_name, key); - return *nv; - } - - /// - @safe unittest { - Json value = Json.emptyObject; - value["a"] = 1; - value["b"] = true; - value["c"] = "foo"; - assert(value["a"] == 1); - assert(value["b"] == true); - assert(value["c"] == "foo"); - assert(value["not-existing"].type() == Type.undefined); - } - - /** - Returns a slice of a JSON array. - */ - inout(Json[]) opSlice() - inout @trusted { - checkType!(Json[])(); - return m_array; - } - /// - inout(Json[]) opSlice(size_t from, size_t to) - inout @trusted { - checkType!(Json[])(); - return m_array[from .. to]; - } - - /** - Returns the number of entries of string, array or object typed JSON values. - */ - @property size_t length() - const @trusted { - checkType!(string, Json[], Json[string])("property length"); - switch(m_type){ - case Type.string: return m_string.length; - case Type.array: return m_array.length; - case Type.object: return m_object.length; - default: assert(false); - } - } - - /** - Allows foreach iterating over JSON objects and arrays. - */ - int opApply(scope int delegate(ref Json obj) del) - @system { - checkType!(Json[], Json[string])("opApply"); - if( m_type == Type.array ){ - foreach( ref v; m_array ) - if( auto ret = del(v) ) - return ret; - return 0; - } else { - foreach( ref v; m_object ) - if( v.type != Type.undefined ) - if( auto ret = del(v) ) - return ret; - return 0; - } - } - /// ditto - int opApply(scope int delegate(ref const Json obj) del) - const @system { - checkType!(Json[], Json[string])("opApply"); - if( m_type == Type.array ){ - foreach( ref v; m_array ) - if( auto ret = del(v) ) - return ret; - return 0; - } else { - foreach( ref v; m_object ) - if( v.type != Type.undefined ) - if( auto ret = del(v) ) - return ret; - return 0; - } - } - /// ditto - int opApply(scope int delegate(ref size_t idx, ref Json obj) del) - @system { - checkType!(Json[])("opApply"); - foreach( idx, ref v; m_array ) - if( auto ret = del(idx, v) ) - return ret; - return 0; - } - /// ditto - int opApply(scope int delegate(ref size_t idx, ref const Json obj) del) - const @system { - checkType!(Json[])("opApply"); - foreach( idx, ref v; m_array ) - if( auto ret = del(idx, v) ) - return ret; - return 0; - } - /// ditto - int opApply(scope int delegate(ref string idx, ref Json obj) del) - @system { - checkType!(Json[string])("opApply"); - foreach( idx, ref v; m_object ) - if( v.type != Type.undefined ) - if( auto ret = del(idx, v) ) - return ret; - return 0; - } - /// ditto - int opApply(scope int delegate(ref string idx, ref const Json obj) del) - const @system { - checkType!(Json[string])("opApply"); - foreach( idx, ref v; m_object ) - if( v.type != Type.undefined ) - if( auto ret = del(idx, v) ) - return ret; - return 0; - } - - private alias KeyValue = Tuple!(string, "key", Json, "value"); - - /// Iterates over all key/value pairs of an object. - @property auto byKeyValue() - @trusted { - checkType!(Json[string])("byKeyValue"); - return m_object.byKeyValue.map!(kv => KeyValue(kv.key, kv.value)).trustedRange; - } - /// ditto - @property auto byKeyValue() - const @trusted { - checkType!(Json[string])("byKeyValue"); - return m_object.byKeyValue.map!(kv => const(KeyValue)(kv.key, kv.value)).trustedRange; - } - /// Iterates over all index/value pairs of an array. - @property auto byIndexValue() - @trusted { - checkType!(Json[])("byIndexValue"); - return zip(iota(0, m_array.length), m_array); - } - /// ditto - @property auto byIndexValue() - const @trusted { - checkType!(Json[])("byIndexValue"); - return zip(iota(0, m_array.length), m_array); - } - /// Iterates over all values of an object or array. - @property auto byValue() - @trusted { - checkType!(Json[], Json[string])("byValue"); - static struct Rng { - private { - bool isArray; - Json[] array; - typeof(Json.init.m_object.byValue) object; - } - - bool empty() @trusted nothrow { if (isArray) return array.length == 0; else return object.empty; } - auto front() @trusted nothrow { if (isArray) return array[0]; else return object.front; } - void popFront() @trusted nothrow { if (isArray) array = array[1 .. $]; else object.popFront(); } - } - - if (m_type == Type.array) return Rng(true, m_array); - else return Rng(false, null, m_object.byValue); - } - /// ditto - @property auto byValue() - const @trusted { - checkType!(Json[], Json[string])("byValue"); - static struct Rng { - @safe: - private { - bool isArray; - const(Json)[] array; - typeof(const(Json).init.m_object.byValue) object; - } - - bool empty() @trusted nothrow { if (isArray) return array.length == 0; else return object.empty; } - auto front() @trusted nothrow { if (isArray) return array[0]; else return object.front; } - void popFront() @trusted nothrow { if (isArray) array = array[1 .. $]; else object.popFront(); } - } - - if (m_type == Type.array) return Rng(true, m_array); - else return Rng(false, null, m_object.byValue); - } - - - /** - Converts this Json object to a std.json.JSONValue object - */ - T opCast(T)() const - if (is(T == JSONValue)) - { - final switch (type) { - case Json.Type.undefined: - case Json.Type.null_: - return JSONValue(null); - case Json.Type.bool_: - return JSONValue(get!bool); - case Json.Type.int_: - return JSONValue(get!long); - case Json.Type.bigInt: - auto bi = get!BigInt; - if (bi > long.max) - return JSONValue((() @trusted => cast(ulong)get!BigInt)()); - else - return JSONValue((() @trusted => cast(long)get!BigInt)()); - case Json.Type.float_: - return JSONValue(get!double); - case Json.Type.string: - return JSONValue(get!string); - case Json.Type.array: - JSONValue[] ret; - foreach (ref const Json e; byValue) - ret ~= cast(JSONValue)e; - return JSONValue(ret); - case Json.Type.object: - JSONValue[string] ret; - foreach (string k, ref const Json e; byKeyValue) { - if( e.type == Json.Type.undefined ) continue; - ret[k] = cast(JSONValue)e; - } - return JSONValue(ret); - } - } - - - /** - Converts the JSON value to the corresponding D type - types must match exactly. - - Available_Types: - $(UL - $(LI `bool` (`Type.bool_`)) - $(LI `double` (`Type.float_`)) - $(LI `float` (Converted from `double`)) - $(LI `long` (`Type.int_`)) - $(LI `ulong`, `int`, `uint`, `short`, `ushort`, `byte`, `ubyte` (Converted from `long`)) - $(LI `string` (`Type.string`)) - $(LI `Json[]` (`Type.array`)) - $(LI `Json[string]` (`Type.object`)) - ) - - See_Also: `opt`, `to`, `deserializeJson` - */ - inout(T) opCast(T)() inout if (!is(T == JSONValue)) { return get!T; } - /// ditto - @property inout(T) get(T)() - inout @trusted { - static if (!is(T : bool) && is(T : long)) - checkType!(long, BigInt)(); - else - checkType!T(); - - static if (is(T == bool)) return m_bool; - else static if (is(T == double)) return m_float; - else static if (is(T == float)) return cast(T)m_float; - else static if (is(T == string)) return m_string; - else static if (is(T == UUID)) return UUID(m_string); - else static if (is(T == Json[])) return m_array; - else static if (is(T == Json[string])) return m_object; - else static if (is(T == BigInt)) return m_type == Type.bigInt ? m_bigInt : BigInt(m_int); - else static if (is(T : long)) { - if (m_type == Type.bigInt) { - enforceJson(m_bigInt <= T.max && m_bigInt >= T.min, "Integer conversion out of bounds error"); - return cast(T)m_bigInt.toLong(); - } else { - enforceJson(m_int <= T.max && m_int >= T.min, "Integer conversion out of bounds error"); - return cast(T)m_int; - } - } - else static assert(0, "JSON can only be cast to (bool, long, std.bigint.BigInt, double, string, Json[] or Json[string]. Not "~T.stringof~"."); - } - - /** - Returns the native type for this JSON if it matches the current runtime type. - - If the runtime type does not match the given native type, the 'def' parameter is returned - instead. - - See_Also: `get` - */ - @property const(T) opt(T)(const(T) def = T.init) - const { - if( typeId!T != m_type ) return def; - return get!T; - } - /// ditto - @property T opt(T)(T def = T.init) - { - if( typeId!T != m_type ) return def; - return get!T; - } - - /** - Converts the JSON value to the corresponding D type - types are converted as necessary. - - Automatically performs conversions between strings and numbers. See - `get` for the list of available types. For converting/deserializing - JSON to complex data types see `deserializeJson`. - - See_Also: `get`, `deserializeJson` - */ - @property inout(T) to(T)() - inout @trusted { - static if( is(T == bool) ){ - final switch( m_type ){ - case Type.undefined: return false; - case Type.null_: return false; - case Type.bool_: return m_bool; - case Type.int_: return m_int != 0; - case Type.bigInt: return m_bigInt != 0; - case Type.float_: return m_float != 0; - case Type.string: return m_string.length > 0; - case Type.array: return m_array.length > 0; - case Type.object: return m_object.length > 0; - } - } else static if( is(T == double) ){ - final switch( m_type ){ - case Type.undefined: return T.init; - case Type.null_: return 0; - case Type.bool_: return m_bool ? 1 : 0; - case Type.int_: return m_int; - case Type.bigInt: return bigIntToLong(); - case Type.float_: return m_float; - case Type.string: return .to!double(cast(string)m_string); - case Type.array: return double.init; - case Type.object: return double.init; - } - } else static if( is(T == float) ){ - final switch( m_type ){ - case Type.undefined: return T.init; - case Type.null_: return 0; - case Type.bool_: return m_bool ? 1 : 0; - case Type.int_: return m_int; - case Type.bigInt: return bigIntToLong(); - case Type.float_: return m_float; - case Type.string: return .to!float(cast(string)m_string); - case Type.array: return float.init; - case Type.object: return float.init; - } - } else static if( is(T == long) ){ - final switch( m_type ){ - case Type.undefined: return 0; - case Type.null_: return 0; - case Type.bool_: return m_bool ? 1 : 0; - case Type.int_: return m_int; - case Type.bigInt: return cast(long)bigIntToLong(); - case Type.float_: return cast(long)m_float; - case Type.string: return .to!long(m_string); - case Type.array: return 0; - case Type.object: return 0; - } - } else static if( is(T : long) ){ - final switch( m_type ){ - case Type.undefined: return 0; - case Type.null_: return 0; - case Type.bool_: return m_bool ? 1 : 0; - case Type.int_: return cast(T)m_int; - case Type.bigInt: return cast(T)bigIntToLong(); - case Type.float_: return cast(T)m_float; - case Type.string: return cast(T).to!long(cast(string)m_string); - case Type.array: return 0; - case Type.object: return 0; - } - } else static if( is(T == string) ){ - switch( m_type ){ - default: return toString(); - case Type.string: return m_string; - } - } else static if( is(T == Json[]) ){ - switch( m_type ){ - default: return Json([this]); - case Type.array: return m_array; - } - } else static if( is(T == Json[string]) ){ - switch( m_type ){ - default: return Json(["value": this]); - case Type.object: return m_object; - } - } else static if( is(T == BigInt) ){ - final switch( m_type ){ - case Type.undefined: return BigInt(0); - case Type.null_: return BigInt(0); - case Type.bool_: return BigInt(m_bool ? 1 : 0); - case Type.int_: return BigInt(m_int); - case Type.bigInt: return m_bigInt; - case Type.float_: return BigInt(cast(long)m_float); - case Type.string: return BigInt(.to!long(m_string)); - case Type.array: return BigInt(0); - case Type.object: return BigInt(0); - } - } else static if (is(T == JSONValue)) { - return cast(JSONValue)this; - } else static assert(0, "JSON can only be cast to (bool, long, std.bigint.BigInt, double, string, Json[] or Json[string]. Not "~T.stringof~"."); - } - - /** - Performs unary operations on the JSON value. - - The following operations are supported for each type: - - $(DL - $(DT Null) $(DD none) - $(DT Bool) $(DD ~) - $(DT Int) $(DD +, -, ++, --) - $(DT Float) $(DD +, -, ++, --) - $(DT String) $(DD none) - $(DT Array) $(DD none) - $(DT Object) $(DD none) - ) - */ - Json opUnary(string op)() - const @trusted { - static if( op == "~" ){ - checkType!bool(); - return Json(~m_bool); - } else static if( op == "+" || op == "-" || op == "++" || op == "--" ){ - checkType!(BigInt, long, double)("unary "~op); - if( m_type == Type.int_ ) mixin("return Json("~op~"m_int);"); - else if( m_type == Type.bigInt ) mixin("return Json("~op~"m_bigInt);"); - else if( m_type == Type.float_ ) mixin("return Json("~op~"m_float);"); - else assert(false); - } else static assert(0, "Unsupported operator '"~op~"' for type JSON."); - } - /** - Performs binary operations between JSON values. - - The two JSON values must be of the same run time type or a JSONException - will be thrown. Only the operations listed are allowed for each of the - types. - - $(DL - $(DT Null) $(DD none) - $(DT Bool) $(DD &&, ||) - $(DT Int) $(DD +, -, *, /, %) - $(DT Float) $(DD +, -, *, /, %) - $(DT String) $(DD ~) - $(DT Array) $(DD ~) - $(DT Object) $(DD in) - ) - */ - Json opBinary(string op)(ref const(Json) other) - const @trusted { - enforceJson(m_type == other.m_type, "Binary operation '"~op~"' between "~m_type.enumToString~" and "~other.m_type.enumToString~" JSON objects."); - static if( op == "&&" ){ - checkType!(bool)(op); - return Json(m_bool && other.m_bool); - } else static if( op == "||" ){ - checkType!(bool)(op); - return Json(m_bool || other.m_bool); - } else static if( op == "+" ){ - checkType!(BigInt, long, double)(op); - if( m_type == Type.int_ ) return Json(m_int + other.m_int); - else if( m_type == Type.bigInt ) return Json(() @trusted { return m_bigInt + other.m_bigInt; } ()); - else if( m_type == Type.float_ ) return Json(m_float + other.m_float); - else assert(false); - } else static if( op == "-" ){ - checkType!(BigInt, long, double)(op); - if( m_type == Type.int_ ) return Json(m_int - other.m_int); - else if( m_type == Type.bigInt ) return Json(() @trusted { return m_bigInt - other.m_bigInt; } ()); - else if( m_type == Type.float_ ) return Json(m_float - other.m_float); - else assert(false); - } else static if( op == "*" ){ - checkType!(BigInt, long, double)(op); - if( m_type == Type.int_ ) return Json(m_int * other.m_int); - else if( m_type == Type.bigInt ) return Json(() @trusted { return m_bigInt * other.m_bigInt; } ()); - else if( m_type == Type.float_ ) return Json(m_float * other.m_float); - else assert(false); - } else static if( op == "/" ){ - checkType!(BigInt, long, double)(op); - if( m_type == Type.int_ ) return Json(m_int / other.m_int); - else if( m_type == Type.bigInt ) return Json(() @trusted { return m_bigInt / other.m_bigInt; } ()); - else if( m_type == Type.float_ ) return Json(m_float / other.m_float); - else assert(false); - } else static if( op == "%" ){ - checkType!(BigInt, long, double)(op); - if( m_type == Type.int_ ) return Json(m_int % other.m_int); - else if( m_type == Type.bigInt ) return Json(() @trusted { return m_bigInt % other.m_bigInt; } ()); - else if( m_type == Type.float_ ) return Json(m_float % other.m_float); - else assert(false); - } else static if( op == "~" ){ - checkType!(string, Json[])(op); - if( m_type == Type.string ) return Json(m_string ~ other.m_string); - else if (m_type == Type.array) return Json(m_array ~ other.m_array); - else assert(false); - } else static assert(0, "Unsupported operator '"~op~"' for type JSON."); - } - /// ditto - Json opBinary(string op)(Json other) @trusted - if( op == "~" ) - { - static if( op == "~" ){ - checkType!(string, Json[])(op); - if( m_type == Type.string ) return Json(m_string ~ other.m_string); - else if( m_type == Type.array ) return Json(m_array ~ other.m_array); - else assert(false); - } else static assert(0, "Unsupported operator '"~op~"' for type JSON."); - } - /// ditto - void opOpAssign(string op)(Json other) @trusted - if (op == "+" || op == "-" || op == "*" || op == "/" || op == "%" || op =="~") - { - enforceJson(m_type == other.m_type || op == "~" && m_type == Type.array, - "Binary operation '"~op~"=' between "~m_type.enumToString~" and "~other.m_type.enumToString~" JSON objects."); - static if( op == "+" ){ - if( m_type == Type.int_ ) m_int += other.m_int; - else if( m_type == Type.bigInt ) m_bigInt += other.m_bigInt; - else if( m_type == Type.float_ ) m_float += other.m_float; - else enforceJson(false, "'+=' only allowed for scalar types, not "~m_type.enumToString~"."); - } else static if( op == "-" ){ - if( m_type == Type.int_ ) m_int -= other.m_int; - else if( m_type == Type.bigInt ) m_bigInt -= other.m_bigInt; - else if( m_type == Type.float_ ) m_float -= other.m_float; - else enforceJson(false, "'-=' only allowed for scalar types, not "~m_type.enumToString~"."); - } else static if( op == "*" ){ - if( m_type == Type.int_ ) m_int *= other.m_int; - else if( m_type == Type.bigInt ) m_bigInt *= other.m_bigInt; - else if( m_type == Type.float_ ) m_float *= other.m_float; - else enforceJson(false, "'*=' only allowed for scalar types, not "~m_type.enumToString~"."); - } else static if( op == "/" ){ - if( m_type == Type.int_ ) m_int /= other.m_int; - else if( m_type == Type.bigInt ) m_bigInt /= other.m_bigInt; - else if( m_type == Type.float_ ) m_float /= other.m_float; - else enforceJson(false, "'/=' only allowed for scalar types, not "~m_type.enumToString~"."); - } else static if( op == "%" ){ - if( m_type == Type.int_ ) m_int %= other.m_int; - else if( m_type == Type.bigInt ) m_bigInt %= other.m_bigInt; - else if( m_type == Type.float_ ) m_float %= other.m_float; - else enforceJson(false, "'%=' only allowed for scalar types, not "~m_type.enumToString~"."); - } else static if( op == "~" ){ - if (m_type == Type.string) m_string ~= other.m_string; - else if (m_type == Type.array) { - if (other.m_type == Type.array) m_array ~= other.m_array; - else appendArrayElement(other); - } else enforceJson(false, "'~=' only allowed for string and array types, not "~m_type.enumToString~"."); - } else static assert(0, "Unsupported operator '"~op~"=' for type JSON."); - } - /// ditto - void opOpAssign(string op, T)(T other) - if (!is(T == Json) && is(typeof(Json(other)))) - { - opOpAssign!op(Json(other)); - } - /// ditto - Json opBinary(string op)(bool other) const { checkType!bool(); mixin("return Json(m_bool "~op~" other);"); } - /// ditto - Json opBinary(string op)(long other) const @trusted - { - checkType!(long, BigInt)(); - if (m_type == Type.bigInt) - mixin("return Json(m_bigInt "~op~" other);"); - else - mixin("return Json(m_int "~op~" other);"); - } - /// ditto - Json opBinary(string op)(BigInt other) const @trusted - { - checkType!(long, BigInt)(); - if (m_type == Type.bigInt) - mixin("return Json(m_bigInt "~op~" other);"); - else - mixin("return Json(m_int "~op~" other);"); - } - /// ditto - Json opBinary(string op)(double other) const { checkType!double(); mixin("return Json(m_float "~op~" other);"); } - /// ditto - Json opBinary(string op)(string other) const @trusted { checkType!string(); mixin("return Json(m_string "~op~" other);"); } - /// ditto - Json opBinary(string op)(Json[] other) @trusted { checkType!(Json[])(); mixin("return Json(m_array "~op~" other);"); } - /// ditto - Json opBinaryRight(string op)(bool other) const { checkType!bool(); mixin("return Json(other "~op~" m_bool);"); } - /// ditto - Json opBinaryRight(string op)(long other) const @trusted - { - checkType!(long, BigInt)(); - if (m_type == Type.bigInt) - mixin("return Json(other "~op~" m_bigInt);"); - else - mixin("return Json(other "~op~" m_int);"); - } - /// ditto - Json opBinaryRight(string op)(BigInt other) const @trusted - { - checkType!(long, BigInt)(); - if (m_type == Type.bigInt) - mixin("return Json(other "~op~" m_bigInt);"); - else - mixin("return Json(other "~op~" m_int);"); - } - /// ditto - Json opBinaryRight(string op)(double other) const { checkType!double(); mixin("return Json(other "~op~" m_float);"); } - /// ditto - Json opBinaryRight(string op)(string other) const @trusted if(op == "~") - { - checkType!string(); - return Json(other ~ m_string); - } - /// ditto - Json opBinaryRight(string op)(Json[] other) @trusted { checkType!(Json[])(); mixin("return Json(other "~op~" m_array);"); } - - - /** Checks wheter a particular key is set and returns a pointer to it. - - For field that don't exist or have a type of `Type.undefined`, - the `in` operator will return `null`. - */ - inout(Json)* opBinaryRight(string op)(string other) inout @trusted - if(op == "in") - { - checkType!(Json[string])(); - auto pv = other in m_object; - if (!pv) return null; - if (pv.type == Type.undefined) return null; - return pv; - } - - /// - @safe unittest { - auto j = Json.emptyObject; - j["a"] = "foo"; - j["b"] = Json.undefined; - - assert("a" in j); - assert(("a" in j).get!string == "foo"); - assert("b" !in j); - assert("c" !in j); - } - - - /** - * The append operator will append arrays. This method always appends it's argument as an array element, so nested arrays can be created. - */ - void appendArrayElement(Json element) - @trusted { - enforceJson(m_type == Type.array, "'appendArrayElement' only allowed for array types, not "~m_type.enumToString~"."); - m_array ~= element; - } - - /** - Compares two JSON values for equality. - - If the two values have different types, they are considered unequal. - This differs with ECMA script, which performs a type conversion before - comparing the values. - */ - - bool opEquals(ref const Json other) - const @trusted { - if( m_type != other.m_type ) return false; - final switch(m_type){ - case Type.undefined: return false; - case Type.null_: return true; - case Type.bool_: return m_bool == other.m_bool; - case Type.int_: return m_int == other.m_int; - case Type.bigInt: return m_bigInt == other.m_bigInt; - case Type.float_: return m_float == other.m_float; - case Type.string: return m_string == other.m_string; - case Type.array: return m_array == other.m_array; - case Type.object: return m_object == other.m_object; - } - } - /// ditto - bool opEquals(const Json other) const { return opEquals(other); } - /// ditto - bool opEquals(typeof(null)) const { return m_type == Type.null_; } - /// ditto - bool opEquals(bool v) const { return m_type == Type.bool_ && m_bool == v; } - /// ditto - bool opEquals(int v) const @trusted { return (m_type == Type.int_ && m_int == v) || (m_type == Type.bigInt && m_bigInt == v); } - /// ditto - bool opEquals(long v) const @trusted { return (m_type == Type.int_ && m_int == v) || (m_type == Type.bigInt && m_bigInt == v); } - /// ditto - bool opEquals(BigInt v) const @trusted { return (m_type == Type.int_ && m_int == v) || (m_type == Type.bigInt && m_bigInt == v); } - /// ditto - bool opEquals(double v) const { return m_type == Type.float_ && m_float == v; } - /// ditto - bool opEquals(string v) const @trusted { return m_type == Type.string && m_string == v; } - - /** - Compares two JSON values. - - If the types of the two values differ, the value with the smaller type - id is considered the smaller value. This differs from ECMA script, which - performs a type conversion before comparing the values. - - JSON values of type Object cannot be compared and will throw an - exception. - */ - int opCmp(ref const Json other) - const @trusted { - if( m_type != other.m_type ) return m_type < other.m_type ? -1 : 1; - final switch(m_type){ - case Type.undefined: return 0; - case Type.null_: return 0; - case Type.bool_: return m_bool < other.m_bool ? -1 : m_bool == other.m_bool ? 0 : 1; - case Type.int_: return m_int < other.m_int ? -1 : m_int == other.m_int ? 0 : 1; - case Type.bigInt: return () @trusted { return m_bigInt < other.m_bigInt; } () ? -1 : m_bigInt == other.m_bigInt ? 0 : 1; - case Type.float_: return m_float < other.m_float ? -1 : m_float == other.m_float ? 0 : 1; - case Type.string: return m_string < other.m_string ? -1 : m_string == other.m_string ? 0 : 1; - case Type.array: return m_array < other.m_array ? -1 : m_array == other.m_array ? 0 : 1; - case Type.object: - enforceJson(false, "JSON objects cannot be compared."); - assert(false); - } - } - - alias opDollar = length; - - /** - Returns the type id corresponding to the given D type. - */ - static @property Type typeId(T)() { - static if( is(T == typeof(null)) ) return Type.null_; - else static if( is(T == bool) ) return Type.bool_; - else static if( is(T == double) ) return Type.float_; - else static if( is(T == float) ) return Type.float_; - else static if( is(T : long) ) return Type.int_; - else static if( is(T == string) ) return Type.string; - else static if( is(T == UUID) ) return Type.string; - else static if( is(T == Json[]) ) return Type.array; - else static if( is(T == Json[string]) ) return Type.object; - else static if( is(T == BigInt) ) return Type.bigInt; - else static assert(false, "Unsupported JSON type '"~T.stringof~"'. Only bool, long, std.bigint.BigInt, double, string, Json[] and Json[string] are allowed."); - } - - /** - Returns the JSON object as a string. - - For large JSON values use writeJsonString instead as this function will store the whole string - in memory, whereas writeJsonString writes it out bit for bit. - - See_Also: writeJsonString, toPrettyString - */ - string toString() - const @trusted { - // DMD BUG: this should actually be all @safe, but for some reason - // @safe inference for writeJsonString doesn't work. - auto ret = appender!string(); - writeJsonString(ret, this); - return ret.data; - } - /// ditto - void toString(scope void delegate(scope const(char)[]) @safe sink, FormatSpec!char fmt) - @trusted { - // DMD BUG: this should actually be all @safe, but for some reason - // @safe inference for writeJsonString doesn't work. - static struct DummyRangeS { - void delegate(scope const(char)[]) @safe sink; - void put(scope const(char)[] str) @safe { sink(str); } - void put(char ch) @trusted { sink((&ch)[0 .. 1]); } - } - auto r = DummyRangeS(sink); - writeJsonString(r, this); - } - /// ditto - void toString(scope void delegate(scope const(char)[]) @system sink, FormatSpec!char fmt) - @system { - // DMD BUG: this should actually be all @safe, but for some reason - // @safe inference for writeJsonString doesn't work. - static struct DummyRange { - void delegate(scope const(char)[]) sink; - @trusted: - void put(scope const(char)[] str) { sink(str); } - void put(char ch) { sink((&ch)[0 .. 1]); } - } - auto r = DummyRange(sink); - writeJsonString(r, this); - } - /// ditto - deprecated("Use a `scope` argument for the `sink` delegate") - void toString(scope void delegate(const(char)[]) @safe sink, FormatSpec!char fmt) - @trusted { - // DMD BUG: this should actually be all @safe, but for some reason - // @safe inference for writeJsonString doesn't work. - static struct DummyRangeS { - void delegate(const(char)[]) @safe sink; - void put(scope const(char)[] str) @safe { sink(str); } - void put(char ch) @trusted { sink((&ch)[0 .. 1]); } - } - auto r = DummyRangeS(sink); - writeJsonString(r, this); - } - /// ditto - void toString(scope void delegate(const(char)[]) @system sink, FormatSpec!char fmt) - @system { - // DMD BUG: this should actually be all @safe, but for some reason - // @safe inference for writeJsonString doesn't work. - static struct DummyRange { - void delegate(const(char)[]) sink; - @trusted: - void put(scope const(char)[] str) { sink(str); } - void put(char ch) { sink((&ch)[0 .. 1]); } - } - auto r = DummyRange(sink); - writeJsonString(r, this); - } - - /** - Returns the JSON object as a "pretty" string. - - --- - auto json = Json(["foo": Json("bar")]); - writeln(json.toPrettyString()); - - // output: - // { - // "foo": "bar" - // } - --- - - Params: - level = Specifies the base amount of indentation for the output. Indentation is always - done using tab characters. - - See_Also: writePrettyJsonString, toString - */ - string toPrettyString(int level = 0) - const @trusted { - auto ret = appender!string(); - writePrettyJsonString(ret, this, level); - return ret.data; - } - - private void checkType(TYPES...)(string op = null) - const { - bool matched = false; - foreach (T; TYPES) if (m_type == typeId!T) matched = true; - if (matched) return; - - string name; - version (VibeJsonFieldNames) { - if (m_name.length) name = m_name ~ " of type " ~ m_type.enumToString; - else name = "JSON of type " ~ m_type.enumToString; - } else name = "JSON of type " ~ m_type.enumToString; - - string expected; - static if (TYPES.length == 1) expected = typeId!(TYPES[0]).enumToString; - else { - foreach (T; TYPES) { - if (expected.length > 0) expected ~= ", "; - expected ~= typeId!T.enumToString; - } - } - - if (!op.length) throw new JSONException(format("Got %s, expected %s.", name, expected)); - else throw new JSONException(format("Got %s, expected %s for %s.", name, expected, op)); - } - - private void initBigInt() - nothrow @trusted { - // BigInt is a struct, and it has a special BigInt.init value, which differs from null. - m_bigInt = BigInt.init; - } - - private void runDestructors() - nothrow @trusted { - if (m_type != Type.bigInt) - { - zeroFields; - return; - } - - BigInt init_; - // After swaping, init_ contains the real number from Json, and it - // will be destroyed when this function is finished. - // m_bigInt now contains static BigInt.init value and destruction may - // be ommited for it. - swap(init_, m_bigInt); - } - - private long bigIntToLong() inout @trusted - { - assert(m_type == Type.bigInt, format("Converting non-bigInt type with bitIntToLong!?: %s", (cast(Type)m_type).enumToString)); - enforceJson(m_bigInt >= long.min && m_bigInt <= long.max, "Number out of range while converting BigInt("~format("%d", m_bigInt)~") to long."); - return m_bigInt.toLong(); - } - - /*invariant() - { - assert(m_type >= Type.Undefined && m_type <= Type.Object); - }*/ -} - -@safe unittest { // issue #1234 - @safe toString - auto j = Json(true); - j.toString((scope str) @safe {}, FormatSpec!char("s")); - assert(j.toString() == "true"); -} - - -/******************************************************************************/ -/* public functions */ -/******************************************************************************/ - -/** - Parses the given range as a JSON string and returns the corresponding Json object. - - The range is shrunk during parsing, leaving any remaining text that is not part of - the JSON contents. - - Throws a JSONException if any parsing error occured. -*/ -Json parseJson(R)(ref R range, scope int* line = null, string filename = null) - if (isForwardRange!R) -{ - Json ret; - enforceJson(!range.empty, "JSON string is empty.", filename, 0); - - skipWhitespace(range, line); - - enforceJson(!range.empty, "JSON string contains only whitespaces.", filename, 0); - - version(JsonLineNumbers) { - int curline = line ? *line : 0; - } - - bool minus = false; - switch( range.front ){ - case 'f': - enforceJson(range.save.dropOne.startsWith("alse"), - "Expected 'false', got '"~range.take(5).to!string~"'.", filename, line); - range.popFrontN(5); - ret = false; - break; - case 'n': - enforceJson(range.save.dropOne.startsWith("ull"), - "Expected 'null', got '"~range.take(4).to!string~"'.", filename, line); - range.popFrontN(4); - ret = null; - break; - case 't': - enforceJson(range.save.dropOne.startsWith("rue"), - "Expected 'true', got '"~range.take(4).to!string~"'.", filename, line); - range.popFrontN(4); - ret = true; - break; - - case '-': - case '0': .. case '9': - bool is_long_overflow; - bool is_float; - auto num = skipNumber(range, is_float, is_long_overflow); - if( is_float ) { - ret = to!double(num); - } else if (is_long_overflow) { - ret = () @trusted { return BigInt(num.to!string); } (); - } else { - ret = to!long(num); - } - break; - case '\"': - ret = skipJsonString(range); - break; - case '[': - auto arr = appender!(Json[]); - range.popFront(); - while (true) { - skipWhitespace(range, line); - enforceJson(!range.empty, "Missing ']' before EOF.", filename, line); - if(range.front == ']') break; - arr ~= parseJson(range, line, filename); - skipWhitespace(range, line); - enforceJson(!range.empty, "Missing ']' before EOF.", filename, line); - enforceJson(range.front == ',' || range.front == ']', - format("Expected ']' or ',' - got '%s'.", range.front), filename, line); - if( range.front == ']' ) break; - else range.popFront(); - } - range.popFront(); - ret = arr.data; - break; - case '{': - Json[string] obj; - range.popFront(); - while (true) { - skipWhitespace(range, line); - enforceJson(!range.empty, "Missing '}' before EOF.", filename, line); - if(range.front == '}') break; - string key = skipJsonString(range); - skipWhitespace(range, line); - enforceJson(range.startsWith(":"), "Expected ':' for key '" ~ key ~ "'", filename, line); - range.popFront(); - skipWhitespace(range, line); - Json itm = parseJson(range, line, filename); - obj[key] = itm; - skipWhitespace(range, line); - enforceJson(!range.empty, "Missing '}' before EOF.", filename, line); - enforceJson(range.front == ',' || range.front == '}', - format("Expected '}' or ',' - got '%s'.", range.front), filename, line); - if (range.front == '}') break; - else range.popFront(); - } - range.popFront(); - ret = obj; - break; - default: - enforceJson(false, format("Expected valid JSON token, got '%s'.", range.take(12)), filename, line); - assert(false); - } - - assert(ret.type != Json.Type.undefined); - version(JsonLineNumbers) ret.line = curline; - return ret; -} - - -unittest { // ensure parseJson works with a generic forward range - static struct R { - const(char)[] text; - - @property char front() { return text[0]; } - @property R save() { return this; } - @property bool empty() const { return text.length == 0; } - void popFront() { text = text[1 .. $]; } - } - - auto r = R(`{"i":42, "s": "foo"}`); - auto j = parseJson(r); - assert(j["i"] == 42); - assert(j["s"] == "foo"); -} - - -/** - Parses the given JSON string and returns the corresponding Json object. - - Throws a JSONException if any parsing error occurs. -*/ -Json parseJsonString(string str, string filename = null) -@safe { - auto strcopy = str; - int line = 0; - auto ret = parseJson(strcopy, () @trusted { return &line; } (), filename); - enforceJson(strcopy.strip().length == 0, "Expected end of string after JSON value.", filename, line); - return ret; -} - -@safe unittest { - // These currently don't work at compile time - assert(parseJsonString("17559991181826658461") == Json(BigInt(17559991181826658461UL))); - assert(parseJsonString("99999999999999999999999999") == () @trusted { return Json(BigInt("99999999999999999999999999")); } ()); - auto json = parseJsonString(`{"hey": "This is @à test éhééhhéhéé !%/??*&?\ud83d\udcec"}`); - assert(json.toPrettyString() == parseJsonString(json.toPrettyString()).toPrettyString()); - - bool test() { - assert(parseJsonString("null") == Json(null)); - assert(parseJsonString("true") == Json(true)); - assert(parseJsonString("false") == Json(false)); - assert(parseJsonString("1") == Json(1)); - assert(parseJsonString("2.0") == Json(2.0)); - assert(parseJsonString("\"test\"") == Json("test")); - assert(parseJsonString("[1, 2, 3]") == Json([Json(1), Json(2), Json(3)])); - assert(parseJsonString("{\"a\": 1}") == Json(["a": Json(1)])); - assert(parseJsonString(`"\\\/\b\f\n\r\t\u1234"`).get!string == "\\/\b\f\n\r\t\u1234"); - - return true; - } - - // Run at compile time and runtime - assert(test()); - static assert(test()); -} - -@safe nothrow unittest { - bool test() { - try parseJsonString(" \t\n "); - catch (Exception e) assert(e.msg.endsWith("JSON string contains only whitespaces.")); - try parseJsonString(`{"a": 1`); - catch (Exception e) assert(e.msg.endsWith("Missing '}' before EOF.")); - try parseJsonString(`{"a": 1 x`); - catch (Exception e) assert(e.msg.endsWith("Expected '}' or ',' - got 'x'.")); - try parseJsonString(`[1`); - catch (Exception e) assert(e.msg.endsWith("Missing ']' before EOF.")); - try parseJsonString(`[1 x`); - catch (Exception e) assert(e.msg.endsWith("Expected ']' or ',' - got 'x'.")); - - return true; - } - - // Run at compile time and runtime - assert(test()); - static assert(test()); -} - -/** - Serializes the given value to JSON. - - The following types of values are supported: - - $(DL - $(DT `Json`) $(DD Used as-is) - $(DT `null`) $(DD Converted to `Json.Type.null_`) - $(DT `bool`) $(DD Converted to `Json.Type.bool_`) - $(DT `float`, `double`) $(DD Converted to `Json.Type.float_`) - $(DT `short`, `ushort`, `int`, `uint`, `long`, `ulong`) $(DD Converted to `Json.Type.int_`) - $(DT `BigInt`) $(DD Converted to `Json.Type.bigInt`) - $(DT `string`) $(DD Converted to `Json.Type.string`) - $(DT `T[]`) $(DD Converted to `Json.Type.array`) - $(DT `T[string]`) $(DD Converted to `Json.Type.object`) - $(DT `struct`) $(DD Converted to `Json.Type.object`) - $(DT `class`) $(DD Converted to `Json.Type.object` or `Json.Type.null_`) - ) - - All entries of an array or an associative array, as well as all R/W properties and - all public fields of a struct/class are recursively serialized using the same rules. - - Fields ending with an underscore will have the last underscore stripped in the - serialized output. This makes it possible to use fields with D keywords as their name - by simply appending an underscore. - - The following methods can be used to customize the serialization of structs/classes: - - --- - Json toJson() const; - static T fromJson(Json src); - - string toString() const; - static T fromString(string src); - --- - - The methods will have to be defined in pairs. The first pair that is implemented by - the type will be used for serialization (i.e. `toJson` overrides `toString`). - - See_Also: `deserializeJson`, `vibe.data.serialization` -*/ -Json serializeToJson(T)(auto ref T value) -{ - return serialize!JsonSerializer(value); -} -/// ditto -void serializeToJson(R, T)(R destination, auto ref T value) - if (isOutputRange!(R, char) || isOutputRange!(R, ubyte)) -{ - serialize!(JsonStringSerializer!R)(value, destination); -} -/// ditto -string serializeToJsonString(T)(auto ref T value) -{ - auto ret = appender!string; - serializeToJson(ret, value); - return ret.data; -} - -/// -@safe unittest { - struct Foo { - int number; - string str; - } - - Foo f; - - f.number = 12; - f.str = "hello"; - - string json = serializeToJsonString(f); - assert(json == `{"number":12,"str":"hello"}`); - Json jsonval = serializeToJson(f); - assert(jsonval.type == Json.Type.object); - assert(jsonval["number"] == Json(12)); - assert(jsonval["str"] == Json("hello")); -} - - -/** - Serializes the given value to a pretty printed JSON string. - - See_also: `serializeToJson`, `vibe.data.serialization` -*/ -void serializeToPrettyJson(R, T)(R destination, auto ref T value) - if (isOutputRange!(R, char) || isOutputRange!(R, ubyte)) -{ - serialize!(JsonStringSerializer!(R, true))(value, destination); -} -/// ditto -string serializeToPrettyJson(T)(auto ref T value) -{ - auto ret = appender!string; - serializeToPrettyJson(ret, value); - return ret.data; -} - -/// -@safe unittest { - struct Foo { - int number; - string str; - } - - Foo f; - f.number = 12; - f.str = "hello"; - - string json = serializeToPrettyJson(f); - assert(json == -`{ - "number": 12, - "str": "hello" -}`); -} - - -/** - Deserializes a JSON value into the destination variable. - - The same types as for `serializeToJson()` are supported and handled inversely. - - See_Also: `serializeToJson`, `serializeToJsonString`, `vibe.data.serialization` -*/ -void deserializeJson(T)(ref T dst, Json src) -{ - dst = deserializeJson!T(src); -} -/// ditto -T deserializeJson(T)(Json src) -{ - return deserialize!(JsonSerializer, T)(src); -} -/// ditto -T deserializeJson(T, R)(R input) - if (!is(R == Json) && isForwardRange!R) -{ - return deserialize!(JsonStringSerializer!R, T)(input); -} - -/// -@safe unittest { - struct Foo { - int number; - string str; - } - Foo f = deserializeJson!Foo(`{"number": 12, "str": "hello"}`); - assert(f.number == 12); - assert(f.str == "hello"); -} - -@safe unittest { - import std.stdio; - enum Foo : string { k = "test" } - enum Boo : int { l = 5 } - static struct S { float a; double b; bool c; int d; string e; byte f; ubyte g; long h; ulong i; float[] j; Foo k; Boo l; } - immutable S t = {1.5, -3.0, true, int.min, "Test", -128, 255, long.min, ulong.max, [1.1, 1.2, 1.3], Foo.k, Boo.l}; - S u; - deserializeJson(u, serializeToJson(t)); - assert(t.a == u.a); - assert(t.b == u.b); - assert(t.c == u.c); - assert(t.d == u.d); - assert(t.e == u.e); - assert(t.f == u.f); - assert(t.g == u.g); - assert(t.h == u.h); - assert(t.i == u.i); - assert(t.j == u.j); - assert(t.k == u.k); - assert(t.l == u.l); -} - -@safe unittest -{ - assert(uint.max == serializeToJson(uint.max).deserializeJson!uint); - assert(ulong.max == serializeToJson(ulong.max).deserializeJson!ulong); -} - -@safe unittest { - static struct A { int value; static A fromJson(Json val) @safe { return A(val.get!int); } Json toJson() const @safe { return Json(value); } } - static struct C { int value; static C fromString(string val) @safe { return C(val.to!int); } string toString() const @safe { return value.to!string; } } - static struct D { int value; } - - assert(serializeToJson(const A(123)) == Json(123)); - assert(serializeToJson(A(123)) == Json(123)); - assert(serializeToJson(const C(123)) == Json("123")); - assert(serializeToJson(C(123)) == Json("123")); - assert(serializeToJson(const D(123)) == serializeToJson(["value": 123])); - assert(serializeToJson(D(123)) == serializeToJson(["value": 123])); -} - -@safe unittest { - auto d = Date(2001,1,1); - deserializeJson(d, serializeToJson(Date.init)); - assert(d == Date.init); - deserializeJson(d, serializeToJson(Date(2001,1,1))); - assert(d == Date(2001,1,1)); - struct S { immutable(int)[] x; } - S s; - deserializeJson(s, serializeToJson(S([1,2,3]))); - assert(s == S([1,2,3])); - struct T { - @optional S s; - @optional int i; - @optional float f_; // underscore strip feature - @optional double d; - @optional string str; - } - auto t = T(S([1,2,3])); - deserializeJson(t, parseJsonString(`{ "s" : null, "i" : null, "f" : null, "d" : null, "str" : null }`)); - assert(text(t) == text(T())); -} - -@safe unittest { - static class C { - @safe: - int a; - private int _b; - @property int b() const { return _b; } - @property void b(int v) { _b = v; } - - @property int test() const { return 10; } - - void test2() {} - } - C c = new C; - c.a = 1; - c.b = 2; - - C d; - deserializeJson(d, serializeToJson(c)); - assert(c.a == d.a); - assert(c.b == d.b); -} - -@safe unittest { - static struct C { @safe: int value; static C fromString(string val) { return C(val.to!int); } string toString() const { return value.to!string; } } - enum Color { Red, Green, Blue } - { - static class T { - @safe: - string[Color] enumIndexedMap; - string[C] stringableIndexedMap; - this() { - enumIndexedMap = [ Color.Red : "magenta", Color.Blue : "deep blue" ]; - stringableIndexedMap = [ C(42) : "forty-two" ]; - } - } - - T original = new T; - original.enumIndexedMap[Color.Green] = "olive"; - T other; - deserializeJson(other, serializeToJson(original)); - assert(serializeToJson(other) == serializeToJson(original)); - } - { - static struct S { - string[Color] enumIndexedMap; - string[C] stringableIndexedMap; - } - - S *original = new S; - original.enumIndexedMap = [ Color.Red : "magenta", Color.Blue : "deep blue" ]; - original.enumIndexedMap[Color.Green] = "olive"; - original.stringableIndexedMap = [ C(42) : "forty-two" ]; - S other; - deserializeJson(other, serializeToJson(original)); - assert(serializeToJson(other) == serializeToJson(original)); - } -} - -@safe unittest { - import std.typecons : Nullable; - - struct S { Nullable!int a, b; } - S s; - s.a = 2; - - auto j = serializeToJson(s); - assert(j["a"].type == Json.Type.int_); - assert(j["b"].type == Json.Type.null_); - - auto t = deserializeJson!S(j); - assert(!t.a.isNull() && t.a == 2); - assert(t.b.isNull()); -} - -@safe unittest { // #840 - int[2][2] nestedArray = 1; - assert(nestedArray.serializeToJson.deserializeJson!(typeof(nestedArray)) == nestedArray); -} - -@safe unittest { // #1109 - static class C { - @safe: - int mem; - this(int m) { mem = m; } - static C fromJson(Json j) { return new C(j.get!int-1); } - Json toJson() const { return Json(mem+1); } - } - const c = new C(13); - assert(serializeToJson(c) == Json(14)); - assert(deserializeJson!C(Json(14)).mem == 13); -} - -@safe unittest { // const and mutable json - Json j = Json(1); - const k = Json(2); - assert(serializeToJson(j) == Json(1)); - assert(serializeToJson(k) == Json(2)); -} - -@safe unittest { // issue #1660 - deserialize AA whose key type is string-based enum - enum Foo: string - { - Bar = "bar", - Buzz = "buzz" - } - - struct S { - int[Foo] f; - } - - const s = S([Foo.Bar: 2000]); - assert(serializeToJson(s)["f"] == Json([Foo.Bar: Json(2000)])); - - auto j = Json.emptyObject; - j["f"] = [Foo.Bar: Json(2000)]; - assert(deserializeJson!S(j).f == [Foo.Bar: 2000]); -} - -@safe unittest { - struct V { - UUID v; - } - - const u = UUID("318d7a61-e41b-494e-90d3-0a99f5531bfe"); - const s = `{"v":"318d7a61-e41b-494e-90d3-0a99f5531bfe"}`; - auto j = Json(["v": Json(u)]); - - const v = V(u); - - assert(serializeToJson(v) == j); - - j = Json.emptyObject; - j["v"] = u; - assert(deserializeJson!V(j).v == u); - - assert(serializeToJsonString(v) == s); - assert(deserializeJson!V(s).v == u); -} - -/** - Serializer for a plain Json representation. - - See_Also: vibe.data.serialization.serialize, vibe.data.serialization.deserialize, serializeToJson, deserializeJson -*/ -struct JsonSerializer { - template isJsonBasicType(T) { enum isJsonBasicType = std.traits.isNumeric!T || isBoolean!T || isSomeString!T || is(T == typeof(null)) || is(Unqual!T == UUID) || isJsonSerializable!T; } - - template isSupportedValueType(T) { enum isSupportedValueType = isJsonBasicType!T || is(Unqual!T == Json) || is(Unqual!T == JSONValue); } - - private { - Json m_current; - Json[] m_compositeStack; - } - - this(Json data) @safe { m_current = data; } - - @disable this(this); - - // - // serialization - // - Json getSerializedResult() @safe { return m_current; } - void beginWriteDictionary(Traits)() { m_compositeStack ~= Json.emptyObject; } - void endWriteDictionary(Traits)() { m_current = m_compositeStack[$-1]; m_compositeStack.length--; } - void beginWriteDictionaryEntry(Traits)(string name) {} - void endWriteDictionaryEntry(Traits)(string name) { m_compositeStack[$-1][name] = m_current; } - - void beginWriteArray(Traits)(size_t) { m_compositeStack ~= Json.emptyArray; } - void endWriteArray(Traits)() { m_current = m_compositeStack[$-1]; m_compositeStack.length--; } - void beginWriteArrayEntry(Traits)(size_t) {} - void endWriteArrayEntry(Traits)(size_t) { m_compositeStack[$-1].appendArrayElement(m_current); } - - void writeValue(Traits, T)(auto ref T value) - if (!is(Unqual!T == Json)) - { - alias UT = Unqual!T; - static if (is(UT == JSONValue)) { - m_current = Json(value); - } else static if (isJsonSerializable!UT) { - static if (!__traits(compiles, () @safe { return value.toJson(); } ())) - pragma(msg, "Non-@safe toJson/fromJson methods are deprecated - annotate "~UT.stringof~".toJson() with @safe."); - m_current = () @trusted { return value.toJson(); } (); - } else static if (isSomeString!T && !is(UT == string)) { - writeValue!Traits(value.to!string); - } else m_current = Json(value); - } - - void writeValue(Traits, T)(auto ref T value) if (is(T == Json)) { m_current = value; } - void writeValue(Traits, T)(auto ref T value) if (!is(T == Json) && is(T : const(Json))) { m_current = value.clone; } - - // - // deserialization - // - void readDictionary(Traits)(scope void delegate(string) @safe field_handler) - { - enforceJson(m_current.type == Json.Type.object, "Expected JSON object, got "~m_current.type.enumToString); - auto old = m_current; - foreach (string key, value; m_current.get!(Json[string])) { - if (value.type == Json.Type.undefined) { - continue; - } - - m_current = value; - field_handler(key); - } - m_current = old; - } - - void beginReadDictionaryEntry(Traits)(string name) {} - void endReadDictionaryEntry(Traits)(string name) {} - - void readArray(Traits)(scope void delegate(size_t) @safe size_callback, scope void delegate() @safe entry_callback) - { - enforceJson(m_current.type == Json.Type.array, "Expected JSON array, got "~m_current.type.enumToString); - auto old = m_current; - size_callback(m_current.length); - foreach (ent; old.get!(Json[])) { - m_current = ent; - entry_callback(); - } - m_current = old; - } - - void beginReadArrayEntry(Traits)(size_t index) {} - void endReadArrayEntry(Traits)(size_t index) {} - - T readValue(Traits, T)() - @safe { - static if (is(T == Json)) return m_current; - else static if (is(T == JSONValue)) return cast(JSONValue)m_current; - else static if (isJsonSerializable!T) { - static if (!__traits(compiles, () @safe { return T.fromJson(m_current); } ())) - pragma(msg, "Non-@safe toJson/fromJson methods are deprecated - annotate "~T.stringof~".fromJson() with @safe."); - return () @trusted { return T.fromJson(m_current); } (); - } else static if (is(T == float) || is(T == double)) { - switch (m_current.type) { - default: return cast(T)m_current.get!long; - case Json.Type.null_: goto case; - case Json.Type.undefined: return T.nan; - case Json.Type.float_: return cast(T)m_current.get!double; - case Json.Type.bigInt: return cast(T)m_current.bigIntToLong(); - } - } else static if (is(T == const(char)[])) { - return readValue!(Traits, string); - } else static if (isSomeString!T && !is(T == string)) { - return readValue!(Traits, string).to!T; - } else static if (is(T == string)) { - if (m_current.type == Json.Type.array) { // legacy support for pre-#2150 serialization results - return () @trusted { // appender - auto r = appender!string; - foreach (s; m_current.get!(Json[])) - r.put(s.get!string()); - return r.data; - } (); - } else return m_current.get!T(); - } else return m_current.get!T(); - } - - bool tryReadNull(Traits)() { return m_current.type == Json.Type.null_; } -} - -unittest { - struct T { - @optional string a; - } - - auto obj = Json.emptyObject; - obj["a"] = Json.undefined; - assert(obj.deserializeJson!T.a == ""); -} - -unittest { - class C { this(Json j) {foo = j;} Json foo; } - const C c = new C(Json(42)); - assert(serializeToJson(c)["foo"].get!int == 42); -} - -/** - Serializer for a range based plain JSON string representation. - - See_Also: vibe.data.serialization.serialize, vibe.data.serialization.deserialize, serializeToJson, deserializeJson -*/ -struct JsonStringSerializer(R, bool pretty = false) - if (isInputRange!R || isOutputRange!(R, char)) -{ - private { - R m_range; - size_t m_level = 0; - } - - template isJsonBasicType(T) { enum isJsonBasicType = std.traits.isNumeric!T || isBoolean!T || isSomeString!T || is(T == typeof(null)) || is(Unqual!T == UUID) || isJsonSerializable!T; } - - template isSupportedValueType(T) { enum isSupportedValueType = isJsonBasicType!(Unqual!T) || is(Unqual!T == Json) || is(Unqual!T == JSONValue); } - - this(R range) - { - m_range = range; - } - - @disable this(this); - - // - // serialization - // - static if (isOutputRange!(R, char)) { - private { - bool m_firstInComposite; - } - - void getSerializedResult() {} - - void beginWriteDictionary(Traits)() { startComposite(); m_range.put('{'); } - void endWriteDictionary(Traits)() { endComposite(); m_range.put("}"); } - void beginWriteDictionaryEntry(Traits)(string name) - { - startCompositeEntry(); - m_range.put('"'); - m_range.jsonEscape(name); - static if (pretty) m_range.put(`": `); - else m_range.put(`":`); - } - void endWriteDictionaryEntry(Traits)(string name) {} - - void beginWriteArray(Traits)(size_t) { startComposite(); m_range.put('['); } - void endWriteArray(Traits)() { endComposite(); m_range.put(']'); } - void beginWriteArrayEntry(Traits)(size_t) { startCompositeEntry(); } - void endWriteArrayEntry(Traits)(size_t) {} - - void writeValue(Traits, T)(in T value) - { - alias UT = Unqual!T; - static if (is(T == typeof(null))) m_range.put("null"); - else static if (is(UT == bool)) m_range.put(value ? "true" : "false"); - else static if (is(UT : long)) m_range.formattedWriteFixed!32("%s", value); - else static if (is(UT == BigInt)) () @trusted { - static if (__VERSION__ < 2093) - value.toString((scope s) { m_range.put(s); }, "%d"); - else value.toString(m_range, "%d"); - } (); - else static if (is(UT : real)) value == value ? m_range.formattedWriteFixed!32("%.16g", value) : m_range.put("null"); - else static if (is(UT : const(char)[])) { - m_range.put('"'); - m_range.jsonEscape(value); - m_range.put('"'); - } else static if (isSomeString!T) writeValue!Traits(value.to!string); // TODO: avoid memory allocation - else static if (is(UT == UUID)) writeValue!Traits(value.toString()); - else static if (is(UT == Json)) m_range.writeJsonString(value); - else static if (is(UT == JSONValue)) m_range.writeJsonString(Json(value)); - else static if (isJsonSerializable!UT) { - static if (!__traits(compiles, () @safe { return value.toJson(); } ())) - pragma(msg, "Non-@safe toJson/fromJson methods are deprecated - annotate "~UT.stringof~".toJson() with @safe."); - m_range.writeJsonString!(R, pretty)(() @trusted { return value.toJson(); } (), m_level); - } else static assert(false, "Unsupported type: " ~ UT.stringof); - } - - void writeStringSinkValue(Traits, T)(scope auto ref T value) - { - void sink(scope const(char)[] str) { - m_range.jsonEscape(str); - } - - final class R { - void put(char ch) { put(() @trusted { return (&ch)[0 .. 1]; } ()); } - void put(scope const(char)[] str) { m_range.jsonEscape(str); } - } - - m_range.put('"'); - static if (__traits(compiles, value.toString((scope s) => sink(s)))) { - value.toString((scope s) => sink(s)); - } else { - scope r = new R; - value.toString(r); - } - m_range.put('"'); - } - - private void startComposite() - { - static if (pretty) m_level++; - m_firstInComposite = true; - } - - private void startCompositeEntry() - { - if (!m_firstInComposite) { - m_range.put(','); - } else { - m_firstInComposite = false; - } - static if (pretty) indent(); - } - - private void endComposite() - { - static if (pretty) { - m_level--; - if (!m_firstInComposite) indent(); - } - m_firstInComposite = false; - } - - private void indent() - { - m_range.put('\n'); - foreach (i; 0 .. m_level) m_range.put('\t'); - } - } - - // - // deserialization - // - static if (isInputRange!(R)) { - private { - int m_line = 0; - } - - void readDictionary(Traits)(scope void delegate(string) @safe entry_callback) - { - m_range.skipWhitespace(&m_line); - enforceJson(!m_range.empty && m_range.front == '{', "Expecting object."); - m_range.popFront(); - bool first = true; - while(true) { - m_range.skipWhitespace(&m_line); - enforceJson(!m_range.empty, "Missing '}'."); - if (m_range.front == '}') { - m_range.popFront(); - break; - } else if (!first) { - enforceJson(m_range.front == ',', "Expecting ',' or '}', not '"~m_range.front.to!string~"'."); - m_range.popFront(); - m_range.skipWhitespace(&m_line); - } else first = false; - - auto name = m_range.skipJsonString(&m_line); - - m_range.skipWhitespace(&m_line); - enforceJson(!m_range.empty && m_range.front == ':', "Expecting ':', not '"~m_range.front.to!string~"'."); - m_range.popFront(); - - entry_callback(name); - } - } - - void beginReadDictionaryEntry(Traits)(string name) {} - void endReadDictionaryEntry(Traits)(string name) {} - - void readArray(Traits)(scope void delegate(size_t) @safe size_callback, scope void delegate() @safe entry_callback) - { - m_range.skipWhitespace(&m_line); - enforceJson(!m_range.empty && m_range.front == '[', "Expecting array."); - m_range.popFront(); - bool first = true; - while(true) { - m_range.skipWhitespace(&m_line); - enforceJson(!m_range.empty, "Missing ']'."); - if (m_range.front == ']') { - m_range.popFront(); - break; - } else if (!first) { - enforceJson(m_range.front == ',', "Expecting ',' or ']'."); - m_range.popFront(); - } else first = false; - - entry_callback(); - } - } - - void beginReadArrayEntry(Traits)(size_t index) {} - void endReadArrayEntry(Traits)(size_t index) {} - - T readValue(Traits, T)() - { - m_range.skipWhitespace(&m_line); - static if (is(T == typeof(null))) { enforceJson(m_range.take(4).equal("null"), "Expecting 'null'."); return null; } - else static if (is(T == bool)) { - bool ret = m_range.front == 't'; - string expected = ret ? "true" : "false"; - foreach (ch; expected) { - enforceJson(m_range.front == ch, "Expecting 'true' or 'false'."); - m_range.popFront(); - } - return ret; - } else static if (is(T : long)) { - bool is_float; - bool is_long_overflow; - auto num = m_range.skipNumber(is_float, is_long_overflow); - enforceJson(!is_float, "Expecting integer number."); - enforceJson(!is_long_overflow, num.to!string~" is too big for long."); - return to!T(num); - } else static if (is(T : BigInt)) { - bool is_float; - bool is_long_overflow; - auto num = m_range.skipNumber(is_float, is_long_overflow); - enforceJson(!is_float, "Expecting integer number."); - return BigInt(num); - } else static if (is(T : real)) { - bool is_float; - bool is_long_overflow; - auto num = m_range.skipNumber(is_float, is_long_overflow); - return to!T(num); - } - else static if (is(T == string) || is(T == const(char)[])) { - if (!m_range.empty && m_range.front == '[') { - return () @trusted { // appender - auto ret = appender!string(); - readArray!Traits((sz) {}, () @trusted { - ret.put(m_range.skipJsonString(&m_line)); - }); - return ret.data; - } (); - } else return m_range.skipJsonString(&m_line); - } - else static if (isSomeString!T) return readValue!(Traits, string).to!T; - else static if (is(T == UUID)) return UUID(readValue!(Traits, string)()); - else static if (is(T == Json)) return m_range.parseJson(&m_line); - else static if (is(T == JSONValue)) return cast(JSONValue)m_range.parseJson(&m_line); - else static if (isJsonSerializable!T) { - static if (!__traits(compiles, () @safe { return T.fromJson(Json.init); } ())) - pragma(msg, "Non-@safe toJson/fromJson methods are deprecated - annotate "~T.stringof~".fromJson() with @safe."); - return () @trusted { return T.fromJson(m_range.parseJson(&m_line)); } (); - } else static assert(false, "Unsupported type: " ~ T.stringof); - } - - bool tryReadNull(Traits)() - { - m_range.skipWhitespace(&m_line); - if (m_range.front != 'n') return false; - foreach (ch; "null") { - enforceJson(m_range.front == ch, "Expecting 'null'."); - m_range.popFront(); - } - assert(m_range.empty || m_range.front != 'l'); - return true; - } - - void skipValue() @safe - { - m_range.skipWhitespace(&m_line); - switch(m_range.front) { - case '[': - m_range.popFront(); - bool first = true; - while(true) { - m_range.skipWhitespace(&m_line); - enforceJson(!m_range.empty, "Missing ']'."); - if (m_range.front == ']') { - m_range.popFront(); - break; - } else if (!first) { - enforceJson(m_range.front == ',', "Expecting ',' or ']'."); - m_range.popFront(); - } else first = false; - skipValue(); - } - break; - case '{': - m_range.popFront(); - bool first = true; - while(true) { - m_range.skipWhitespace(&m_line); - enforceJson(!m_range.empty, "Missing '}'."); - if (m_range.front == '}') { - m_range.popFront(); - break; - } else if (!first) { - enforceJson(m_range.front == ',', "Expecting ',' or '}', not '"~m_range.front.to!string~"'."); - m_range.popFront(); - m_range.skipWhitespace(&m_line); - } else first = false; - - m_range.skipJsonString(&m_line); - - m_range.skipWhitespace(&m_line); - enforceJson(!m_range.empty && m_range.front == ':', "Expecting ':', not '"~m_range.front.to!string~"'."); - m_range.popFront(); - - skipValue(); - } - break; - case '"': - m_range.skipJsonString(&m_line); - break; - case '-': - case '0': .. case '9': - bool dummy; // ignore - m_range.skipNumber(dummy, dummy); - break; - case 't': - foreach (ch; "true") { - enforceJson(m_range.front == ch, "Expecting 'true'."); - m_range.popFront(); - } - break; - case 'f': - foreach (ch; "false") { - enforceJson(m_range.front == ch, "Expecting 'false'."); - m_range.popFront(); - } - break; - case 'n': - foreach (ch; "null") { - enforceJson(m_range.front == ch, "Expecting 'null'."); - m_range.popFront(); - } - break; - default: - throw new JSONException("Expected start of object, array, string, number, boolean or null value, got"~m_range.front.to!string); - } - m_range.skipWhitespace(&m_line); - } - } -} - -unittest { - static assert(doesSerializerSupportStringSink!(JsonStringSerializer!(Appender!string))); - - auto app = appender!string; - auto ser = JsonStringSerializer!(Appender!string)(app); - static struct T1 { void toString(scope void delegate(scope const(char)[])) {} } - static struct T2 { void toString(R)(scope ref R dst) { dst.put('f'); dst.put("foo"); } } - - ser.writeStringSinkValue!void(T1.init); - ser.writeStringSinkValue!void(T2.init); -} - -/// Cloning JSON arrays -unittest -{ - Json value = Json([ Json([ Json.emptyArray ]), Json.emptyArray ]).clone; - - assert(value.length == 2); - assert(value[0].length == 1); - assert(value[0][0].length == 0); -} - -unittest -{ - assert(serializeToJsonString(double.nan) == "null"); - assert(serializeToJsonString(Json()) == "null"); - assert(serializeToJsonString(Json(["bar":Json("baz"),"foo":Json()])) == `{"bar":"baz"}`); - - struct Foo{Json bar = Json();} - Foo f; - assert(serializeToJsonString(f) == `{"bar":null}`); -} - -/** - Writes the given JSON object as a JSON string into the destination range. - - This function will convert the given JSON value to a string without adding - any white space between tokens (no newlines, no indentation and no padding). - The output size is thus minimized, at the cost of bad human readability. - - Params: - dst = References the string output range to which the result is written. - json = Specifies the JSON value that is to be stringified. - level = Specifies the base amount of indentation for the output. Indentation is always - done using tab characters. - - See_Also: Json.toString, writePrettyJsonString -*/ -void writeJsonString(R, bool pretty = false)(ref R dst, in Json json, size_t level = 0) -@safe // if( isOutputRange!R && is(ElementEncodingType!R == char) ) -{ - final switch( json.type ){ - case Json.Type.undefined: dst.put("null"); break; - case Json.Type.null_: dst.put("null"); break; - case Json.Type.bool_: dst.put(json.get!bool ? "true" : "false"); break; - case Json.Type.int_: formattedWriteFixed!32(dst, "%d", json.get!long); break; - case Json.Type.bigInt: - () @trusted { - static if (__VERSION__ < 2093) - json.get!BigInt.toString((scope s) { dst.put(s); }, "%d"); - else json.get!BigInt.toString(dst, "%d"); - } (); - break; - case Json.Type.float_: - auto d = json.get!double; - if (d != d) - dst.put("null"); // JSON has no NaN value so set null - else - formattedWriteFixed!32(dst, "%.16g", json.get!double); - break; - case Json.Type.string: - dst.put('\"'); - jsonEscape(dst, json.get!string); - dst.put('\"'); - break; - case Json.Type.array: - dst.put('['); - bool first = true; - foreach (ref const Json e; json.byValue) { - if( !first ) dst.put(","); - first = false; - static if (pretty) { - dst.put('\n'); - foreach (tab; 0 .. level+1) dst.put('\t'); - } - if (e.type == Json.Type.undefined) dst.put("null"); - else writeJsonString!(R, pretty)(dst, e, level+1); - } - static if (pretty) { - if (json.length > 0) { - dst.put('\n'); - foreach (tab; 0 .. level) dst.put('\t'); - } - } - dst.put(']'); - break; - case Json.Type.object: - dst.put('{'); - bool first = true; - foreach (string k, ref const Json e; json.byKeyValue) { - if( e.type == Json.Type.undefined ) continue; - if( !first ) dst.put(','); - first = false; - static if (pretty) { - dst.put('\n'); - foreach (tab; 0 .. level+1) dst.put('\t'); - } - dst.put('\"'); - jsonEscape(dst, k); - dst.put(pretty ? `": ` : `":`); - writeJsonString!(R, pretty)(dst, e, level+1); - } - static if (pretty) { - if (json.length > 0) { - dst.put('\n'); - foreach (tab; 0 .. level) dst.put('\t'); - } - } - dst.put('}'); - break; - } -} - -unittest { - auto a = Json.emptyObject; - a["a"] = Json.emptyArray; - a["b"] = Json.emptyArray; - a["b"] ~= Json(1); - a["b"] ~= Json.emptyObject; - - assert(a.toString() == `{"a":[],"b":[1,{}]}` || a.toString() == `{"b":[1,{}],"a":[]}`); - assert(a.toPrettyString() == -`{ - "a": [], - "b": [ - 1, - {} - ] -}` - || a.toPrettyString() == `{ - "b": [ - 1, - {} - ], - "a": [] -}`); -} - -unittest { // #735 - auto a = Json.emptyArray; - a ~= "a"; - a ~= Json(); - a ~= "b"; - a ~= null; - a ~= "c"; - assert(a.toString() == `["a",null,"b",null,"c"]`); -} - -unittest { - auto a = Json.emptyArray; - a ~= Json(1); - a ~= Json(2); - a ~= Json(3); - a ~= Json(4); - a ~= Json(5); - - auto b = Json(a[0..a.length]); - assert(a == b); - - auto c = Json(a[0..$]); - assert(a == c); - assert(b == c); - - auto d = [Json(1),Json(2),Json(3)]; - assert(d == a[0..a.length-2]); - assert(d == a[0..$-2]); -} - -unittest { - auto j = Json(double.init); - - assert(j.toString == "null"); // A double nan should serialize to null - j = 17.04f; - assert(j.toString == "17.04"); // A proper double should serialize correctly - - double d; - deserializeJson(d, Json.undefined); // Json.undefined should deserialize to nan - assert(d != d); - deserializeJson(d, Json(null)); // Json.undefined should deserialize to nan - assert(d != d); -} -/** - Writes the given JSON object as a prettified JSON string into the destination range. - - The output will contain newlines and indents to make the output human readable. - - Params: - dst = References the string output range to which the result is written. - json = Specifies the JSON value that is to be stringified. - level = Specifies the base amount of indentation for the output. Indentation is always - done using tab characters. - - See_Also: Json.toPrettyString, writeJsonString -*/ -void writePrettyJsonString(R)(ref R dst, in Json json, int level = 0) -// if( isOutputRange!R && is(ElementEncodingType!R == char) ) -{ - writeJsonString!(R, true)(dst, json, level); -} - - -/** - Helper function that escapes all Unicode characters in a JSON string. -*/ -string convertJsonToASCII(string json) -{ - auto ret = appender!string; - jsonEscape!true(ret, json); - return ret.data; -} - - -/// private -private void jsonEscape(bool escape_unicode = false, R)(ref R dst, const(char)[] s) -{ - size_t startPos = 0; - - void putInterval(size_t curPos) - { - if (curPos > startPos) - dst.put(s[startPos..curPos]); - startPos = curPos + 1; - } - - for (size_t pos = 0; pos < s.length; pos++) { - immutable(char) ch = s[pos]; - - switch (ch) { - default: - static if (escape_unicode) { - if (ch <= 0x20 || ch >= 0x80) - { - putInterval(pos); - import std.utf : decode; - int len; - dchar codepoint = decode(s, pos); - /* codepoint is in BMP */ - if(codepoint < 0x10000) - { - dst.formattedWriteFixed!32("\\u%04X", codepoint); - } - /* not in BMP -> construct a UTF-16 surrogate pair */ - else - { - int first, last; - - codepoint -= 0x10000; - first = 0xD800 | ((codepoint & 0xffc00) >> 10); - last = 0xDC00 | (codepoint & 0x003ff); - - dst.formattedWriteFixed!32("\\u%04X\\u%04X", first, last); - } - startPos = pos; - pos -= 1; - } - } - else - { - if (ch < 0x20) - { - putInterval(pos); - dst.formattedWriteFixed!32("\\u%04X", ch); - } - } - break; - case '\\': putInterval(pos); dst.put("\\\\"); break; - case '\r': putInterval(pos); dst.put("\\r"); break; - case '\n': putInterval(pos); dst.put("\\n"); break; - case '\t': putInterval(pos); dst.put("\\t"); break; - case '\"': putInterval(pos); dst.put("\\\""); break; - case '/': - // this avoids the sequence " 0 && s[pos-1] == '<') - { - putInterval(pos); - dst.put("\\/"); - } - break; - } - } - // last interval - putInterval(s.length); -} - -/// private -private string jsonUnescape(R)(ref R range) -{ - auto ret = appender!string(); - while(!range.empty){ - auto ch = range.front; - switch( ch ){ - case '"': return ret.data; - case '\\': - range.popFront(); - enforceJson(!range.empty, "Unterminated string escape sequence."); - switch(range.front){ - default: enforceJson(false, "Invalid string escape sequence."); break; - case '"': ret.put('\"'); range.popFront(); break; - case '\\': ret.put('\\'); range.popFront(); break; - case '/': ret.put('/'); range.popFront(); break; - case 'b': ret.put('\b'); range.popFront(); break; - case 'f': ret.put('\f'); range.popFront(); break; - case 'n': ret.put('\n'); range.popFront(); break; - case 'r': ret.put('\r'); range.popFront(); break; - case 't': ret.put('\t'); range.popFront(); break; - case 'u': - - dchar decode_unicode_escape() { - enforceJson(range.front == 'u'); - range.popFront(); - dchar uch = 0; - foreach( i; 0 .. 4 ){ - uch *= 16; - enforceJson(!range.empty, "Unicode sequence must be '\\uXXXX'."); - auto dc = range.front; - range.popFront(); - - if( dc >= '0' && dc <= '9' ) uch += dc - '0'; - else if( dc >= 'a' && dc <= 'f' ) uch += dc - 'a' + 10; - else if( dc >= 'A' && dc <= 'F' ) uch += dc - 'A' + 10; - else enforceJson(false, "Unicode sequence must be '\\uXXXX'."); - } - return uch; - } - - auto uch = decode_unicode_escape(); - - if(0xD800 <= uch && uch <= 0xDBFF) { - /* surrogate pair */ - range.popFront(); // backslash '\' - auto uch2 = decode_unicode_escape(); - enforceJson(0xDC00 <= uch2 && uch2 <= 0xDFFF, "invalid Unicode"); - { - /* valid second surrogate */ - uch = - ((uch - 0xD800) << 10) + - (uch2 - 0xDC00) + - 0x10000; - } - } - ret.put(uch); - break; - } - break; - default: - ret.put(ch); - range.popFront(); - break; - } - } - return ret.data; -} - -private auto skipNumber(R)(ref R s, out bool is_float, out bool is_long_overflow) @safe - if (isNarrowString!R) -{ - auto r = s.representation; - version (assert) auto rEnd = (() @trusted => r.ptr + r.length - 1)(); - auto res = skipNumber(r, is_float, is_long_overflow); - version (assert) assert(rEnd == (() @trusted => r.ptr + r.length - 1)()); // check nothing taken off the end - s = s[$ - r.length .. $]; - return res.assumeUTF(); -} - -/// private -private auto skipNumber(R)(ref R s, out bool is_float, out bool is_long_overflow) - if (!isNarrowString!R && isForwardRange!R) -{ - auto sOrig = s.save; - size_t idx = 0; - is_float = false; - is_long_overflow = false; - ulong int_part = 0; - if (s.front == '-') { - s.popFront(); ++idx; - } - if (s.front == '0') { - s.popFront(); ++idx; - } - else { - enforceJson(isDigit(s.front), "Digit expected at beginning of number."); - int_part = s.front - '0'; - s.popFront(); ++idx; - while( !s.empty && isDigit(s.front) ) { - if (!is_long_overflow) { - auto dig = s.front - '0'; - if ((long.max / 10) > int_part || ((long.max / 10) == int_part && (long.max % 10) >= dig)) { - int_part *= 10; - int_part += dig; - } - else { - is_long_overflow = true; - } - } - s.popFront(); ++idx; - } - } - - if( !s.empty && s.front == '.' ) { - s.popFront(); ++idx; - is_float = true; - while( !s.empty && isDigit(s.front) ) { - s.popFront(); ++idx; - } - } - - if( !s.empty && (s.front == 'e' || s.front == 'E') ) { - s.popFront(); ++idx; - is_float = true; - if( !s.empty && (s.front == '+' || s.front == '-') ) { - s.popFront(); ++idx; - } - enforceJson( !s.empty && isDigit(s.front), "Expected exponent." ~ sOrig.takeExactly(idx).to!string); - s.popFront(); ++idx; - while( !s.empty && isDigit(s.front) ) { - s.popFront(); ++idx; - } - } - - return sOrig.takeExactly(idx); -} - -unittest -{ - import std.meta : AliasSeq; - // test for string and for a simple range - foreach (foo; AliasSeq!(to!string, map!"a")) { - auto test_1 = foo("9223372036854775806"); // lower then long.max - auto test_2 = foo("9223372036854775807"); // long.max - auto test_3 = foo("9223372036854775808"); // greater then long.max - bool is_float; - bool is_long_overflow; - test_1.skipNumber(is_float, is_long_overflow); - assert(!is_long_overflow); - test_2.skipNumber(is_float, is_long_overflow); - assert(!is_long_overflow); - test_3.skipNumber(is_float, is_long_overflow); - assert(is_long_overflow); - } -} - -/// private -private string skipJsonString(R)(ref R s, int* line = null) -{ - // TODO: count or disallow any newlines inside of the string - enforceJson(!s.empty && s.front == '"', "Expected '\"' to start string."); - s.popFront(); - string ret = jsonUnescape(s); - enforceJson(!s.empty && s.front == '"', "Expected '\"' to terminate string."); - s.popFront(); - return ret; -} - -/// private -private void skipWhitespace(R)(ref R s, int* line = null) -{ - while (!s.empty) { - switch (s.front) { - default: return; - case ' ', '\t': s.popFront(); break; - case '\n': - s.popFront(); - if (!s.empty && s.front == '\r') s.popFront(); - if (line) (*line)++; - break; - case '\r': - s.popFront(); - if (!s.empty && s.front == '\n') s.popFront(); - if (line) (*line)++; - break; - } - } -} - -private bool isDigit(dchar ch) @safe nothrow pure { return ch >= '0' && ch <= '9'; } - -private string underscoreStrip(string field_name) -@safe nothrow pure { - if( field_name.length < 1 || field_name[$-1] != '_' ) return field_name; - else return field_name[0 .. $-1]; -} - -/// private -package template isJsonSerializable(T) { enum isJsonSerializable = is(typeof(T.init.toJson()) : Json) && is(typeof(T.fromJson(Json())) : T); } - -private void enforceJson(string file = __FILE__, size_t line = __LINE__)(bool cond, lazy string message = "JSON exception") -{ - import std.exception : enforce; - enforce!JSONException(cond, message, file, line); -} - -private void enforceJson(string file = __FILE__, size_t line = __LINE__)(bool cond, lazy string message, string err_file, int err_line) -{ - import std.exception : enforce; - enforce!JSONException(cond, format("%s(%s): Error: %s", err_file, err_line+1, message), file, line); -} - -private void enforceJson(string file = __FILE__, size_t line = __LINE__)(bool cond, lazy string message, string err_file, int* err_line) -{ - enforceJson!(file, line)(cond, message, err_file, err_line ? *err_line : -1); -} - -private auto trustedRange(R)(R range) -{ - static struct Rng { - private R range; - @property bool empty() @trusted { return range.empty; } - @property auto front() @trusted { return range.front; } - void popFront() @trusted { range.popFront(); } - } - return Rng(range); -} - -// test for vibe.utils.DictionaryList -@safe unittest { - import vibe.container.dictionarylist; - - static assert(isCustomSerializable!(DictionaryList!int)); - - DictionaryList!(int, false) b; - b.addField("a", 1); - b.addField("A", 2); - auto app = appender!string(); - serializeToJson(app, b); - assert(app.data == `[{"key":"a","value":1},{"key":"A","value":2}]`, app.data); - - DictionaryList!(int, true, 2) c; - c.addField("a", 1); - c.addField("b", 2); - c.addField("a", 3); - c.remove("b"); - auto appc = appender!string(); - serializeToJson(appc, c); - assert(appc.data == `[{"key":"a","value":1},{"key":"a","value":3}]`, appc.data); -} - -// make sure Json is usable for CTFE -@safe unittest { - static assert(is(typeof({ - struct Test { - Json object_ = Json.emptyObject; - Json array = Json.emptyArray; - } - })), "CTFE for Json type failed."); - - static Json test() { - Json j; - j = Json(42); - j = Json([Json(true)]); - j = Json(["foo": Json(null)]); - j = Json("foo"); - return j; - } - enum j = test(); - static assert(j == Json("foo")); -} - -@safe unittest { // XSS prevention - assert(Json("some/path").toString() == `"<\/script>some/path"`); - assert(serializeToJsonString("some/path") == `"<\/script>some/path"`); -} - -@system unittest { // Recursive structures - static struct Bar { Bar[] foos; int i; } - auto b = deserializeJson!Bar(`{"i":1,"foos":[{"foos":[],"i":2}]}`); - assert(b.i == 1); - assert(b.foos.length == 1); - assert(b.foos[0].i == 2); - assert(b.foos[0].foos.length == 0); -} - -@safe unittest { // Json <-> std.json.JSONValue - auto astr = `{ - "null": null, - "string": "Hello", - "integer": 123456, - "uinteger": 18446744073709551614, - "float": 12.34, - "object": { "hello": "world" }, - "array": [1, 2, "string"], - "true": true, - "false": false - }`; - auto a = parseJsonString(astr); - - // test JSONValue -> Json conversion - assert(Json(cast(JSONValue)a) == a); - - // test Json -> JSONValue conversion - auto v = cast(JSONValue)a; - assert(deserializeJson!JSONValue(serializeToJson(v)) == v); - - // test JSON strint <-> JSONValue serialization - assert(deserializeJson!JSONValue(astr) == v); - assert(parseJsonString(serializeToJsonString(v)) == a); - - // test using std.conv for the conversion - import std.conv : to; - assert(a.to!JSONValue.to!Json == a); - assert(to!Json(to!JSONValue(a)) == a); -} - -@safe unittest { // issue #2150 - serialization of const/mutable strings + wide character strings - assert(serializeToJson(cast(const(char)[])"foo") == Json("foo")); - assert(serializeToJson("foo".dup) == Json("foo")); - assert(deserializeJson!string(Json("foo")) == "foo"); - assert(deserializeJson!string(Json([Json("f"), Json("o"), Json("o")])) == "foo"); - assert(serializeToJsonString(cast(const(char)[])"foo") == "\"foo\""); - assert(deserializeJson!string("\"foo\"") == "foo"); - - assert(serializeToJson(cast(const(wchar)[])"foo"w) == Json("foo")); - assert(serializeToJson("foo"w.dup) == Json("foo")); - assert(deserializeJson!wstring(Json("foo")) == "foo"); - assert(deserializeJson!wstring(Json([Json("f"), Json("o"), Json("o")])) == "foo"); - assert(serializeToJsonString(cast(const(wchar)[])"foo"w) == "\"foo\""); - assert(deserializeJson!wstring("\"foo\"") == "foo"); - - assert(serializeToJson(cast(const(dchar)[])"foo"d) == Json("foo")); - assert(serializeToJson("foo"d.dup) == Json("foo")); - assert(deserializeJson!dstring(Json("foo")) == "foo"); - assert(deserializeJson!dstring(Json([Json("f"), Json("o"), Json("o")])) == "foo"); - assert(serializeToJsonString(cast(const(dchar)[])"foo"d) == "\"foo\""); - assert(deserializeJson!dstring("\"foo\"") == "foo"); -} - - -unittest { // issue #1647 - JSON deserializeJson throws exception on unknown input fields - struct S { - string foo; - } - S expected = S("bar"); - assert(deserializeJson!S(`{"foo":"bar","baz":"bam"}`) == expected); -} diff --git a/data/vibe/data/serialization.d b/data/vibe/data/serialization.d deleted file mode 100644 index 5d14ed5df6..0000000000 --- a/data/vibe/data/serialization.d +++ /dev/null @@ -1,2263 +0,0 @@ -/** - Generic serialization framework. - - This module provides general means for implementing (de-)serialization with - a standardized behavior. - - Supported_types: - The following rules are applied in order when serializing or - deserializing a certain type: - - $(OL - $(LI An `enum` type is serialized as its raw value, except if - `@byName` is used, in which case the name of the enum value - is serialized.) - $(LI Any type that is specifically supported by the serializer - is directly serialized. For example, the BSON serializer - supports `BsonObjectID` directly.) - $(LI Arrays and tuples (`std.typecons.Tuple`) are serialized - using the array serialization functions where each element is - serialized again according to these rules.) - $(LI Associative arrays are serialized similar to arrays. The key - type of the AA must satisfy the `isStringSerializable` trait - and will always be serialized as a string.) - $(LI Any `Nullable!T` will be serialized as either `null`, or - as the contained value (subject to these rules again).) - $(LI Any `Typedef!T` will be serialized as if it were just `T`.) - $(LI Any `BitFlags!T` value will be serialized as `T[]`) - $(LI Types satisfying the `isPolicySerializable` trait for the - supplied `Policy` will be serialized as the value returned - by the policy `toRepresentation` function (again subject to - these rules).) - $(LI Types satisfying the `isCustomSerializable` trait will be - serialized as the value returned by their `toRepresentation` - method (again subject to these rules).) - $(LI Types satisfying the `isISOExtStringSerializable` trait will be - serialized as a string, as returned by their `toISOExtString` - method. This causes types such as `SysTime` to be serialized - as strings.) - $(LI Types satisfying the `isStringSinkSerializable` trait will be - serialized as a string using the `toString(sink)` method. `sink` - can either be a delegate that takes a `char` array argument, or - an output range of `char`.) - $(LI Types satisfying the `isStringSerializable` trait will be - serialized as a string, as returned by their `toString` - method.) - $(LI Struct and class types by default will be serialized as - associative arrays, where the key is the name of the - corresponding field (can be overridden using the `@name` - attribute). If the struct/class is annotated with `@asArray`, - it will instead be serialized as a flat array of values in the - order of declaration. Null class references will be serialized - as `null`.) - $(LI Pointer types will be serialized as either `null`, or as - the value they point to.) - $(LI Built-in integers and floating point values, as well as - boolean values will be converted to strings, if the serializer - doesn't support them directly.) - ) - - Note that no aliasing detection is performed, so that pointers, class - references and arrays referencing the same memory will be serialized - as multiple copies. When in turn deserializing the data, they will also - end up as separate copies in memory. - - Field_names: - By default, the field name of the serialized D type (for `struct` and - `class` aggregates) is represented as-is in the serialized result. To - circumvent name clashes with D's keywords, a single trailing underscore of - any field name is stipped, so that a field name of `version_` results in - just `"version"` as the serialized value. Names can also be freely - customized using the `@name` annotation. - - Associative array keys are always represented using their direct string - representation. - - Serializer_implementation: - Serializers are implemented in terms of a struct with template methods that - get called by the serialization framework: - - --- - struct ExampleSerializer { - enum isSupportedValueType(T) = is(T == string) || is(T == typeof(null)); - - // serialization - auto getSerializedResult(); - void beginWriteDocument(TypeTraits)(); - void endWriteDocument(TypeTraits)(); - void beginWriteDictionary(TypeTraits)(size_t length); [OR] void beginWriteDictionary(TypeTraits)(); - void endWriteDictionary(TypeTraits)(); - void beginWriteDictionaryEntry(ElementTypeTraits)(string name); - void endWriteDictionaryEntry(ElementTypeTraits)(string name); - void beginWriteArray(TypeTraits)(size_t length); - void endWriteArray(TypeTraits)(); - void beginWriteArrayEntry(ElementTypeTraits)(size_t index); - void endWriteArrayEntry(ElementTypeTraits)(size_t index); - void writeValue(TypeTraits, T)(T value); - - // deserialization - - void readDictionary(TypeTraits)(scope void delegate(string) entry_callback); - void beginReadDictionaryEntry(ElementTypeTraits)(string); - void endReadDictionaryEntry(ElementTypeTraits)(string); - void readArray(TypeTraits)(scope void delegate(size_t) size_callback, scope void delegate() entry_callback); - void beginReadArrayEntry(ElementTypeTraits)(size_t index); - void endReadArrayEntry(ElementTypeTraits)(size_t index); - T readValue(TypeTraits, T)(); - bool tryReadNull(TypeTraits)(); - - // skipValue() is optional. It will be called by the entry_callback in readDictionary - // whenever the key passed to the entry_callback cannot be found. - void skipValue(); - } - --- - - The `TypeTraits` type passed to the individual methods has the following members: - $(UL - $(LI `Type`: The original type of the field to serialize) - $(LI `Attributes`: User defined attributes attached to the field) - $(LI `Policy`: An alias to the policy used for the serialization process) - ) - - `ElementTypeTraits` have the following additional members: - $(UL - $(LI `ContainerType`: The original type of the enclosing container type) - $(LI `ContainerAttributes`: User defined attributes attached to the enclosing container) - ) - - Copyright: © 2013-2016 rejectedsoftware e.K. - License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. - Authors: Sönke Ludwig -*/ -module vibe.data.serialization; - -import vibe.internal.meta.traits; -import vibe.internal.meta.uda; - -import std.array : Appender, appender; -import std.conv : ConvException, to; -import std.exception : enforce; -import std.range.primitives : ElementType, isInputRange; -import std.traits; -import std.typetuple; - - -/** - Serializes a value with the given serializer. - - The serializer must have a value result for the first form - to work. Otherwise, use the range based form. - - See_Also: `vibe.data.json.JsonSerializer`, `vibe.data.json.JsonStringSerializer`, `vibe.data.bson.BsonSerializer` -*/ -auto serialize(Serializer, T, ARGS...)(auto ref T value, ARGS args) -{ - auto serializer = Serializer(args); - serialize(serializer, value); - return serializer.getSerializedResult(); -} -/// ditto -void serialize(Serializer, T)(ref Serializer serializer, auto ref T value) -{ - serializeWithPolicy!(Serializer, DefaultPolicy)(serializer, value); -} - -/** Note that there is a convenience function `vibe.data.json.serializeToJson` - that can be used instead of manually invoking `serialize`. -*/ -unittest { - import vibe.data.json; - - struct Test { - int value; - string text; - } - - Test test; - test.value = 12; - test.text = "Hello"; - - Json serialized = serialize!JsonSerializer(test); - assert(serialized["value"].get!int == 12); - assert(serialized["text"].get!string == "Hello"); -} - -unittest { - import vibe.data.json; - - // Make sure that immutable(char[]) works just like string - // (i.e., immutable(char)[]). - immutable key = "answer"; - auto ints = [key: 42]; - auto serialized = serialize!JsonSerializer(ints); - assert(serialized[key].get!int == 42); -} - -/** - Serializes a value with the given serializer, representing values according to `Policy` when possible. - - The serializer must have a value result for the first form - to work. Otherwise, use the range based form. - - See_Also: `vibe.data.json.JsonSerializer`, `vibe.data.json.JsonStringSerializer`, `vibe.data.bson.BsonSerializer` -*/ -auto serializeWithPolicy(Serializer, alias Policy, T, ARGS...)(auto ref T value, ARGS args) -{ - auto serializer = Serializer(args); - serializeWithPolicy!(Serializer, Policy)(serializer, value); - return serializer.getSerializedResult(); -} -/// ditto -void serializeWithPolicy(Serializer, alias Policy, T)(ref Serializer serializer, auto ref T value) -{ - static if (is(typeof(serializer.beginWriteDocument!T()))) - serializer.beginWriteDocument!T(); - serializeValueImpl!(Serializer, Policy).serializeValue!T(serializer, value); - static if (is(typeof(serializer.endWriteDocument!T()))) - serializer.endWriteDocument!T(); -} -/// -version (unittest) -{ -} - -/// -unittest { - import vibe.data.json; - - template SizePol(T) - if (__traits(allMembers, T) == TypeTuple!("x", "y")) - { - import std.conv; - import std.array; - - static string toRepresentation(T value) @safe { - return to!string(value.x) ~ "x" ~ to!string(value.y); - } - - static T fromRepresentation(string value) { - string[] fields = value.split('x'); - alias fieldT = typeof(T.x); - auto x = to!fieldT(fields[0]); - auto y = to!fieldT(fields[1]); - return T(x, y); - } - } - - static struct SizeI { - int x; - int y; - } - SizeI sizeI = SizeI(1,2); - Json serializedI = serializeWithPolicy!(JsonSerializer, SizePol)(sizeI); - assert(serializedI.get!string == "1x2"); - - static struct SizeF { - float x; - float y; - } - SizeF sizeF = SizeF(0.1f,0.2f); - Json serializedF = serializeWithPolicy!(JsonSerializer, SizePol)(sizeF); - assert(serializedF.get!string == "0.1x0.2"); -} - - -/** - Deserializes and returns a serialized value. - - serialized_data can be either an input range or a value containing - the serialized data, depending on the type of serializer used. - - See_Also: `vibe.data.json.JsonSerializer`, `vibe.data.json.JsonStringSerializer`, `vibe.data.bson.BsonSerializer` -*/ -T deserialize(Serializer, T, ARGS...)(ARGS args) -{ - return deserializeWithPolicy!(Serializer, DefaultPolicy, T)(args); -} - -/** Note that there is a convenience function `vibe.data.json.deserializeJson` - that can be used instead of manually invoking `deserialize`. -*/ -unittest { - import vibe.data.json; - - struct Test { - int value; - string text; - } - - Json serialized = Json.emptyObject; - serialized["value"] = 12; - serialized["text"] = "Hello"; - - Test test = deserialize!(JsonSerializer, Test)(serialized); - assert(test.value == 12); - assert(test.text == "Hello"); -} - -/** - Deserializes and returns a serialized value, interpreting values according to `Policy` when possible. - - serialized_data can be either an input range or a value containing - the serialized data, depending on the type of serializer used. - - See_Also: `vibe.data.json.JsonSerializer`, `vibe.data.json.JsonStringSerializer`, `vibe.data.bson.BsonSerializer` -*/ -T deserializeWithPolicy(Serializer, alias Policy, T, ARGS...)(ARGS args) -{ - auto deserializer = Serializer(args); - return deserializeValueImpl!(Serializer, Policy).deserializeValue!T(deserializer); -} - -/// -unittest { - import vibe.data.json; - - template SizePol(T) - if (__traits(allMembers, T) == TypeTuple!("x", "y")) - { - import std.conv; - import std.array; - - static string toRepresentation(T value) - @safe { - return to!string(value.x) ~ "x" ~ to!string(value.y); - } - - static T fromRepresentation(string value) - @safe { - string[] fields = value.split('x'); - alias fieldT = typeof(T.x); - auto x = to!fieldT(fields[0]); - auto y = to!fieldT(fields[1]); - return T(x, y); - } - } - - static struct SizeI { - int x; - int y; - } - - Json serializedI = "1x2"; - SizeI sizeI = deserializeWithPolicy!(JsonSerializer, SizePol, SizeI)(serializedI); - assert(sizeI.x == 1); - assert(sizeI.y == 2); - - static struct SizeF { - float x; - float y; - } - Json serializedF = "0.1x0.2"; - SizeF sizeF = deserializeWithPolicy!(JsonSerializer, SizePol, SizeF)(serializedF); - assert(sizeF.x == 0.1f); - assert(sizeF.y == 0.2f); -} - -private template serializeValueImpl(Serializer, alias Policy) { - alias _Policy = Policy; - static assert(Serializer.isSupportedValueType!string, "All serializers must support string values."); - static assert(Serializer.isSupportedValueType!(typeof(null)), "All serializers must support null values."); - - // work around https://issues.dlang.org/show_bug.cgi?id=16528 - static if (isSafeSerializer!Serializer) { - void serializeValue(T, ATTRIBUTES...)(ref Serializer ser, auto ref T value) @safe { serializeValueDeduced!(T, ATTRIBUTES)(ser, value); } - } else { - void serializeValue(T, ATTRIBUTES...)(ref Serializer ser, auto ref T value) { serializeValueDeduced!(T, ATTRIBUTES)(ser, value); } - } - - private void serializeValueDeduced(T, ATTRIBUTES...)(ref Serializer ser, auto ref T value) - { - import std.typecons : BitFlags, Nullable, Tuple, Typedef, TypedefType, tuple; - - alias TU = Unqual!T; - - alias Traits = .Traits!(TU, _Policy, ATTRIBUTES); - - static if (isPolicySerializable!(Policy, TU)) { - alias CustomType = typeof(Policy!TU.toRepresentation(TU.init)); - ser.serializeValue!(CustomType, ATTRIBUTES)(Policy!TU.toRepresentation(value)); - } else static if (is(TU == enum)) { - static if (hasPolicyAttributeL!(ByNameAttribute, Policy, ATTRIBUTES)) { - ser.serializeValue!(string)(value.to!string()); - } else { - ser.serializeValue!(OriginalType!TU)(cast(OriginalType!TU)value); - } - } else static if (Serializer.isSupportedValueType!TU) { - static if (is(TU == typeof(null))) ser.writeValue!Traits(null); - else ser.writeValue!(Traits)(value); - } else static if (/*isInstanceOf!(Tuple, TU)*/is(T == Tuple!TPS, TPS...)) { - import std.algorithm.searching: all; - static if (all!"!a.empty"([TU.fieldNames]) && - !hasPolicyAttributeL!(AsArrayAttribute, Policy, ATTRIBUTES)) { - static if (__traits(compiles, ser.beginWriteDictionary!TU(0))) { - auto nfields = value.length; - ser.beginWriteDictionary!Traits(nfields); - } else { - ser.beginWriteDictionary!Traits(); - } - foreach (i, _; T.Types) { - alias TV = typeof(value[i]); - alias STraits = SubTraits!(Traits, TV); - ser.beginWriteDictionaryEntry!STraits(underscoreStrip(TU.fieldNames[i])); - ser.serializeValue!(TV, ATTRIBUTES)(value[i]); - ser.endWriteDictionaryEntry!STraits(underscoreStrip(TU.fieldNames[i])); - } - static if (__traits(compiles, ser.endWriteDictionary!TU(0))) { - ser.endWriteDictionary!Traits(nfields); - } else { - ser.endWriteDictionary!Traits(); - } - } else static if (TU.Types.length == 1) { - ser.serializeValue!(typeof(value[0]), ATTRIBUTES)(value[0]); - } else { - ser.beginWriteArray!Traits(value.length); - foreach (i, _; T.Types) { - alias TV = typeof(value[i]); - alias STraits = SubTraits!(Traits, TV); - ser.beginWriteArrayEntry!STraits(i); - ser.serializeValue!(TV, ATTRIBUTES)(value[i]); - ser.endWriteArrayEntry!STraits(i); - } - ser.endWriteArray!Traits(); - } - } else static if (isArray!TU) { - alias TV = typeof(value[0]); - alias STraits = SubTraits!(Traits, TV); - ser.beginWriteArray!Traits(value.length); - foreach (i, ref el; value) { - ser.beginWriteArrayEntry!STraits(i); - ser.serializeValue!(TV, ATTRIBUTES)(el); - ser.endWriteArrayEntry!STraits(i); - } - ser.endWriteArray!Traits(); - } else static if (isAssociativeArray!TU) { - alias TK = KeyType!TU; - alias TV = ValueType!TU; - alias STraits = SubTraits!(Traits, TV); - - static if (__traits(compiles, ser.beginWriteDictionary!TU(0))) { - auto nfields = value.length; - ser.beginWriteDictionary!Traits(nfields); - } else { - ser.beginWriteDictionary!Traits(); - } - foreach (key, ref el; value) { - string keyname; - static if (is(TK : string)) keyname = key; - else static if (is(TK : real) || is(TK : long) || is(TK == enum)) keyname = key.to!string; - else static if (isStringSerializable!TK) keyname = key.toString(); - else static assert(false, "Associative array keys must be strings, numbers, enums, or have toString/fromString methods."); - ser.beginWriteDictionaryEntry!STraits(keyname); - ser.serializeValue!(TV, ATTRIBUTES)(el); - ser.endWriteDictionaryEntry!STraits(keyname); - } - static if (__traits(compiles, ser.endWriteDictionary!TU(0))) { - ser.endWriteDictionary!Traits(nfields); - } else { - ser.endWriteDictionary!Traits(); - } - } else static if (/*isInstanceOf!(Nullable, TU)*/is(T == Nullable!TPS, TPS...)) { - if (value.isNull()) ser.serializeValue!(typeof(null))(null); - else ser.serializeValue!(typeof(value.get()), ATTRIBUTES)(value.get()); - } else static if (isInstanceOf!(Typedef, TU)) { - ser.serializeValue!(TypedefType!TU, ATTRIBUTES)(cast(TypedefType!TU)value); - } else static if (is(TU == BitFlags!E, E)) { - alias STraits = SubTraits!(Traits, E); - - size_t cnt = 0; - foreach (v; EnumMembers!E) - if (value & v) - cnt++; - - ser.beginWriteArray!Traits(cnt); - cnt = 0; - foreach (v; EnumMembers!E) - if (value & v) { - ser.beginWriteArrayEntry!STraits(cnt); - ser.serializeValue!(E, ATTRIBUTES)(v); - ser.endWriteArrayEntry!STraits(cnt); - cnt++; - } - ser.endWriteArray!Traits(); - } else static if (isCustomSerializable!TU) { - alias CustomType = typeof(T.init.toRepresentation()); - ser.serializeValue!(CustomType, ATTRIBUTES)(value.toRepresentation()); - } else static if (isISOExtStringSerializable!TU) { - ser.serializeValue!(string, ATTRIBUTES)(value.toISOExtString()); - } else static if (isStringSinkSerializable!TU) { - static if (doesSerializerSupportStringSink!Serializer) { - ser.writeStringSinkValue!Traits(value); - } else { - import std.format : formattedWrite; - auto app = appender!string; - app.formattedWrite("%s", value); - ser.serializeValue!(string, ATTRIBUTES)(app.data); - } - } else static if (isStringSerializable!TU) { - ser.serializeValue!(string, ATTRIBUTES)(value.toString()); - } else static if (is(TU == struct) || is(TU == class)) { - static if (!hasSerializableFields!(TU, Policy)) - pragma(msg, "Serializing composite type "~T.stringof~" which has no serializable fields"); - static if (is(TU == class)) { - if (value is null) { - ser.serializeValue!(typeof(null))(null); - return; - } - } - static auto safeGetMember(string mname)(ref T val) @safe { - static if (__traits(compiles, __traits(getMember, val, mname))) { - return __traits(getMember, val, mname); - } else { - pragma(msg, "Warning: Getter for "~fullyQualifiedName!T~"."~mname~" is not @safe"); - return () @trusted { return __traits(getMember, val, mname); } (); - } - } - static if (hasPolicyAttributeL!(AsArrayAttribute, Policy, ATTRIBUTES)) { - enum nfields = getExpandedFieldCount!(TU, SerializableFields!(TU, Policy)); - ser.beginWriteArray!Traits(nfields); - size_t fcount = 0; - foreach (mname; SerializableFields!(TU, Policy)) { - alias TMS = TypeTuple!(typeof(__traits(getMember, value, mname))); - foreach (j, TM; TMS) { - alias TA = TypeTuple!(__traits(getAttributes, TypeTuple!(__traits(getMember, T, mname))[j])); - alias STraits = SubTraits!(Traits, TM, TA); - ser.beginWriteArrayEntry!STraits(fcount); - static if (!isBuiltinTuple!(T, mname)) - ser.serializeValue!(TM, TA)(safeGetMember!mname(value)); - else - ser.serializeValue!(TM, TA)(tuple(__traits(getMember, value, mname))[j]); - ser.endWriteArrayEntry!STraits(fcount); - fcount++; - } - } - ser.endWriteArray!Traits(); - } else { - static if (__traits(compiles, ser.beginWriteDictionary!Traits(0))) { - auto nfields = getExpandedFieldCount!(TU, SerializableFields!(TU, Policy)); - - foreach (mname; SerializableFields!(TU, Policy)) { - static if (!isBuiltinTuple!(T, mname)) { - auto vt = safeGetMember!mname(value); - static if (is(typeof(vt) : Nullable!NVT, NVT) - && hasPolicyAttribute!(EmbedNullableIgnoreNullAttribute, Policy, TypeTuple!(__traits(getMember, T, mname))[0])) { - if (vt.isNull) nfields--; - } - } - } - - ser.beginWriteDictionary!Traits(nfields); - } else { - ser.beginWriteDictionary!Traits(); - } - foreach (mname; SerializableFields!(TU, Policy)) { - alias TM = TypeTuple!(typeof(__traits(getMember, TU, mname))); - alias TA = TypeTuple!(__traits(getAttributes, TypeTuple!(__traits(getMember, T, mname))[0])); - enum name = getPolicyAttribute!(TU, mname, NameAttribute, Policy)(NameAttribute!DefaultPolicy(underscoreStrip(mname))).name; - static if (!isBuiltinTuple!(T, mname)) { - auto vtn = safeGetMember!mname(value); - static if (is(typeof(vtn) : Nullable!NVT, NVT) - && hasPolicyAttribute!(EmbedNullableIgnoreNullAttribute, Policy, TypeTuple!(__traits(getMember, T, mname))[0])) { - if (vtn.isNull) continue; - auto vt = vtn.get; - } else { - auto vt = vtn; - } - } else { - alias TTM = TypeTuple!(typeof(__traits(getMember, value, mname))); - auto vt = tuple!TTM(__traits(getMember, value, mname)); - } - alias STraits = SubTraits!(Traits, typeof(vt), TA); - ser.beginWriteDictionaryEntry!STraits(name); - ser.serializeValue!(typeof(vt), TA)(vt); - ser.endWriteDictionaryEntry!STraits(name); - } - static if (__traits(compiles, ser.endWriteDictionary!Traits(0))) { - ser.endWriteDictionary!Traits(nfields); - } else { - ser.endWriteDictionary!Traits(); - } - } - } else static if (isPointer!TU) { - if (value is null) { - ser.writeValue!Traits(null); - return; - } - ser.serializeValue!(PointerTarget!TU)(*value); - } else static if (is(TU == bool) || is(TU : real) || is(TU : long)) { - ser.serializeValue!(string, ATTRIBUTES)(to!string(value)); - } else static assert(false, "Unsupported serialization type: " ~ T.stringof); - } -} - -/// -package template doesSerializerSupportStringSink(SerT) -{ - static struct T1 { void toString(scope void delegate(scope const(char)[])) {} } - static struct T2 { void toString(R)(ref R dst) { dst.put('f'); dst.put("foo"); } } - - enum doesSerializerSupportStringSink = - is(typeof(SerT.init.writeStringSinkValue!(Traits!(T1, DefaultPolicy))(T1.init))) - && is(typeof(SerT.init.writeStringSinkValue!(Traits!(T2, DefaultPolicy))(T2.init))); -} - -/// -template isStringSinkSerializable(T) -{ - import std.range : nullSink; - - private void sink(S : const(char)[])(scope S s) @safe {} - - enum isStringSinkSerializable = - ( - is(typeof(T.init.toString((scope str) => sink(str)))) - || is(typeof(T.init.toString(nullSink))) - ) - && is(typeof(T.fromString(string.init)) : T); -} - -unittest { - import std.array : split; - import std.format : formattedWrite; - import vibe.data.json; - - static struct X(alias hasSink) { - private int i; - private string s; - - static if (hasSink) { - void toString (scope void delegate(scope const(char)[]) @safe dg) @safe - { - formattedWrite(dg, "%d;%s", this.i, this.s); - } - } - - string toString () @safe const pure nothrow - { - return "42;hello"; - } - - static X fromString (string s) @safe pure - { - auto parts = s.split(";"); - auto x = X(parts[0].to!int, parts[1]); - return x; - } - } - - static assert(!isStringSinkSerializable!(X!false)); - static assert(isStringSinkSerializable!(X!true)); - - // old toString() style methods still work if no sink overload presented - auto serialized1 = X!false(7,"x1").serializeToJsonString(); - assert(serialized1 == `"42;hello"`); - auto deserialized1 = deserializeJson!(X!false)(serialized1); - assert(deserialized1.i == 42); - assert(deserialized1.s == "hello"); - - // sink overload takes precedence - auto serialized2 = X!true(7,"x2").serializeToJsonString(); - assert(serialized2 == `"7;x2"`); - auto deserialized2 = deserializeJson!(X!true)(serialized2); - assert(deserialized2.i == 7); - assert(deserialized2.s == "x2"); - - // type is sink serializable, but serializer doesn't support sink - auto serialized3 = X!true(7,"x2").serializeToJson(); - assert(to!string(serialized3) == `"7;x2"`); - auto deserialized3 = deserializeJson!(X!true)(serialized3); - assert(deserialized3.i == 7); - assert(deserialized3.s == "x2"); -} - -private struct Traits(T, alias POL, ATTRIBUTES...) -{ - alias Type = T; - alias Policy = POL; - alias Attributes = TypeTuple!ATTRIBUTES; -} - -private struct SubTraits(Traits, T, A...) -{ - alias Type = Unqual!T; - alias Attributes = TypeTuple!A; - alias Policy = Traits.Policy; - alias ContainerType = Traits.Type; - alias ContainerAttributes = Traits.Attributes; -} - -private template deserializeValueImpl(Serializer, alias Policy) { - alias _Policy = Policy; - static assert(Serializer.isSupportedValueType!string, "All serializers must support string values."); - static assert(Serializer.isSupportedValueType!(typeof(null)), "All serializers must support null values."); - - // work around https://issues.dlang.org/show_bug.cgi?id=16528 - static if (isSafeDeserializer!Serializer) { - T deserializeValue(T, ATTRIBUTES...)(ref Serializer ser) @safe { return deserializeValueDeduced!(T, ATTRIBUTES)(ser); } - } else { - T deserializeValue(T, ATTRIBUTES...)(ref Serializer ser) { return deserializeValueDeduced!(T, ATTRIBUTES)(ser); } - } - - T deserializeValueDeduced(T, ATTRIBUTES...)(ref Serializer ser) if(!isMutable!T) - { - import std.algorithm.mutation : move; - auto ret = deserializeValue!(Unqual!T, ATTRIBUTES)(ser); - return () @trusted { return cast(T)ret.move; } (); - } - - T deserializeValueDeduced(T, ATTRIBUTES...)(ref Serializer ser) if(isMutable!T) - { - import std.typecons : BitFlags, Nullable, Typedef, TypedefType, Tuple; - - alias Traits = .Traits!(T, _Policy, ATTRIBUTES); - - static if (isPolicySerializable!(Policy, T)) { - alias CustomType = typeof(Policy!T.toRepresentation(T.init)); - return Policy!T.fromRepresentation(ser.deserializeValue!(CustomType, ATTRIBUTES)); - } else static if (is(T == enum)) { - static if (hasPolicyAttributeL!(ByNameAttribute, Policy, ATTRIBUTES)) { - return ser.deserializeValue!(string, ATTRIBUTES).to!T(); - } else { - auto value = ser.deserializeValue!(OriginalType!T); - switch (value) { - default: - throw new ConvException("Unexpected enum value " ~ value.to!string); - static foreach (enumvalue; NoDuplicates!(EnumMembers!T)) - case enumvalue: return enumvalue; - } - } - } else static if (Serializer.isSupportedValueType!T) { - return ser.readValue!(Traits, T)(); - } else static if (/*isInstanceOf!(Tuple, TU)*/is(T == Tuple!TPS, TPS...)) { - enum fieldsCount = T.Types.length; - import std.algorithm.searching: all; - static if (all!"!a.empty"([T.fieldNames]) && - !hasPolicyAttributeL!(AsArrayAttribute, Policy, ATTRIBUTES)) { - T ret; - bool[fieldsCount] set; - ser.readDictionary!Traits((name) { - switch (name) { - default: - static if (is(typeof(ser.skipValue()))) { - ser.skipValue(); - } - break; - foreach (i, TV; T.Types) { - enum fieldName = underscoreStrip(T.fieldNames[i]); - alias STraits = SubTraits!(Traits, TV); - case fieldName: { - ser.beginReadDictionaryEntry!STraits(fieldName); - ret[i] = ser.deserializeValue!(TV, ATTRIBUTES); - ser.endReadDictionaryEntry!STraits(fieldName); - set[i] = true; - } break; - } - } - }); - foreach (i, fieldName; T.fieldNames) - enforce(set[i], "Missing tuple field '"~fieldName~"' of type '"~T.Types[i].stringof~"' ("~Policy.stringof~")."); - return ret; - } else static if (fieldsCount == 1) { - return T(ser.deserializeValue!(T.Types[0], ATTRIBUTES)()); - } else { - T ret; - size_t currentField = 0; - ser.readArray!Traits((sz) { assert(sz == 0 || sz == fieldsCount); }, { - switch (currentField++) { - default: break; - foreach (i, TV; T.Types) { - alias STraits = SubTraits!(Traits, TV); - case i: { - ser.beginReadArrayEntry!STraits(i); - ret[i] = ser.deserializeValue!(TV, ATTRIBUTES); - ser.endReadArrayEntry!STraits(i); - } break; - } - } - }); - enforce(currentField == fieldsCount, "Missing tuple field(s) - expected '"~fieldsCount.stringof~"', received '"~currentField.stringof~"' ("~Policy.stringof~")."); - return ret; - } - } else static if (isStaticArray!T) { - alias TV = typeof(T.init[0]); - alias STraits = SubTraits!(Traits, TV); - T ret; - size_t i = 0; - ser.readArray!Traits((sz) { assert(sz == 0 || sz == T.length); }, { - assert(i < T.length); - ser.beginReadArrayEntry!STraits(i); - ret[i] = ser.deserializeValue!(TV, ATTRIBUTES); - ser.endReadArrayEntry!STraits(i); - i++; - }); - return ret; - } else static if (isDynamicArray!T) { - alias TV = typeof(T.init[0]); - alias STraits = SubTraits!(Traits, TV); - //auto ret = appender!T(); - T ret; // Cannot use appender because of DMD BUG 10690/10859/11357 - ser.readArray!Traits((sz) @safe { ret.reserve(sz); }, () @safe { - size_t i = ret.length; - ser.beginReadArrayEntry!STraits(i); - static if (__traits(compiles, () @safe { ser.deserializeValue!(TV, ATTRIBUTES); })) - ret ~= ser.deserializeValue!(TV, ATTRIBUTES); - else // recursive array https://issues.dlang.org/show_bug.cgi?id=16528 - ret ~= (() @trusted => ser.deserializeValue!(TV, ATTRIBUTES))(); - ser.endReadArrayEntry!STraits(i); - }); - return ret;//cast(T)ret.data; - } else static if (isAssociativeArray!T) { - alias TK = KeyType!T; - alias TV = ValueType!T; - alias STraits = SubTraits!(Traits, TV); - - T ret; - ser.readDictionary!Traits((name) @safe { - TK key; - static if (is(TK == string) || (is(TK == enum) && is(OriginalType!TK == string))) key = cast(TK)name; - else static if (is(TK : real) || is(TK : long) || is(TK == enum)) key = name.to!TK; - else static if (isStringSerializable!TK) key = TK.fromString(name); - else static assert(false, "Associative array keys must be strings, numbers, enums, or have toString/fromString methods."); - ser.beginReadDictionaryEntry!STraits(name); - ret[key] = ser.deserializeValue!(TV, ATTRIBUTES); - ser.endReadDictionaryEntry!STraits(name); - }); - return ret; - } else static if (isInstanceOf!(Nullable, T)) { - if (ser.tryReadNull!Traits()) return T.init; - return T(ser.deserializeValue!(typeof(T.init.get()), ATTRIBUTES)); - } else static if (isInstanceOf!(Typedef, T)) { - return T(ser.deserializeValue!(TypedefType!T, ATTRIBUTES)); - } else static if (is(T == BitFlags!E, E)) { - alias STraits = SubTraits!(Traits, E); - T ret; - size_t i = 0; - ser.readArray!Traits((sz) {}, { - ser.beginReadArrayEntry!STraits(i); - ret |= ser.deserializeValue!(E, ATTRIBUTES); - ser.endReadArrayEntry!STraits(i); - i++; - }); - return ret; - } else static if (isCustomSerializable!T) { - alias CustomType = typeof(T.init.toRepresentation()); - return T.fromRepresentation(ser.deserializeValue!(CustomType, ATTRIBUTES)); - } else static if (isISOExtStringSerializable!T) { - return T.fromISOExtString(ser.readValue!(Traits, string)()); - } else static if (isStringSerializable!T) { - return T.fromString(ser.readValue!(Traits, string)()); - } else static if (is(T == struct) || is(T == class)) { - static if (is(T == class)) { - if (ser.tryReadNull!Traits()) return null; - } - - T ret; - string name; - bool[getExpandedFieldsData!(T, SerializableFields!(T, Policy)).length] set; - static if (is(T == class)) ret = new T; - - void safeSetMember(string mname, U)(ref T value, U fval) - @safe { - static if (__traits(compiles, () @safe { __traits(getMember, value, mname) = fval; })) - __traits(getMember, value, mname) = fval; - else { - pragma(msg, "Warning: Setter for "~fullyQualifiedName!T~"."~mname~" is not @safe"); - () @trusted { __traits(getMember, value, mname) = fval; } (); - } - } - - static if (hasPolicyAttributeL!(AsArrayAttribute, Policy, ATTRIBUTES)) { - size_t idx = 0; - ser.readArray!Traits((sz){}, { - static if (hasSerializableFields!(T, Policy)) { - switch (idx++) { - default: break; - foreach (i, FD; getExpandedFieldsData!(T, SerializableFields!(T, Policy))) { - enum mname = FD[0]; - enum msindex = FD[1]; - alias MT = TypeTuple!(__traits(getMember, T, mname)); - alias MTI = MT[msindex]; - alias TMTI = typeof(MTI); - alias TMTIA = TypeTuple!(__traits(getAttributes, MTI)); - alias STraits = SubTraits!(Traits, TMTI, TMTIA); - - case i: - static if (hasPolicyAttribute!(OptionalAttribute, Policy, MTI)) - if (ser.tryReadNull!STraits()) return; - set[i] = true; - ser.beginReadArrayEntry!STraits(i); - static if (!isBuiltinTuple!(T, mname)) { - safeSetMember!mname(ret, ser.deserializeValue!(TMTI, TMTIA)); - } else { - __traits(getMember, ret, mname)[msindex] = ser.deserializeValue!(TMTI, TMTIA); - } - ser.endReadArrayEntry!STraits(i); - break; - } - } - } else { - pragma(msg, "Deserializing composite type "~T.stringof~" which has no serializable fields."); - } - }); - } else { - ser.readDictionary!Traits((name) { - static if (hasSerializableFields!(T, Policy)) { - switch (name) { - default: - static if (is(typeof(ser.skipValue()))) { - ser.skipValue(); - } - break; - foreach (i, mname; SerializableFields!(T, Policy)) { - alias TM = TypeTuple!(typeof(__traits(getMember, T, mname))); - alias TA = TypeTuple!(__traits(getAttributes, TypeTuple!(__traits(getMember, T, mname))[0])); - alias STraits = SubTraits!(Traits, TM, TA); - enum fname = getPolicyAttribute!(T, mname, NameAttribute, Policy)(NameAttribute!DefaultPolicy(underscoreStrip(mname))).name; - case fname: - static if (hasPolicyAttribute!(OptionalAttribute, Policy, TypeTuple!(__traits(getMember, T, mname))[0])) - if (ser.tryReadNull!STraits()) return; - set[i] = true; - ser.beginReadDictionaryEntry!STraits(fname); - static if (!isBuiltinTuple!(T, mname)) { - safeSetMember!mname(ret, ser.deserializeValue!(TM, TA)); - } else { - __traits(getMember, ret, mname) = ser.deserializeValue!(Tuple!TM, TA); - } - ser.endReadDictionaryEntry!STraits(fname); - break; - } - } - } else { - pragma(msg, "Deserializing composite type "~T.stringof~" which has no serializable fields."); - } - }); - } - foreach (i, mname; SerializableFields!(T, Policy)) - static if (!hasPolicyAttribute!(OptionalAttribute, Policy, TypeTuple!(__traits(getMember, T, mname))[0])) - enforce(set[i], "Missing non-optional field '"~mname~"' of type '"~T.stringof~"' ("~Policy.stringof~")."); - return ret; - } else static if (isPointer!T) { - if (ser.tryReadNull!Traits()) return null; - alias PT = PointerTarget!T; - auto ret = new PT; - *ret = ser.deserializeValue!(PT, ATTRIBUTES); - return ret; - } else static if (is(T == bool) || is(T : real) || is(T : long)) { - return to!T(ser.deserializeValue!string()); - } else static assert(false, "Unsupported serialization type: " ~ T.stringof); - } -} - - -/** - Attribute for overriding the field name during (de-)serialization. - - Note that without the `@name` attribute there is a shorter alternative - for using names that collide with a D keyword. A single trailing - underscore will automatically be stripped when determining a field - name. -*/ -NameAttribute!Policy name(alias Policy = DefaultPolicy)(string name) -{ - return NameAttribute!Policy(name); -} -/// -unittest { - struct CustomPolicy {} - - struct Test { - // serialized as "screen-size": - @name("screen-size") int screenSize; - - // serialized as "print-size" by default, - // but as "PRINTSIZE" if CustomPolicy is used for serialization. - @name("print-size") - @name!CustomPolicy("PRINTSIZE") - int printSize; - - // serialized as "version" - int version_; - } -} - - -/** - Attribute marking a field as optional during deserialization. -*/ -@property OptionalAttribute!Policy optional(alias Policy = DefaultPolicy)() -{ - return OptionalAttribute!Policy(); -} -/// -unittest { - struct Test { - // does not need to be present during deserialization - @optional int screenSize = 100; - } -} - - -/** - Attribute for marking non-serialized fields. -*/ -@property IgnoreAttribute!Policy ignore(alias Policy = DefaultPolicy)() -{ - return IgnoreAttribute!Policy(); -} -/// -unittest { - struct Test { - // is neither serialized not deserialized - @ignore int screenSize; - } -} -/// -unittest { - template CustomPolicy(T) { - // ... - } - - struct Test { - // not (de)serialized for serializeWithPolicy!(Test, CustomPolicy) - // but for other policies or when serialized without a policy - @ignore!CustomPolicy int screenSize; - } -} - - -/** - Attribute for forcing serialization of enum fields by name instead of by value. -*/ -@property ByNameAttribute!Policy byName(alias Policy = DefaultPolicy)() -{ - return ByNameAttribute!Policy(); -} -/// -unittest { - enum Color { - red, - green, - blue - } - - struct Test { - // serialized as an int (e.g. 1 for Color.green) - Color color; - // serialized as a string (e.g. "green" for Color.green) - @byName Color namedColor; - // serialized as array of ints - Color[] colorArray; - // serialized as array of strings - @byName Color[] namedColorArray; - } -} - - -/** - Attribute for representing a struct/class as an array instead of an object. - - Usually structs and class objects are serialized as dictionaries mapping - from field name to value. Using this attribute, they will be serialized - as a flat array instead. Note that changing the layout will make any - already serialized data mismatch when this attribute is used. -*/ -@property AsArrayAttribute!Policy asArray(alias Policy = DefaultPolicy)() -{ - return AsArrayAttribute!Policy(); -} -/// -unittest { - struct Fields { - int f1; - string f2; - double f3; - } - - struct Test { - // serialized as name:value pairs ["f1": int, "f2": string, "f3": double] - Fields object; - // serialized as a sequential list of values [int, string, double] - @asArray Fields array; - } - - import vibe.data.json; - static assert(is(typeof(serializeToJson(Test())))); -} - - -/** - Makes this nullable as if it is not a nullable to the serializer. Ignores the field completely when it is null. - - Works with Nullable!classes and Nullable!structs. Behavior is undefined if this is applied to other types. - - Implicitly marks this as optional for deserialization. (Keeps the struct default value when not present in serialized value) -*/ -@property EmbedNullableIgnoreNullAttribute!Policy embedNullable(alias Policy = DefaultPolicy)() -{ - return EmbedNullableIgnoreNullAttribute!Policy(); -} -/// -unittest { - import std.typecons : Nullable; - - struct Test { - // Not serialized at all if null, ignored on deserialization if not present. - @embedNullable Nullable!int field; - } -} - - -/// -enum FieldExistence -{ - missing, - exists, - defer -} - -/// User defined attribute (not intended for direct use) -struct NameAttribute(alias POLICY) { alias Policy = POLICY; string name; } -/// ditto -struct OptionalAttribute(alias POLICY) { alias Policy = POLICY; } -/// ditto -struct IgnoreAttribute(alias POLICY) { alias Policy = POLICY; } -/// ditto -struct ByNameAttribute(alias POLICY) { alias Policy = POLICY; } -/// ditto -struct AsArrayAttribute(alias POLICY) { alias Policy = POLICY; } -/// ditto -struct EmbedNullableIgnoreNullAttribute(alias POLICY) { alias Policy = POLICY; } - -/** - Checks if a given type has a custom serialization representation. - - A class or struct type is custom serializable if it defines a pair of - `toRepresentation`/`fromRepresentation` methods. Any class or - struct type that has this trait will be serialized by using the return - value of it's `toRepresentation` method instead of the original value. - - This trait has precedence over `isISOExtStringSerializable` and - `isStringSerializable`. -*/ -template isCustomSerializable(T) -{ - enum bool isCustomSerializable = is(typeof(T.init.toRepresentation())) && is(typeof(T.fromRepresentation(T.init.toRepresentation())) == T); -} -/// -unittest { - // represented as a single uint when serialized - static struct S { - ushort x, y; - - uint toRepresentation() const { return x + (y << 16); } - static S fromRepresentation(uint i) { return S(i & 0xFFFF, i >> 16); } - } - - static assert(isCustomSerializable!S); -} - - -/** - Checks if a given type has an ISO extended string serialization representation. - - A class or struct type is ISO extended string serializable if it defines a - pair of `toISOExtString`/`fromISOExtString` methods. Any class or - struct type that has this trait will be serialized by using the return - value of it's `toISOExtString` method instead of the original value. - - This is mainly useful for supporting serialization of the the date/time - types in `std.datetime`. - - This trait has precedence over `isStringSerializable`. -*/ -template isISOExtStringSerializable(T) -{ - enum bool isISOExtStringSerializable = is(typeof(T.init.toISOExtString()) : string) && is(typeof(T.fromISOExtString("")) : T); -} -/// -unittest { - import std.datetime; - - static assert(isISOExtStringSerializable!DateTime); - static assert(isISOExtStringSerializable!SysTime); - - // represented as an ISO extended string when serialized - static struct S { - // dummy example implementations - string toISOExtString() const { return ""; } - static S fromISOExtString(string s) { return S.init; } - } - - static assert(isISOExtStringSerializable!S); -} - - -/** - Checks if a given type has a string serialization representation. - - A class or struct type is string serializable if it defines a pair of - `toString`/`fromString` methods. Any class or struct type that - has this trait will be serialized by using the return value of it's - `toString` method instead of the original value. -*/ -template isStringSerializable(T) -{ - enum bool isStringSerializable = is(typeof(T.init.toString()) : string) && is(typeof(T.fromString("")) : T); -} -/// -unittest { - import std.conv; - - // represented as a string when serialized - static struct S { - int value; - - // dummy example implementations - string toString() const { return value.to!string(); } - static S fromString(string s) { return S(s.to!int()); } - } - - static assert(isStringSerializable!S); -} - - -/** Default policy (performs no customization). -*/ -template DefaultPolicy(T) -{ -} - -/** - Checks if a given policy supports custom serialization for a given type. - - A class or struct type is custom serializable according to a policy if - the policy defines a pair of `toRepresentation`/`fromRepresentation` - functions. Any class or struct type that has this trait for the policy supplied to - `serializeWithPolicy` will be serialized by using the return value of the - policy `toRepresentation` function instead of the original value. - - This trait has precedence over `isCustomSerializable`, - `isISOExtStringSerializable` and `isStringSerializable`. - - See_Also: `vibe.data.serialization.serializeWithPolicy` -*/ -template isPolicySerializable(alias Policy, T) -{ - enum bool isPolicySerializable = is(typeof(Policy!T.toRepresentation(T.init))) && - is(typeof(Policy!T.fromRepresentation(Policy!T.toRepresentation(T.init))) : T); -} -/// -unittest { - import std.conv; - - // represented as the boxed value when serialized - static struct Box(T) { - T value; - } - - template BoxPol(S) - { - auto toRepresentation(S s) { - return s.value; - } - - S fromRepresentation(typeof(S.init.value) v) { - return S(v); - } - } - static assert(isPolicySerializable!(BoxPol, Box!int)); -} - - -/** - Chains serialization policy. - - Constructs a serialization policy that given a type `T` will apply the - first compatible policy `toRepresentation` and `fromRepresentation` - functions. Policies are evaluated left-to-right according to - `isPolicySerializable`. - - See_Also: `vibe.data.serialization.serializeWithPolicy` -*/ -template ChainedPolicy(alias Primary, Fallbacks...) -{ - static if (Fallbacks.length == 0) { - alias ChainedPolicy = Primary; - } else { - alias ChainedPolicy = ChainedPolicy!(ChainedPolicyImpl!(Primary, Fallbacks[0]), Fallbacks[1..$]); - } -} -/// -unittest { - import std.conv; - - // To be represented as the boxed value when serialized - static struct Box(T) { - T value; - } - // Also to berepresented as the boxed value when serialized, but has - // a different way to access the value. - static struct Box2(T) { - private T v; - ref T get() { - return v; - } - } - template BoxPol(S) - { - auto toRepresentation(S s) { - return s.value; - } - - S fromRepresentation(typeof(toRepresentation(S.init)) v) { - return S(v); - } - } - template Box2Pol(S) - { - auto toRepresentation(S s) { - return s.get(); - } - - S fromRepresentation(typeof(toRepresentation(S.init)) v) { - S s; - s.get() = v; - return s; - } - } - alias ChainPol = ChainedPolicy!(BoxPol, Box2Pol); - static assert(!isPolicySerializable!(BoxPol, Box2!int)); - static assert(!isPolicySerializable!(Box2Pol, Box!int)); - static assert(isPolicySerializable!(ChainPol, Box!int)); - static assert(isPolicySerializable!(ChainPol, Box2!int)); -} - -private template ChainedPolicyImpl(alias Primary, alias Fallback) -{ - template Pol(T) - { - static if (isPolicySerializable!(Primary, T)) { - alias toRepresentation = Primary!T.toRepresentation; - alias fromRepresentation = Primary!T.fromRepresentation; - } else { - alias toRepresentation = Fallback!T.toRepresentation; - alias fromRepresentation = Fallback!T.fromRepresentation; - } - } - alias ChainedPolicyImpl = Pol; -} - -private template isBuiltinTuple(T, string member) -{ - alias TM = AliasSeq!(typeof(__traits(getMember, T.init, member))); - static if (TM.length > 1) enum isBuiltinTuple = true; - else static if (is(typeof(__traits(getMember, T.init, member)) == TM[0])) - enum isBuiltinTuple = false; - else enum isBuiltinTuple = true; // single-element tuple -} - -// heuristically determines @safe'ty of the serializer by testing readValue and writeValue for type int -private template isSafeSerializer(S) -{ - alias T = Traits!(int, DefaultPolicy); - static if (__traits(hasMember, S, "writeValue")) - enum isSafeSerializer = __traits(compiles, (S s) @safe { s.writeValue!T(42); }); - else static assert(0, "Serializer is missing required writeValue method"); -} - -// heuristically determines @safe'ty of the deserializer by testing readValue and writeValue for type int -private template isSafeDeserializer(S) -{ - alias T = Traits!(int, DefaultPolicy); - static if (__traits(hasMember, S, "readValue")) - enum isSafeDeserializer = __traits(compiles, (S s) @safe { s.readValue!(T, int)(); }); - else static assert(0, "Deserializer is missing required readValue method"); -} - -private template hasAttribute(T, alias decl) { enum hasAttribute = findFirstUDA!(T, decl).found; } - -unittest { - @asArray int i1; - static assert(hasAttribute!(AsArrayAttribute!DefaultPolicy, i1)); - int i2; - static assert(!hasAttribute!(AsArrayAttribute!DefaultPolicy, i2)); -} - -private template hasPolicyAttribute(alias T, alias POLICY, alias decl) -{ - // __traits(identifier) to hack around T being a template and not a type - // this if makes hasPolicyAttribute!(OptionalAttribute) == true when EmbedNullableIgnoreNullAttribute is present. - static if (__traits(identifier, T) == __traits(identifier, OptionalAttribute)) - enum hasPolicyAttribute = hasPolicyAttributeImpl!(T, POLICY, decl) - || hasPolicyAttributeImpl!(EmbedNullableIgnoreNullAttribute, POLICY, decl); - else - enum hasPolicyAttribute = hasPolicyAttributeImpl!(T, POLICY, decl); -} - -private template hasPolicyAttributeImpl(alias T, alias POLICY, alias decl) -{ - enum hasPolicyAttributeImpl = hasAttribute!(T!POLICY, decl) || hasAttribute!(T!DefaultPolicy, decl); -} - -unittest { - import std.typecons : Nullable; - - template CP(T) {} - @asArray!CP int i1; - @asArray int i2; - int i3; - @embedNullable Nullable!int i4; - - static assert(hasPolicyAttribute!(AsArrayAttribute, CP, i1)); - static assert(hasPolicyAttribute!(AsArrayAttribute, CP, i2)); - static assert(!hasPolicyAttribute!(AsArrayAttribute, CP, i3)); - static assert(!hasPolicyAttribute!(AsArrayAttribute, DefaultPolicy, i1)); - static assert(hasPolicyAttribute!(AsArrayAttribute, DefaultPolicy, i2)); - static assert(!hasPolicyAttribute!(AsArrayAttribute, DefaultPolicy, i3)); - static assert(hasPolicyAttribute!(EmbedNullableIgnoreNullAttribute, DefaultPolicy, i4)); - static assert(hasPolicyAttribute!(OptionalAttribute, DefaultPolicy, i4)); - static assert(!hasPolicyAttribute!(IgnoreAttribute, DefaultPolicy, i4)); -} - - -private template hasAttributeL(T, ATTRIBUTES...) { - static if (ATTRIBUTES.length == 1) { - enum hasAttributeL = is(typeof(ATTRIBUTES[0]) == T); - } else static if (ATTRIBUTES.length > 1) { - enum hasAttributeL = hasAttributeL!(T, ATTRIBUTES[0 .. $/2]) || hasAttributeL!(T, ATTRIBUTES[$/2 .. $]); - } else { - enum hasAttributeL = false; - } -} - -unittest { - static assert(hasAttributeL!(AsArrayAttribute!DefaultPolicy, byName, asArray)); - static assert(!hasAttributeL!(AsArrayAttribute!DefaultPolicy, byName)); -} - -private template hasPolicyAttributeL(alias T, alias POLICY, ATTRIBUTES...) -{ - enum hasPolicyAttributeL = hasAttributeL!(T!POLICY, ATTRIBUTES) || hasAttributeL!(T!DefaultPolicy, ATTRIBUTES); -} - -private static T getAttribute(TT, string mname, T)(T default_value) -{ - enum val = findFirstUDA!(T, __traits(getMember, TT, mname)); - static if (val.found) return val.value; - else return default_value; -} - -private static auto getPolicyAttribute(TT, string mname, alias Attribute, alias Policy)(Attribute!DefaultPolicy default_value) -{ - enum val = findFirstUDA!(Attribute!Policy, TypeTuple!(__traits(getMember, TT, mname))[0]); - static if (val.found) return val.value; - else { - enum val2 = findFirstUDA!(Attribute!DefaultPolicy, TypeTuple!(__traits(getMember, TT, mname))[0]); - static if (val2.found) return val2.value; - else return default_value; - } -} - -private string underscoreStrip(string field_name) -@safe nothrow @nogc { - if( field_name.length < 1 || field_name[$-1] != '_' ) return field_name; - else return field_name[0 .. $-1]; -} - - -private template hasSerializableFields(T, alias POLICY, size_t idx = 0) -{ - enum hasSerializableFields = SerializableFields!(T, POLICY).length > 0; - /*static if (idx < __traits(allMembers, T).length) { - enum mname = __traits(allMembers, T)[idx]; - static if (!isRWPlainField!(T, mname) && !isRWField!(T, mname)) enum hasSerializableFields = hasSerializableFields!(T, idx+1); - else static if (hasAttribute!(IgnoreAttribute, __traits(getMember, T, mname))) enum hasSerializableFields = hasSerializableFields!(T, idx+1); - else enum hasSerializableFields = true; - } else enum hasSerializableFields = false;*/ -} - -private template SerializableFields(COMPOSITE, alias POLICY) -{ - alias SerializableFields = FilterSerializableFields!(COMPOSITE, POLICY, __traits(allMembers, COMPOSITE)); -} - -private template FilterSerializableFields(COMPOSITE, alias POLICY, FIELDS...) -{ - static if (FIELDS.length > 1) { - alias FilterSerializableFields = TypeTuple!( - FilterSerializableFields!(COMPOSITE, POLICY, FIELDS[0 .. $/2]), - FilterSerializableFields!(COMPOSITE, POLICY, FIELDS[$/2 .. $])); - } else static if (FIELDS.length == 1) { - alias T = COMPOSITE; - enum mname = FIELDS[0]; - static if (isRWPlainField!(T, mname) || isRWField!(T, mname)) { - alias Tup = TypeTuple!(__traits(getMember, COMPOSITE, FIELDS[0])); - static if (Tup.length != 1) { - alias FilterSerializableFields = TypeTuple!(mname); - } else { - static if (!hasPolicyAttribute!(IgnoreAttribute, POLICY, __traits(getMember, T, mname))) - { - alias FilterSerializableFields = TypeTuple!(mname); - } else alias FilterSerializableFields = TypeTuple!(); - } - } else alias FilterSerializableFields = TypeTuple!(); - } else alias FilterSerializableFields = TypeTuple!(); -} - -private size_t getExpandedFieldCount(T, FIELDS...)() -{ - size_t ret = 0; - foreach (F; FIELDS) ret += TypeTuple!(__traits(getMember, T, F)).length; - return ret; -} - -private template getExpandedFieldsData(T, FIELDS...) -{ - import std.meta : aliasSeqOf, staticMap; - import std.range : repeat, zip, iota; - - enum subfieldsCount(alias F) = TypeTuple!(__traits(getMember, T, F)).length; - alias processSubfield(alias F) = aliasSeqOf!(zip(repeat(F), iota(subfieldsCount!F))); - alias getExpandedFieldsData = staticMap!(processSubfield, FIELDS); -} - -/// Uses Base64 representation for `ubyte[]` instead of `to!string` -public class Base64ArrayPolicy (R) if (isArray!R && is(ElementType!R : ubyte)) -{ - public static string toRepresentation (in R data) @safe pure - { - import std.base64 : Base64; - return Base64.encode(data); - } - - public static ubyte[] fromRepresentation (in string data) @safe pure - { - import std.base64 : Base64; - return Base64.decode(data); - } -} - -/******************************************************************************/ -/* General serialization unit testing */ -/******************************************************************************/ - -version (unittest) { - static assert(isSafeSerializer!TestSerializer); - static assert(isSafeDeserializer!TestSerializer); - - private struct TestSerializer { - import std.array, std.conv, std.range, std.string, std.typecons; - - string result; - - enum isSupportedValueType(T) = is(T == string) || is(T == typeof(null)) || is(T == float) || is (T == int); - - template unqualSeq(Specs...) - { - static if (Specs.length == 0) alias unqualSeq = AliasSeq!(); - else static if (is(Specs[0])) alias unqualSeq = AliasSeq!(Unqual!(Specs[0]), unqualSeq!(Specs[1 .. $])); - else alias unqualSeq = AliasSeq!(Specs[0], unqualSeq!(Specs[1 .. $])); - } - - template unqualType(T) { - static if (isAssociativeArray!T) alias unqualType = Unqual!(ValueType!T)[Unqual!(KeyType!T)]; - else static if (isTuple!T) alias unqualType = Tuple!(unqualSeq!(TemplateArgsOf!T)); - else static if (isArray!T && !isSomeString!T) alias unqualType = Unqual!(ElementType!T)[]; - else alias unqualType = Unqual!T; - } - - string getSerializedResult() @safe { return result; } - void beginWriteDictionary(Traits)() { result ~= "D("~unqualType!(Traits.Type).mangleof~"){"; } - void endWriteDictionary(Traits)() { result ~= "}D("~unqualType!(Traits.Type).mangleof~")"; } - void beginWriteDictionaryEntry(Traits)(string name) { result ~= "DE("~unqualType!(Traits.Type).mangleof~","~name~")("; } - void endWriteDictionaryEntry(Traits)(string name) { result ~= ")DE("~unqualType!(Traits.Type).mangleof~","~name~")"; } - void beginWriteArray(Traits)(size_t length) { result ~= "A("~unqualType!(Traits.Type).mangleof~")["~length.to!string~"]["; } - void endWriteArray(Traits)() { result ~= "]A("~unqualType!(Traits.Type).mangleof~")"; } - void beginWriteArrayEntry(Traits)(size_t i) { result ~= "AE("~unqualType!(Traits.Type).mangleof~","~i.to!string~")("; } - void endWriteArrayEntry(Traits)(size_t i) { result ~= ")AE("~unqualType!(Traits.Type).mangleof~","~i.to!string~")"; } - void writeValue(Traits, T)(T value) { - if (is(T == typeof(null))) result ~= "null"; - else { - assert(isSupportedValueType!(unqualType!T)); - result ~= "V("~(unqualType!T).mangleof~")("~value.to!string~")"; - } - } - - // deserialization - void readDictionary(Traits)(scope void delegate(string) @safe entry_callback) - { - skip("D("~unqualType!(Traits.Type).mangleof~"){"); - while (result.startsWith("DE(")) { - result = result[3 .. $]; - auto idx = result.indexOf(','); - auto idx2 = result.indexOf(")("); - assert(idx > 0 && idx2 > idx); - auto t = result[0 .. idx]; - auto n = result[idx+1 .. idx2]; - result = result[idx2+2 .. $]; - entry_callback(n); - skip(")DE("~t~","~n~")"); - } - skip("}D("~unqualType!(Traits.Type).mangleof~")"); - } - - void beginReadDictionaryEntry(Traits)(string name) {} - void endReadDictionaryEntry(Traits)(string name) {} - - void readArray(Traits)(scope void delegate(size_t) @safe size_callback, scope void delegate() @safe entry_callback) - { - skip("A("~unqualType!(Traits.Type).mangleof~")["); - auto bidx = result.indexOf("]["); - assert(bidx > 0); - auto cnt = result[0 .. bidx].to!size_t; - result = result[bidx+2 .. $]; - - size_t i = 0; - while (result.startsWith("AE(")) { - result = result[3 .. $]; - auto idx = result.indexOf(','); - auto idx2 = result.indexOf(")("); - assert(idx > 0 && idx2 > idx); - auto t = result[0 .. idx]; - auto n = result[idx+1 .. idx2]; - result = result[idx2+2 .. $]; - assert(n == i.to!string); - entry_callback(); - skip(")AE("~t~","~n~")"); - i++; - } - skip("]A("~unqualType!(Traits.Type).mangleof~")"); - - assert(i == cnt); - } - - void beginReadArrayEntry(Traits)(size_t index) {} - void endReadArrayEntry(Traits)(size_t index) {} - - T readValue(Traits, T)() - { - skip("V("~unqualType!T.mangleof~")("); - auto idx = result.indexOf(')'); - assert(idx >= 0); - auto ret = result[0 .. idx].to!T; - result = result[idx+1 .. $]; - return ret; - } - - void skip(string prefix) - @safe { - assert(result.startsWith(prefix), prefix ~ " vs. " ~ result); - result = result[prefix.length .. $]; - } - - bool tryReadNull(Traits)() - { - if (result.startsWith("null")) { - result = result[4 .. $]; - return true; - } else return false; - } - } -} - -unittest { // basic serialization behavior - import std.typecons : Nullable; - - static void test(T)(auto ref T value, string expected) { - assert(serialize!TestSerializer(value) == expected, serialize!TestSerializer(value)); - static if (isPointer!T) { - if (value) assert(*deserialize!(TestSerializer, T)(expected) == *value); - else assert(deserialize!(TestSerializer, T)(expected) is null); - } else static if (is(T == Nullable!U, U)) { - if (value.isNull()) assert(deserialize!(TestSerializer, T)(expected).isNull); - else assert(deserialize!(TestSerializer, T)(expected) == value); - } else assert(deserialize!(TestSerializer, T)(expected) == value); - } - - test("hello", "V(Aya)(hello)"); - test(12, "V(i)(12)"); - test(12.0, "V(Aya)(12)"); - test(12.0f, "V(f)(12)"); - assert(serialize!TestSerializer(null) == "null"); - test(["hello", "world"], "A(AAya)[2][AE(Aya,0)(V(Aya)(hello))AE(Aya,0)AE(Aya,1)(V(Aya)(world))AE(Aya,1)]A(AAya)"); - string mangleOfAA = (string[string]).mangleof; - test(["hello": "world"], "D(" ~ mangleOfAA ~ "){DE(Aya,hello)(V(Aya)(world))DE(Aya,hello)}D(" ~ mangleOfAA ~ ")"); - test(cast(int*)null, "null"); - int i = 42; - test(&i, "V(i)(42)"); - Nullable!int j; - test(j, "null"); - j = 42; - test(j, "V(i)(42)"); -} - -unittest { // basic user defined types - static struct S { string f; } - enum Sm = S.mangleof; - auto s = S("hello"); - enum s_ser = "D("~Sm~"){DE(Aya,f)(V(Aya)(hello))DE(Aya,f)}D("~Sm~")"; - assert(serialize!TestSerializer(s) == s_ser, serialize!TestSerializer(s)); - assert(deserialize!(TestSerializer, S)(s_ser) == s); - - static class C { string f; } - enum Cm = C.mangleof; - C c; - assert(serialize!TestSerializer(c) == "null"); - c = new C; - c.f = "hello"; - enum c_ser = "D("~Cm~"){DE(Aya,f)(V(Aya)(hello))DE(Aya,f)}D("~Cm~")"; - assert(serialize!TestSerializer(c) == c_ser); - assert(deserialize!(TestSerializer, C)(c_ser).f == c.f); - - enum E { hello, world } - assert(serialize!TestSerializer(E.hello) == "V(i)(0)"); - assert(serialize!TestSerializer(E.world) == "V(i)(1)"); -} - -unittest { // tuple serialization - import std.typecons : Tuple; - - static struct S(T...) { T f; } - enum Sm = S!(int, string).mangleof; - enum Tum = Tuple!(int, string).mangleof; - const s = S!(int, string)(42, "hello"); - - const ss = serialize!TestSerializer(s); - const es = "D("~Sm~"){DE("~Tum~",f)(A("~Tum~")[2][AE(i,0)(V(i)(42))AE(i,0)AE(Aya,1)(V(Aya)(hello))AE(Aya,1)]A("~Tum~"))DE("~Tum~",f)}D("~Sm~")"; - assert(ss == es); - - const dss = deserialize!(TestSerializer, typeof(s))(ss); - assert(dss == s); - - static struct T { @asArray S!(int, string) g; } - enum Tm = T.mangleof; - const t = T(s); - - const st = serialize!TestSerializer(t); - const et = "D("~Tm~"){DE("~Sm~",g)(A("~Sm~")[2][AE(i,0)(V(i)(42))AE(i,0)AE(Aya,1)(V(Aya)(hello))AE(Aya,1)]A("~Sm~"))DE("~Sm~",g)}D("~Tm~")"; - assert(st == et); - - const dst = deserialize!(TestSerializer, typeof(t))(st); - assert(dst == t); -} - -unittest { // named tuple serialization - import std.typecons : tuple; - - static struct I { - int i; - } - - static struct S { - int x; - string s_; - } - - static struct T { - @asArray - typeof(tuple!(FieldNameTuple!I)(I.init.tupleof)) tuple1AsArray; - - @name(fullyQualifiedName!I) - typeof(tuple!(FieldNameTuple!I)(I.init.tupleof)) tuple1AsDictionary; - - @asArray - typeof(tuple!(FieldNameTuple!S)(S.init.tupleof)) tuple2AsArray; - - @name(fullyQualifiedName!S) - typeof(tuple!(FieldNameTuple!S)(S.init.tupleof)) tuple2AsDictionary; - } - - const i = I(42); - const s = S(42, "hello"); - const T t = { i.tupleof, i.tupleof, s.tupleof, s.tupleof }; - - const st = serialize!TestSerializer(t); - - enum Tm = T.mangleof; - enum TuIm = typeof(T.tuple1AsArray).mangleof; - enum TuSm = typeof(T.tuple2AsArray).mangleof; - - const et = - "D("~Tm~")"~ - "{"~ - "DE("~TuIm~",tuple1AsArray)"~ - "("~ - "V(i)(42)"~ - ")"~ - "DE("~TuIm~",tuple1AsArray)"~ - "DE("~TuIm~","~fullyQualifiedName!I~")"~ - "("~ - "D("~TuIm~")"~ - "{"~ - "DE(i,i)"~ - "("~ - "V(i)(42)"~ - ")"~ - "DE(i,i)"~ - "}"~ - "D("~TuIm~")"~ - ")"~ - "DE("~TuIm~","~fullyQualifiedName!I~")"~ - "DE("~TuSm~",tuple2AsArray)"~ - "("~ - "A("~TuSm~")[2]"~ - "["~ - "AE(i,0)"~ - "("~ - "V(i)(42)"~ - ")"~ - "AE(i,0)"~ - "AE(Aya,1)"~ - "("~ - "V(Aya)(hello)"~ - ")"~ - "AE(Aya,1)"~ - "]"~ - "A("~TuSm~")"~ - ")"~ - "DE("~TuSm~",tuple2AsArray)"~ - "DE("~TuSm~","~fullyQualifiedName!S~")"~ - "("~ - "D("~TuSm~")"~ - "{"~ - "DE(i,x)"~ - "("~ - "V(i)(42)"~ - ")"~ - "DE(i,x)"~ - "DE(Aya,s)"~ - "("~ - "V(Aya)(hello)"~ - ")"~ - "DE(Aya,s)"~ - "}"~ - "D("~TuSm~")"~ - ")"~ - "DE("~TuSm~","~fullyQualifiedName!S~")"~ - "}"~ - "D("~Tm~")"; - assert(st == et); - - const dst = deserialize!(TestSerializer, typeof(t))(st); - assert(dst == t); -} - -unittest { // testing the various UDAs - enum E { hello, world } - enum Em = E.mangleof; - static struct S { - @byName E e; - @ignore int i; - @optional float f; - } - enum Sm = S.mangleof; - auto s = S(E.world, 42, 1.0f); - assert(serialize!TestSerializer(s) == - "D("~Sm~"){DE("~Em~",e)(V(Aya)(world))DE("~Em~",e)DE(f,f)(V(f)(1))DE(f,f)}D("~Sm~")"); -} - -unittest { // custom serialization support - // iso-ext - import std.datetime; - auto t = TimeOfDay(6, 31, 23); - assert(serialize!TestSerializer(t) == "V(Aya)(06:31:23)"); - auto d = Date(1964, 1, 23); - assert(serialize!TestSerializer(d) == "V(Aya)(1964-01-23)"); - auto dt = DateTime(d, t); - assert(serialize!TestSerializer(dt) == "V(Aya)(1964-01-23T06:31:23)"); - auto st = SysTime(dt, UTC()); - assert(serialize!TestSerializer(st) == "V(Aya)(1964-01-23T06:31:23Z)"); -} - -@safe unittest { // custom serialization support - // string - static struct S1 { int i; string toString() const @safe { return "hello"; } static S1 fromString(string) @safe { return S1.init; } } - static struct S2 { int i; string toString() const { return "hello"; } } - enum S2m = S2.mangleof; - static struct S3 { int i; static S3 fromString(string) { return S3.init; } } - enum S3m = S3.mangleof; - assert(serialize!TestSerializer(S1.init) == "V(Aya)(hello)"); - assert(serialize!TestSerializer(S2.init) == "D("~S2m~"){DE(i,i)(V(i)(0))DE(i,i)}D("~S2m~")"); - assert(serialize!TestSerializer(S3.init) == "D("~S3m~"){DE(i,i)(V(i)(0))DE(i,i)}D("~S3m~")"); - - // custom - static struct C1 { int i; float toRepresentation() const @safe { return 1.0f; } static C1 fromRepresentation(float f) @safe { return C1.init; } } - static struct C2 { int i; float toRepresentation() const { return 1.0f; } } - enum C2m = C2.mangleof; - static struct C3 { int i; static C3 fromRepresentation(float f) { return C3.init; } } - enum C3m = C3.mangleof; - assert(serialize!TestSerializer(C1.init) == "V(f)(1)"); - assert(serialize!TestSerializer(C2.init) == "D("~C2m~"){DE(i,i)(V(i)(0))DE(i,i)}D("~C2m~")"); - assert(serialize!TestSerializer(C3.init) == "D("~C3m~"){DE(i,i)(V(i)(0))DE(i,i)}D("~C3m~")"); -} - -unittest // Testing corner case: member function returning by ref -{ - import vibe.data.json; - - static struct S - { - int i; - ref int foo() return { return i; } - } - - static assert(__traits(compiles, { S().serializeToJson(); })); - static assert(__traits(compiles, { Json().deserializeJson!S(); })); - - auto s = S(1); - assert(s.serializeToJson().deserializeJson!S() == s); -} - -unittest // Testing corner case: Variadic template constructors and methods -{ - import vibe.data.json; - - static struct S - { - int i; - this(Args...)(Args args) {} - int foo(Args...)(Args args) { return i; } - ref int bar(Args...)(Args args) { return i; } - } - - static assert(__traits(compiles, { S().serializeToJson(); })); - static assert(__traits(compiles, { Json().deserializeJson!S(); })); - - auto s = S(1); - assert(s.serializeToJson().deserializeJson!S() == s); -} - -@safe unittest // Make sure serializing through properties still works -{ - import vibe.data.json; - - static struct S - { - @safe: - public int i; - private int privateJ; - - @property int j() @safe { return privateJ; } - @property void j(int j) @safe { privateJ = j; } - } - - auto s = S(1, 2); - assert(s.serializeToJson().deserializeJson!S() == s); -} - -@safe unittest // Immutable data deserialization -{ - import vibe.data.json; - - static struct S { - int a; - } - static class C { - immutable(S)[] arr; - } - - auto c = new C; - c.arr ~= S(10); - auto d = c.serializeToJson().deserializeJson!(immutable C); - static assert(is(typeof(d) == immutable C)); - assert(d.arr == c.arr); -} - -unittest { // test BitFlags serialization - import std.typecons : BitFlags; - - enum Flag { - a = 1<<0, - b = 1<<1, - c = 1<<2 - } - enum Flagm = Flag.mangleof; - - alias Flags = BitFlags!Flag; - enum Flagsm = Flags.mangleof; - - enum Fi_ser = "A("~Flagsm~")[0][]A("~Flagsm~")"; - assert(serialize!TestSerializer(Flags.init) == Fi_ser); - - enum Fac_ser = "A("~Flagsm~")[2][AE("~Flagm~",0)(V(i)(1))AE("~Flagm~",0)AE("~Flagm~",1)(V(i)(4))AE("~Flagm~",1)]A("~Flagsm~")"; - assert(serialize!TestSerializer(Flags(Flag.a, Flag.c)) == Fac_ser); - - struct S { @byName Flags f; } - enum Sm = S.mangleof; - enum Sac_ser = "D("~Sm~"){DE("~Flagsm~",f)(A("~Flagsm~")[2][AE("~Flagm~",0)(V(Aya)(a))AE("~Flagm~",0)AE("~Flagm~",1)(V(Aya)(c))AE("~Flagm~",1)]A("~Flagsm~"))DE("~Flagsm~",f)}D("~Sm~")"; - - assert(serialize!TestSerializer(S(Flags(Flag.a, Flag.c))) == Sac_ser); - - assert(deserialize!(TestSerializer, Flags)(Fi_ser) == Flags.init); - assert(deserialize!(TestSerializer, Flags)(Fac_ser) == Flags(Flag.a, Flag.c)); - assert(deserialize!(TestSerializer, S)(Sac_ser) == S(Flags(Flag.a, Flag.c))); -} - -@safe unittest { // issue #1182 - struct T { - int x; - string y; - } - struct S { - @asArray T t; - } - - auto s = S(T(42, "foo")); - enum Sm = S.mangleof; - enum Tm = T.mangleof; - enum s_ser = "D("~Sm~"){DE("~Tm~",t)(A("~Tm~")[2][AE(i,0)(V(i)(42))AE(i,0)AE(Aya,1)(V(Aya)(foo))AE(Aya,1)]A("~Tm~"))DE("~Tm~",t)}D("~Sm~")"; - - auto serialized = serialize!TestSerializer(s); - assert(serialized == s_ser, serialized); - assert(deserialize!(TestSerializer, S)(serialized) == s); -} - -@safe unittest { // issue #1352 - ingore per policy - struct P1 {} - struct P2 {} - - struct T { - @ignore int a = 5; - @ignore!P1 @ignore!P2 int b = 6; - @ignore!P1 c = 7; - int d = 8; - } - - auto t = T(1, 2, 3, 4); - auto Tm = T.mangleof; - auto t_ser_plain = "D("~Tm~"){DE(i,b)(V(i)(2))DE(i,b)DE(i,c)(V(i)(3))DE(i,c)DE(i,d)(V(i)(4))DE(i,d)}D("~Tm~")"; - auto t_ser_p1 = "D("~Tm~"){DE(i,d)(V(i)(4))DE(i,d)}D("~Tm~")"; - auto t_ser_p2 = "D("~Tm~"){DE(i,c)(V(i)(3))DE(i,c)DE(i,d)(V(i)(4))DE(i,d)}D("~Tm~")"; - - { - auto serialized_plain = serialize!TestSerializer(t); - assert(serialized_plain == t_ser_plain); - assert(deserialize!(TestSerializer, T)(serialized_plain) == T(5, 2, 3, 4)); - } - - { - auto serialized_p1 = serializeWithPolicy!(TestSerializer, P1)(t); - assert(serialized_p1 == t_ser_p1, serialized_p1); - assert(deserializeWithPolicy!(TestSerializer, P1, T)(serialized_p1) == T(5, 6, 7, 4)); - } - - { - auto serialized_p2 = serializeWithPolicy!(TestSerializer, P2)(t); - assert(serialized_p2 == t_ser_p2); - assert(deserializeWithPolicy!(TestSerializer, P2, T)(serialized_p2) == T(5, 6, 3, 4)); - } -} - -unittest { - import std.conv : to; - import std.string : toLower, toUpper; - - template P(T) if (is(T == enum)) { - @safe: - static string toRepresentation(T v) { return v.to!string.toLower(); } - static T fromRepresentation(string str) { return str.toUpper().to!T; } - } - - - enum E { - RED, - GREEN - } - - assert(P!E.fromRepresentation("green") == E.GREEN); - static assert(isPolicySerializable!(P, E)); - - auto ser_red = "V(Aya)(red)"; - assert(serializeWithPolicy!(TestSerializer, P)(E.RED) == ser_red, serializeWithPolicy!(TestSerializer, P)(E.RED)); - assert(deserializeWithPolicy!(TestSerializer, P, E)(ser_red) == E.RED); - - import vibe.data.json : Json, JsonSerializer; - assert(serializeWithPolicy!(JsonSerializer, P)(E.RED) == Json("red")); -} - -unittest { - static struct R { int y; } - static struct Custom { - @safe: - int x; - R toRepresentation() const { return R(x); } - static Custom fromRepresentation(R r) { return Custom(r.y); } - } - - auto c = Custom(42); - auto Rn = R.mangleof; - auto ser = serialize!TestSerializer(c); - assert(ser == "D("~Rn~"){DE(i,y)(V(i)(42))DE(i,y)}D("~Rn~")"); - auto deser = deserialize!(TestSerializer, Custom)(ser); - assert(deser.x == 42); -} - -unittest { - import std.typecons : Typedef; - alias T = Typedef!int; - auto ser = serialize!TestSerializer(T(42)); - assert(ser == "V(i)(42)", ser); - auto deser = deserialize!(TestSerializer, T)(ser); - assert(deser == 42); -} - -@safe unittest { - static struct Foo { Foo[] foos; } - Foo f; - string ser = serialize!TestSerializer(f); - assert(deserialize!(TestSerializer, Foo)(ser) == f); -} - -@system unittest { - static struct SystemSerializer { - TestSerializer ser; - alias ser this; - this(string s) { ser.result = s; } - T readValue(Traits, T)() @system { return ser.readValue!(Traits, T); } - void writeValue(Traits, T)(T value) @system { ser.writeValue!(Traits, T)(value); } - void readDictionary(Traits)(scope void delegate(string) @system entry_callback) { return ser.readDictionary!Traits((s) @trusted { entry_callback(s); }); } - void readArray(Traits)(scope void delegate(size_t) @system size_callback, scope void delegate() @system entry_callback) { ser.readArray!Traits((s) @trusted { size_callback(s); }, () @trusted { entry_callback(); }); } - } - - static struct Bar { Bar[] foos; int i; } - Bar f; - string ser = serialize!SystemSerializer(f); - assert(deserialize!(SystemSerializer, Bar)(ser) == f); -} - -@safe unittest { - static struct S { @name("+foo") int bar; } - auto Sn = S.mangleof; - auto s = S(42); - string ser = serialize!TestSerializer(s); - assert(ser == "D("~Sn~"){DE(i,+foo)(V(i)(42))DE(i,+foo)}D("~Sn~")", ser); - auto deser = deserialize!(TestSerializer, S)(ser); - assert(deser.bar == 42); -} - -@safe unittest { - static struct S { int bar_; } - auto Sn = S.mangleof; - auto s = S(42); - string ser = serialize!TestSerializer(s); - assert(ser == "D("~Sn~"){DE(i,bar)(V(i)(42))DE(i,bar)}D("~Sn~")", ser); - auto deser = deserialize!(TestSerializer, S)(ser); - assert(deser.bar_ == 42); -} - -@safe unittest { // issue 1941 - static struct Bar { Bar[] foos; int i; } - Bar b1 = {[{null, 2}], 1}; - auto s = serialize!TestSerializer(b1); - auto b = deserialize!(TestSerializer, Bar)(s); - assert(b.i == 1); - assert(b.foos.length == 1); - assert(b.foos[0].i == 2); -} - -unittest { // issue 1991 - @system property getters/setters does not compile - static class A { - @safe: - @property @name("foo") { - string fooString() const { return "a"; } - void fooString(string a) { } - } - } - - auto a1 = new A; - auto b = serialize!TestSerializer(a1); - auto a2 = deserialize!(TestSerializer, A)(b); -} - -unittest { // issue #2110 - single-element tuples - static struct F { int field; } - - { - static struct S { typeof(F.init.tupleof) fields; } - auto b = serialize!TestSerializer(S(42)); - auto a = deserialize!(TestSerializer, S)(b); - assert(a.fields[0] == 42); - } - - { - static struct T { @asArray typeof(F.init.tupleof) fields; } - auto b = serialize!TestSerializer(T(42)); - auto a = deserialize!(TestSerializer, T)(b); - assert(a.fields[0] == 42); - } -} - -@safe unittest { - import std.typecons : Nullable; - - struct S { - @embedNullable Nullable!int x; - @embedNullable Nullable!string s; - } - - enum Sn = S.mangleof; - - auto s = S(Nullable!int(3), Nullable!string.init); - auto expected = "D("~Sn~"){DE(i,x)(V(i)(3))DE(i,x)}D("~Sn~")"; - - assert(serialize!TestSerializer(s) == expected, serialize!TestSerializer(s)); - assert(deserialize!(TestSerializer, S)(expected) == s); - - s.s = "hello"; - expected = "D("~Sn~"){DE(i,x)(V(i)(3))DE(i,x)DE(Aya,s)(V(Aya)(hello))DE(Aya,s)}D("~Sn~")"; - assert(serialize!TestSerializer(s) == expected, serialize!TestSerializer(s)); - assert(deserialize!(TestSerializer, S)(expected) == s); - - s.x.nullify(); - expected = "D("~Sn~"){DE(Aya,s)(V(Aya)(hello))DE(Aya,s)}D("~Sn~")"; - assert(serialize!TestSerializer(s) == expected); - assert(deserialize!(TestSerializer, S)(expected) == s); - - s.s.nullify(); - expected = "D("~Sn~"){}D("~Sn~")"; - assert(serialize!TestSerializer(s) == expected); - assert(deserialize!(TestSerializer, S)(expected) == s); -} - -unittest { - import std.conv : ConvException; - import std.exception : assertThrown, assertNotThrown; - - enum Testable : string { - foo = "foo", - bar = "bar", - baz = "bar" - } - - Testable deserializeString(string value) { - return deserialize!(TestSerializer, Testable)("V(Aya)(" ~ value ~ ")"); - } - - foreach (string val; ["foo", "bar"]) - assert(deserializeString(val) == val.to!Testable); - - assertThrown!ConvException(deserializeString("foobar")); -} - -unittest { - import std.conv : ConvException; - import std.exception : assertThrown, assertNotThrown; - - enum Foo { - foobar - } - - struct Testable { - @byName - Foo bar; - } - - void deserializeString(string value) { - auto d = "D(" ~ Testable.mangleof ~ ")"; - auto de = "DE(" ~ Foo.mangleof ~ ",bar)"; - deserialize!(TestSerializer, Testable)(d ~ "{" ~ de ~ "(V(Aya)(" ~ value ~ "))" ~ de ~ "}" ~ d); - } - - assertNotThrown(deserializeString("foobar")); - assertThrown!ConvException(deserializeString("baz")); -} diff --git a/data/vibe/internal/meta/codegen.d b/data/vibe/internal/meta/codegen.d deleted file mode 100644 index 1396881254..0000000000 --- a/data/vibe/internal/meta/codegen.d +++ /dev/null @@ -1,409 +0,0 @@ -/** - Templates and CTFE-functions useful for type introspection during code generation. - - Some of those are very similar to `traits` utilities but instead of general type - information focus on properties that are most important during such code generation. - - Copyright: © 2013 Sönke Ludwig - License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. - Authors: Sönke Ludwig, Михаил Страшун -*/ - -module vibe.internal.meta.codegen; - -import std.traits : FunctionTypeOf, isSomeFunction; - -/* - As user types defined inside unittest blocks don't have proper parent - module, those need to be defined outside for tests that require module - inspection for some reasons. All such tests use single declaration - compiled in this module in unittest version. -*/ -version(unittest) -{ - private: - interface TestInterface - { - static struct Inner - { - } - - const(Inner[]) func1(ref string name); - ref int func1(); - shared(Inner[4]) func2(...) const; - immutable(int[string]) func3(in Inner anotherName) @safe; - } -} - -/** - For a given type T finds all user-defined symbols it embeds. - - Important property of such symbols is that they are likely to - need an explicit import if used in some other scope / module. - - Implementation is incomplete and tuned for REST interface generation needs. - - Params: - T = type to introspect for qualified symbols - - Returns: - tuple of "interesting" symbols, no duplicates -*/ -template getSymbols(T) -{ - import std.typetuple : TypeTuple, NoDuplicates, staticMap; - import std.traits; - - private template Implementation(T) - { - static if (is(T == U!V, alias U, V)) { // single-argument template support - alias Implementation = TypeTuple!(U, Implementation!V); - } - else static if (isAggregateType!T || is(T == enum)) { - alias Implementation = Unqual!T; - } - else static if (isStaticArray!T || isArray!T) { - alias Implementation = Implementation!(Unqual!(typeof(T.init[0]))); - } - else static if (isAssociativeArray!T) { - alias Implementation = TypeTuple!( - Implementation!(Unqual!(ValueType!T)), - Implementation!(Unqual!(KeyType!T)) - ); - } - else static if (isPointer!T) { - alias Implementation = Implementation!(Unqual!(PointerTarget!T)); - } - else - alias Implementation = TypeTuple!(); - } - - alias getSymbols = NoDuplicates!(Implementation!T); -} - -/// -unittest -{ - import std.typetuple : TypeTuple; - - struct A {} - interface B {} - alias Type = A[const(B[A*])]; - struct C(T) {} - - // can't directly compare tuples thus comparing their string representation - static assert (getSymbols!Type.stringof == TypeTuple!(A, B).stringof); - static assert (getSymbols!int.stringof == TypeTuple!().stringof); - static assert (getSymbols!(C!A).stringof == TypeTuple!(C, A).stringof); -} - -/** - For a given interface I finds all modules that types in its methods - come from. - - These modules need to be imported in the scope code generated from I - is used to avoid errors with unresolved symbols for user types. - - Params: - I = interface to inspect - - Returns: - list of module name strings, no duplicates -*/ -string[] getRequiredImports(I)() - if (is(I == interface)) -{ - import std.traits : MemberFunctionsTuple, moduleName, - ParameterTypeTuple, ReturnType; - - if( !__ctfe ) - assert(false); - - bool[string] visited; - string[] ret; - - void addModule(string name) - { - if (name !in visited) { - ret ~= name; - visited[name] = true; - } - } - - foreach (method; __traits(allMembers, I)) { - // WORKAROUND #1045 / @@BUG14375@@ - static if (method.length != 0) - foreach (overload; MemberFunctionsTuple!(I, method)) { - alias FuncType = FunctionTypeOf!overload; - - foreach (symbol; getSymbols!(ReturnType!FuncType)) { - static if (__traits(compiles, moduleName!symbol)) { - addModule(moduleName!symbol); - } - } - - foreach (P; ParameterTypeTuple!FuncType) { - foreach (symbol; getSymbols!P) { - static if (__traits(compiles, moduleName!symbol)) { - addModule(moduleName!symbol); - } - } - } - } - } - - return ret; -} - -/// -unittest -{ - // `Test` is an interface using single user type - enum imports = getRequiredImports!TestInterface; - static assert (imports.length == 1); - static assert (imports[0] == "vibe.internal.meta.codegen"); -} - -/** - * Returns a Tuple of the parameters. - * It can be used to declare function. - */ -template ParameterTuple(alias Func) -{ - static if (is(FunctionTypeOf!Func Params == __parameters)) { - alias ParameterTuple = Params; - } else static assert(0, "Argument to ParameterTuple must be a function"); -} - -/// -unittest -{ - void foo(string val = "Test", int = 10); - void bar(ParameterTuple!foo) { assert(val == "Test"); } - // Variadic functions require special handling: - import core.vararg; - void foo2(string val, ...); - void bar2(ParameterTuple!foo2, ...) { assert(val == "42"); } - - bar(); - bar2("42"); - - // Note: outside of a parameter list, it's value is the type of the param. - import std.traits : ParameterDefaultValueTuple; - ParameterTuple!(foo)[0] test = ParameterDefaultValueTuple!(foo)[0]; - assert(test == "Test"); -} - -/// Returns a Tuple containing a 1-element parameter list, with an optional default value. -/// Can be used to concatenate a parameter to a parameter list, or to create one. -template ParameterTuple(T, string identifier, DefVal : void = void) -{ - import std.string : format; - mixin(q{private void __func(T %s);}.format(identifier)); - alias ParameterTuple = ParameterTuple!__func; -} - - -/// Ditto -template ParameterTuple(T, string identifier, alias DefVal) -{ - import std.string : format; - mixin(q{private void __func(T %s = DefVal);}.format(identifier)); - alias ParameterTuple = ParameterTuple!__func; -} - -/// -unittest -{ - void foo(ParameterTuple!(int, "arg2")) { assert(arg2 == 42); } - foo(42); - - void bar(string arg); - void bar2(ParameterTuple!bar, ParameterTuple!(string, "val")) { assert(val == arg); } - bar2("isokay", "isokay"); - - // For convenience, you can directly pass the result of std.traits.ParameterDefaultValueTuple - // without checking for void. - import std.traits : PDVT = ParameterDefaultValueTuple; - import std.traits : arity; - void baz(string test, int = 10); - - static assert(is(PDVT!(baz)[0] == void)); - // void baz2(string test2, string test); - void baz2(ParameterTuple!(string, "test2", PDVT!(baz)[0]), ParameterTuple!(baz)[0..$-1]) { assert(test == test2); } - static assert(arity!baz2 == 2); - baz2("Try", "Try"); - - // void baz3(string test, int = 10, int ident = 10); - void baz3(ParameterTuple!baz, ParameterTuple!(int, "ident", PDVT!(baz)[1])) { assert(ident == 10); } - baz3("string"); - - import std.datetime; - void baz4(ParameterTuple!(SysTime, "epoch", Clock.currTime)) { assert((Clock.currTime - epoch) < 30.seconds); } - baz4(); - - // Convertion are possible for default parameters... - alias baz5PT = ParameterTuple!(SysTime, "epoch", uint.min); - - // However this blows up because of @@bug 14369@@ - // alias baz6PT = ParameterTuple!(SysTime, "epoch", PDVT!(baz4)[0])); - - alias baz7PT = ParameterTuple!(SysTime, "epoch", uint.max); - // Non existing convertion are detected. - static assert(!__traits(compiles, { alias baz7PT = ParameterTuple!(SysTime, "epoch", Object.init); })); - // And types are refused - static assert(!__traits(compiles, { alias baz7PT = ParameterTuple!(SysTime, "epoch", uint); })); -} - -/// Returns a string of the functions attributes, suitable to be mixed -/// on the LHS of the function declaration. -/// -/// Unfortunately there is no "nice" syntax for declaring a function, -/// so we have to resort on string for functions attributes. -template FuncAttributes(alias Func) -{ - import std.array : join; - enum FuncAttributes = [__traits(getFunctionAttributes, Func)].join(" "); -} - - - -/// A template mixin which allow you to clone a function, and specify the implementation. -/// -/// Params: -/// Func = An alias to the function to copy. -/// body_ = The implementation of the class which will be mixed in. -/// keepUDA = Whether or not to copy UDAs. Since the primary use case for this template -/// is implementing classes from interface, this defaults to $(D false). -/// identifier = The identifier to give to the new function. Default to the identifier of -/// $(D Func). -/// -/// See_Also: $(D CloneFunctionDecl) to clone a prototype. -mixin template CloneFunction(alias Func, string body_, bool keepUDA = false, string identifier = __traits(identifier, Func)) -{ - // Template mixin: everything has to be self-contained. - import std.format : format; - import std.traits : ReturnType, variadicFunctionStyle, Variadic; - import std.typetuple : TypeTuple; - import vibe.internal.meta.codegen : ParameterTuple, FuncAttributes; - // Sadly this is not possible: - // class Test { - // int foo(string par) pure @safe nothrow { /* ... */ } - // typeof(foo) bar { - // return foo(par); - // } - // } - static if (keepUDA) - private alias UDA = TypeTuple!(__traits(getAttributes, Func)); - else - private alias UDA = TypeTuple!(); - static if (variadicFunctionStyle!Func == Variadic.no) { - mixin(q{ - @(UDA) ReturnType!Func %s(ParameterTuple!Func) %s { - %s - } - }.format(identifier, FuncAttributes!Func, body_)); - } else static if (variadicFunctionStyle!Func == Variadic.typesafe) { - mixin(q{ - @(UDA) ReturnType!Func %s(ParameterTuple!Func...) %s { - %s - } - }.format(identifier, FuncAttributes!Func, body_)); - } else - static assert(0, "Variadic style " ~ variadicFunctionStyle!Func.stringof - ~ " not implemented."); -} - -/// -unittest -{ - import std.typetuple : TypeTuple; - - interface ITest - { - @("42") int foo(string par, int, string p = "foo", int = 10) pure @safe nothrow const; - @property int foo2() pure @safe nothrow const; - // Issue #1144 - void variadicFun(ref size_t bar, string[] args...); - // Gives weird error message, not supported so far - //bool variadicDFun(...); - } - - class Test : ITest - { - mixin CloneFunction!(ITest.foo, q{return 84;}, false, "customname"); - override: - mixin CloneFunction!(ITest.foo, q{return 42;}, true); - mixin CloneFunction!(ITest.foo2, q{return 42;}); - mixin CloneFunction!(ITest.variadicFun, q{bar = args.length;}); - //mixin CloneFunction!(ITest.variadicDFun, q{return true;}); - } - - // UDA tests - static assert(__traits(getAttributes, Test.customname).length == 0); - static assert(__traits(getAttributes, Test.foo2).length == 0); - static assert(__traits(getAttributes, Test.foo) == TypeTuple!("42")); - - assert(new Test().foo("", 21) == 42); - assert(new Test().foo2 == 42); - assert(new Test().customname("", 21) == 84); - - size_t l; - new Test().variadicFun(l, "Hello", "variadic", "world"); - assert(l == 3); - - //assert(new Test().variadicDFun("Hello", "again", "variadic", "world")); -} - -/// A template mixin which allow you to clone a function declaration -/// -/// Params: -/// Func = An alias to the function to copy. -/// keepUDA = Whether or not to copy UDAs. Since the primary use case for this template -/// is copying a definition, this defaults to $(D true). -/// identifier = The identifier to give to the new function. Default to the identifier of -/// $(D Func). -/// -/// See_Also : $(D CloneFunction) to implement a function. -mixin template CloneFunctionDecl(alias Func, bool keepUDA = true, string identifier = __traits(identifier, Func)) -{ - // Template mixin: everything has to be self-contained. - import std.format : format; - import std.traits : ReturnType, variadicFunctionStyle, Variadic; - import std.typetuple : TypeTuple; - import vibe.internal.meta.codegen : ParameterTuple, FuncAttributes; - - static if (keepUDA) - private enum UDA = q{@(TypeTuple!(__traits(getAttributes, Func)))}; - else - private enum UDA = ""; - - static if (variadicFunctionStyle!Func == Variadic.no) { - mixin(q{ - %s ReturnType!Func %s(ParameterTuple!Func) %s; - }.format(UDA, identifier, FuncAttributes!Func)); - } else static if (variadicFunctionStyle!Func == Variadic.typesafe) { - mixin(q{ - %s ReturnType!Func %s(ParameterTuple!Func...) %s; - }.format(UDA, identifier, FuncAttributes!Func)); - } else - static assert(0, "Variadic style " ~ variadicFunctionStyle!Func.stringof - ~ " not implemented."); - -} - -/// -unittest { - import std.typetuple : TypeTuple; - - enum Foo; - interface IUDATest { - @(Foo, "forty-two", 42) const(Object) bar() @safe; - } - interface UDATest { - mixin CloneFunctionDecl!(IUDATest.bar); - } - // Tuples don't like when you compare types using '=='. - static assert(is(TypeTuple!(__traits(getAttributes, UDATest.bar))[0] == Foo)); - static assert(__traits(getAttributes, UDATest.bar)[1 .. $] == TypeTuple!("forty-two", 42)); -} diff --git a/data/vibe/internal/meta/funcattr.d b/data/vibe/internal/meta/funcattr.d deleted file mode 100644 index fede2e1303..0000000000 --- a/data/vibe/internal/meta/funcattr.d +++ /dev/null @@ -1,917 +0,0 @@ -/** - Helpers for working with user-defined attributes that can be attached to - function or method to modify its behavior. In some sense those are similar to - Python decorator. D does not support this feature natively but - it can be emulated within certain code generation framework. - - Copyright: © 2013 Sönke Ludwig - License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. - Authors: Михаил Страшун - */ - -module vibe.internal.meta.funcattr; - -import std.traits : isInstanceOf, ReturnType; -import vibe.internal.meta.traits : RecursiveFunctionAttributes; - -/// example -unittest -{ - struct Context - { - int increment; - string token; - bool updated = false; - } - - static int genID(Context* context) - { - static int id = 0; - return (id += context.increment); - } - - static string update(string result, Context* context) - { - context.updated = true; - return result ~ context.token; - } - - class API - { - @before!genID("id") @after!update() - string handler(int id, string name, string text) - { - import std.string : format; - - return format("[%s] %s : %s", id, name, text); - } - } - - auto api = new API(); - auto context = new Context(5, " | token"); - auto funcattr = createAttributedFunction!(API.handler)(context); - auto result = funcattr(&api.handler, "Developer", "Hello, World!"); - - assert (result == "[5] Developer : Hello, World! | token"); - assert (context.updated); -} - -/** - Marks function/method for usage with `AttributedFunction`. - - Former will call a Hook before calling attributed function/method and - provide its return value as input parameter. - - Params: - Hook = function/method symbol to run before attributed function/method - parameter_name = name in attributed function/method parameter list to bind result to - - Returns: - internal attribute struct that embeds supplied information -*/ -auto before(alias Hook)(string parameter_name) -{ - return InputAttribute!Hook(parameter_name); -} - -/// -unittest -{ - int genID() { return 42; } - - @before!genID("id") - void foo(int id, double something) {} -} - -/** - Marks function/method for usage with `AttributedFunction`. - - Former will call a Hook after calling attributed function/method and provide - its return value as a single input parameter for a Hook. - - There can be only one "after"-attribute attached to a single symbol. - - Params: - Hook = function/method symbol to run after attributed function/method - - Returns: - internal attribute struct that embeds supplied information -*/ -@property auto after(alias Hook)() -{ - return OutputAttribute!Hook(); -} - -/// -unittest -{ - auto filter(int result) - { - return result; - } - - @after!filter() - int foo() { return 42; } -} -/** - Checks if parameter is calculated by one of attached - functions. - - Params: - Function = function symbol to query for attributes - name = parameter name to check - - Returns: - `true` if it is calculated -*/ -template IsAttributedParameter(alias Function, string name) -{ - import std.traits : FunctionTypeOf; - - static assert (is(FunctionTypeOf!Function)); - - private { - alias Data = AttributedParameterMetadata!Function; - - template Impl(T...) - { - static if (T.length == 0) { - enum Impl = false; - } - else { - static if (T[0].name == name) { - enum Impl = true; - } - else { - enum Impl = Impl!(T[1..$]); - } - } - } - } - - enum IsAttributedParameter = Impl!Data; -} - -template HasFuncAttributes(alias Func) -{ - import std.typetuple; - enum HasFuncAttributes = (anySatisfy!(isOutputAttribute, __traits(getAttributes, Func)) - || anySatisfy!(isInputAttribute, __traits(getAttributes, Func))); -} - -unittest { - string foo() { return "Hello"; } - string bar(int) { return foo(); } - - @before!foo("b") void baz1(string b) {} - @after!bar() string baz2() { return "Hi"; } - @before!foo("b") @after!bar() string baz3(string b) { return "Hi"; } - - static assert (HasFuncAttributes!baz1); - static assert (HasFuncAttributes!baz2); - static assert (HasFuncAttributes!baz3); - - string foobar1(string b) { return b; } - @("Irrelevant", 42) string foobar2(string b) { return b; } - - static assert (!HasFuncAttributes!foobar1); - static assert (!HasFuncAttributes!foobar2); -} - -/** - Computes the given attributed parameter using the corresponding @before modifier. -*/ -auto computeAttributedParameter(alias FUNCTION, string NAME, ARGS...)(ARGS args) -{ - import std.meta : Filter; - static assert(IsAttributedParameter!(FUNCTION, NAME), "Missing @before attribute for parameter "~NAME); - alias input_attributes = Filter!(isInputAttribute, RecursiveFunctionAttributes!FUNCTION); - foreach (att; input_attributes) - static if (att.parameter == NAME) { - return att.evaluator(args); - } - assert(false); -} - - -/** - Computes the given attributed parameter using the corresponding @before modifier. - - This overload tries to invoke the given function as a member of the $(D ctx) - parameter. It also supports accessing private member functions using the - $(D PrivateAccessProxy) mixin. -*/ -auto computeAttributedParameterCtx(alias FUNCTION, string NAME, T, ARGS...)(T ctx, ARGS args) -{ - import std.meta : AliasSeq, Filter; - static assert(IsAttributedParameter!(FUNCTION, NAME), "Missing @before attribute for parameter "~NAME); - alias input_attributes = Filter!(isInputAttribute, RecursiveFunctionAttributes!FUNCTION); - foreach (att; input_attributes) - static if (att.parameter == NAME) { - static if (!__traits(isStaticFunction, att.evaluator)) { - static if (is(typeof(ctx.invokeProxy__!(att.evaluator)(args)))) - return ctx.invokeProxy__!(att.evaluator)(args); - else return __traits(getMember, ctx, __traits(identifier, att.evaluator))(args); - } else { - return att.evaluator(args); - } - } - assert(false); -} - - -/** - Helper mixin to support private member functions for $(D @before) attributes. -*/ -mixin template PrivateAccessProxy() { - auto invokeProxy__(alias MEMBER, ARGS...)(ARGS args) { return MEMBER(args); } -} -/// -unittest { - class MyClass { - @before!computeParam("param") - void method(bool param) - { - assert(param == true); - } - - private bool computeParam() - { - return true; - } - } -} - - -/** - Processes the function return value using all @after modifiers. -*/ -ReturnType!FUNCTION evaluateOutputModifiers(alias FUNCTION, ARGS...)(ReturnType!FUNCTION result, ARGS args) -{ - import std.string : format; - import std.traits : ParameterTypeTuple, ReturnType, fullyQualifiedName; - import std.typetuple : Filter; - import vibe.internal.meta.typetuple : Compare, Group; - - alias output_attributes = Filter!(isOutputAttribute, RecursiveFunctionAttributes!FUNCTION); - foreach (OA; output_attributes) { - import std.typetuple : TypeTuple; - - static assert ( - Compare!( - Group!(ParameterTypeTuple!(OA.modificator)), - Group!(ReturnType!FUNCTION, ARGS) - ), - format( - "Output attribute function '%s%s' argument list " ~ - "does not match provided argument list %s", - fullyQualifiedName!(OA.modificator), - ParameterTypeTuple!(OA.modificator).stringof, - TypeTuple!(ReturnType!FUNCTION, ARGS).stringof - ) - ); - - result = OA.modificator(result, args); - } - return result; -} - -/// -unittest -{ - int foo() - { - return 42; - } - - @before!foo("name1") - void bar(int name1, double name2) - { - } - - static assert (IsAttributedParameter!(bar, "name1")); - static assert (!IsAttributedParameter!(bar, "name2")); - static assert (!IsAttributedParameter!(bar, "oops")); -} - -// internal attribute definitions -private { - - struct InputAttribute(alias Function) - { - alias evaluator = Function; - string parameter; - } - - struct OutputAttribute(alias Function) - { - alias modificator = Function; - } - - template isInputAttribute(T...) - { - enum isInputAttribute = (T.length == 1) && !is(T[0]) && isInstanceOf!(InputAttribute, typeof(T[0])); - } - - unittest - { - void foo() {} - - enum correct = InputAttribute!foo("name"); - enum wrong = OutputAttribute!foo(); - - static assert (isInputAttribute!correct); - static assert (!isInputAttribute!wrong); - } - - template isOutputAttribute(T...) - { - enum isOutputAttribute = (T.length == 1) && isInstanceOf!(OutputAttribute, typeof(T[0])); - } - - unittest - { - void foo() {} - - enum correct = OutputAttribute!foo(); - enum wrong = InputAttribute!foo("name"); - - static assert (isOutputAttribute!correct); - static assert (!isOutputAttribute!wrong); - } -} - -// tools to operate on InputAttribute tuple -private { - - // stores metadata for single InputAttribute "effect" - struct Parameter - { - // evaluated parameter name - string name; - // that parameter index in attributed function parameter list - int index; - // fully qualified return type of attached function - string type; - // for non-basic types - module to import - string origin; - } - - /** - Used to accumulate various parameter-related metadata in one - tuple in one go. - - Params: - Function = attributed functon / method symbol - - Returns: - TypeTuple of Parameter instances, one for every Function - parameter that will be evaluated from attributes. - */ - template AttributedParameterMetadata(alias Function) - { - import std.array : join; - import std.typetuple : Filter, staticMap, staticIndexOf; - import std.traits : ParameterIdentifierTuple, ReturnType, - fullyQualifiedName, moduleName; - - private alias attributes = Filter!( - isInputAttribute, - RecursiveFunctionAttributes!Function - ); - - private alias parameter_names = ParameterIdentifierTuple!Function; - - /* - Creates single Parameter instance. Used in pair with - staticMap. - */ - template BuildParameter(alias attribute) - { - enum name = attribute.parameter; - - static assert ( - is (ReturnType!(attribute.evaluator)) && !(is(ReturnType!(attribute.evaluator) == void)), - "hook functions attached for usage with `AttributedFunction` " ~ - "must have a return type" - ); - - static if (is(typeof(moduleName!(ReturnType!(attribute.evaluator))))) { - enum origin = moduleName!(ReturnType!(attribute.evaluator)); - } - else { - enum origin = ""; - } - - enum BuildParameter = Parameter( - name, - staticIndexOf!(name, parameter_names), - fullyQualifiedName!(ReturnType!(attribute.evaluator)), - origin - ); - - import std.string : format; - - static assert ( - BuildParameter.index >= 0, - format( - "You are trying to attach function result to parameter '%s' " ~ - "but there is no such parameter for '%s(%s)'", - name, - fullyQualifiedName!Function, - join([ parameter_names ], ", ") - ) - ); - } - - alias AttributedParameterMetadata = staticMap!(BuildParameter, attributes); - } - - // no false attribute detection - unittest - { - @(42) void foo() {} - static assert (AttributedParameterMetadata!foo.length == 0); - } - - // does not compile for wrong attribute data - unittest - { - int attached1() { return int.init; } - void attached2() {} - - @before!attached1("doesnotexist") - void bar(int param) {} - - @before!attached2("param") - void baz(int param) {} - - // wrong name - static assert (!__traits(compiles, AttributedParameterMetadata!bar)); - // no return type - static assert (!__traits(compiles, AttributedParameterMetadata!baz)); - } - - // generates expected tuple for valid input - unittest - { - int attached1() { return int.init; } - double attached2() { return double.init; } - - @before!attached1("two") @before!attached2("three") - void foo(string one, int two, double three) {} - - alias result = AttributedParameterMetadata!foo; - static assert (result.length == 2); - static assert (result[0] == Parameter("two", 1, "int")); - static assert (result[1] == Parameter("three", 2, "double")); - } - - /** - Combines types from arguments of initial `AttributedFunction` call - with parameters (types) injected by attributes for that call. - - Used to verify that resulting argument list can be passed to underlying - attributed function. - - Params: - ParameterMeta = Group of Parameter instances for extra data to add into argument list - ParameterList = Group of types from initial argument list - - Returns: - type tuple of expected combined function argument list - */ - template MergeParameterTypes(alias ParameterMeta, alias ParameterList) - { - import vibe.internal.meta.typetuple : isGroup, Group; - - static assert (isGroup!ParameterMeta); - static assert (isGroup!ParameterList); - - static if (ParameterMeta.expand.length) { - enum Parameter meta = ParameterMeta.expand[0]; - - static assert (meta.index <= ParameterList.expand.length); - static if (meta.origin != "") { - mixin("static import " ~ meta.origin ~ ";"); - } - mixin("alias type = " ~ meta.type ~ ";"); - - alias PartialResult = Group!( - ParameterList.expand[0..meta.index], - type, - ParameterList.expand[meta.index..$] - ); - - alias MergeParameterTypes = MergeParameterTypes!( - Group!(ParameterMeta.expand[1..$]), - PartialResult - ); - } - else { - alias MergeParameterTypes = ParameterList.expand; - } - } - - // normal - unittest - { - import vibe.internal.meta.typetuple : Group, Compare; - - alias meta = Group!( - Parameter("one", 2, "int"), - Parameter("two", 3, "string") - ); - - alias initial = Group!( double, double, double ); - - alias merged = Group!(MergeParameterTypes!(meta, initial)); - - static assert ( - Compare!(merged, Group!(double, double, int, string, double)) - ); - } - - // edge - unittest - { - import vibe.internal.meta.typetuple : Group, Compare; - - alias meta = Group!( - Parameter("one", 3, "int"), - Parameter("two", 4, "string") - ); - - alias initial = Group!( double, double, double ); - - alias merged = Group!(MergeParameterTypes!(meta, initial)); - - static assert ( - Compare!(merged, Group!(double, double, double, int, string)) - ); - } - - // out-of-index - unittest - { - import vibe.internal.meta.typetuple : Group; - - alias meta = Group!( - Parameter("one", 20, "int"), - ); - - alias initial = Group!( double ); - - static assert ( - !__traits(compiles, MergeParameterTypes!(meta, initial)) - ); - } - -} - -/** - Entry point for `funcattr` API. - - Helper struct that takes care of calling given Function in a such - way that part of its arguments are evalutated by attached input attributes - (see `before`) and output gets post-processed by output attribute - (see `after`). - - One such structure embeds single attributed function to call and - specific argument type list that can be passed to attached functions. - - Params: - Function = attributed function - StoredArgTypes = Group of argument types for attached functions - -*/ -struct AttributedFunction(alias Function, alias StoredArgTypes) -{ - import std.traits : isSomeFunction, ReturnType, FunctionTypeOf, - ParameterTypeTuple, ParameterIdentifierTuple; - import vibe.internal.meta.typetuple : Group, isGroup, Compare; - import std.functional : toDelegate; - import std.typetuple : Filter; - - static assert (isGroup!StoredArgTypes); - static assert (is(FunctionTypeOf!Function)); - - /** - Stores argument tuple for attached function calls - - Params: - args = tuple of actual argument values - */ - void storeArgs(StoredArgTypes.expand args) - { - m_storedArgs = args; - } - - /** - Used to invoke configured function/method with - all attached attribute functions. - - As aliased method symbols can't be called without - the context, explicit providing of delegate to call - is required - - Params: - dg = delegated created from function / method to call - args = list of arguments to dg not provided by attached attribute function - - Return: - proxies return value of dg - */ - ReturnType!Function opCall(T...)(FunctionDg dg, T args) - { - import std.traits : fullyQualifiedName; - import std.string : format; - - enum hasReturnType = is(ReturnType!Function) && !is(ReturnType!Function == void); - - static if (hasReturnType) { - ReturnType!Function result; - } - - // check that all attached functions have conforming argument lists - foreach (uda; input_attributes) { - static assert ( - Compare!( - Group!(ParameterTypeTuple!(uda.evaluator)), - StoredArgTypes - ), - format( - "Input attribute function '%s%s' argument list " ~ - "does not match provided argument list %s", - fullyQualifiedName!(uda.evaluator), - ParameterTypeTuple!(uda.evaluator).stringof, - StoredArgTypes.expand.stringof - ) - ); - } - - static if (hasReturnType) { - result = prepareInputAndCall(dg, args); - } - else { - prepareInputAndCall(dg, args); - } - - static assert ( - output_attributes.length <= 1, - "Only one output attribute (@after) is currently allowed" - ); - - static if (output_attributes.length) { - import std.typetuple : TypeTuple; - - static assert ( - Compare!( - Group!(ParameterTypeTuple!(output_attributes[0].modificator)), - Group!(ReturnType!Function, StoredArgTypes.expand) - ), - format( - "Output attribute function '%s%s' argument list " ~ - "does not match provided argument list %s", - fullyQualifiedName!(output_attributes[0].modificator), - ParameterTypeTuple!(output_attributes[0].modificator).stringof, - TypeTuple!(ReturnType!Function, StoredArgTypes.expand).stringof - ) - ); - - static if (hasReturnType) { - result = output_attributes[0].modificator(result, m_storedArgs); - } - else { - output_attributes[0].modificator(m_storedArgs); - } - } - - static if (hasReturnType) { - return result; - } - } - - /** - Convenience wrapper tha creates stub delegate for free functions. - - As those do not require context, passing delegate explicitly is not - required. - */ - ReturnType!Function opCall(T...)(T args) - if (!is(T[0] == delegate)) - { - return this.opCall(toDelegate(&Function), args); - } - - private { - // used as an argument tuple when function attached - // to InputAttribute is called - StoredArgTypes.expand m_storedArgs; - - // used as input type for actual function pointer so - // that both free functions and methods can be supplied - alias FunctionDg = typeof(toDelegate(&Function)); - - // information about attributed function arguments - alias ParameterTypes = ParameterTypeTuple!Function; - alias parameter_names = ParameterIdentifierTuple!Function; - - // filtered UDA lists - alias input_attributes = Filter!(isInputAttribute, __traits(getAttributes, Function)); - alias output_attributes = Filter!(isOutputAttribute, __traits(getAttributes, Function)); - } - - private { - - /** - Does all the magic necessary to prepare argument list for attributed - function based on `input_attributes` and `opCall` argument list. - - Catches all name / type / size mismatch erros in that domain via - static asserts. - - Params: - dg = delegate for attributed function / method - args = argument list from `opCall` - - Returns: - proxies return value of dg - */ - ReturnType!Function prepareInputAndCall(T...)(FunctionDg dg, T args) - if (!Compare!(Group!T, Group!(ParameterTypeTuple!Function))) - { - alias attributed_parameters = AttributedParameterMetadata!Function; - // calculated combined input type list - alias Input = MergeParameterTypes!( - Group!attributed_parameters, - Group!T - ); - - import std.traits : fullyQualifiedName; - import std.string : format; - - static assert ( - Compare!(Group!Input, Group!ParameterTypes), - format( - "Calculated input parameter type tuple %s does not match " ~ - "%s%s", - Input.stringof, - fullyQualifiedName!Function, - ParameterTypes.stringof - ) - ); - - // this value tuple will be used to assemble argument list - Input input; - - foreach (i, uda; input_attributes) { - // each iteration cycle is responsible for initialising `input` - // tuple from previous spot to current attributed parameter index - // (including) - - enum index = attributed_parameters[i].index; - - static if (i == 0) { - enum lStart = 0; - enum lEnd = index; - enum rStart = 0; - enum rEnd = index; - } - else { - enum previousIndex = attributed_parameters[i - 1].index; - enum lStart = previousIndex + 1; - enum lEnd = index; - enum rStart = previousIndex + 1 - i; - enum rEnd = index - i; - } - - static if (lStart != lEnd) { - input[lStart..lEnd] = args[rStart..rEnd]; - } - - // during last iteration cycle remaining tail is initialised - // too (if any) - - static if ((i == input_attributes.length - 1) && (index != input.length - 1)) { - input[(index + 1)..$] = args[(index - i)..$]; - } - - input[index] = uda.evaluator(m_storedArgs); - } - - // handle degraded case with no attributes separately - static if (!input_attributes.length) { - input[] = args[]; - } - - return dg(input); - } - - /** - `prepareInputAndCall` overload that operates on argument tuple that exactly - matches attributed function argument list and thus gets updated by - attached function instead of being merged with it - */ - ReturnType!Function prepareInputAndCall(T...)(FunctionDg dg, T args) - if (Compare!(Group!T, Group!(ParameterTypeTuple!Function))) - { - alias attributed_parameters = AttributedParameterMetadata!Function; - - foreach (i, uda; input_attributes) { - enum index = attributed_parameters[i].index; - args[index] = uda.evaluator(m_storedArgs); - } - - return dg(args); - } - } -} - -/// example -unittest -{ - import std.conv; - - static string evaluator(string left, string right) - { - return left ~ right; - } - - // all attribute function must accept same stored parameters - static int modificator(int result, string unused1, string unused2) - { - return result * 2; - } - - @before!evaluator("a") @before!evaluator("c") @after!modificator() - static int sum(string a, int b, string c, double d) - { - return to!int(a) + to!int(b) + to!int(c) + to!int(d); - } - - // ("10", "20") - stored arguments for evaluator() - auto funcattr = createAttributedFunction!sum("10", "20"); - - // `b` and `d` are unattributed, thus `42` and `13.5` will be - // used as their values - int result = funcattr(42, 13.5); - - assert(result == (1020 + 42 + 1020 + to!int(13.5)) * 2); -} - -// testing other prepareInputAndCall overload -unittest -{ - import std.conv; - - static string evaluator(string left, string right) - { - return left ~ right; - } - - // all attribute function must accept same stored parameters - static int modificator(int result, string unused1, string unused2) - { - return result * 2; - } - - @before!evaluator("a") @before!evaluator("c") @after!modificator() - static int sum(string a, int b, string c, double d) - { - return to!int(a) + to!int(b) + to!int(c) + to!int(d); - } - - auto funcattr = createAttributedFunction!sum("10", "20"); - - // `a` and `c` are expected to be simply overwritten - int result = funcattr("1000", 42, "1000", 13.5); - - assert(result == (1020 + 42 + 1020 + to!int(13.5)) * 2); -} - -/** - Syntax sugar in top of AttributedFunction - - Creates AttributedFunction with stored argument types that - match `T` and stores `args` there before returning. -*/ -auto createAttributedFunction(alias Function, T...)(T args) -{ - import vibe.internal.meta.typetuple : Group; - - AttributedFunction!(Function, Group!T) result; - result.storeArgs(args); - return result; -} - -/// -unittest -{ - void foo() {} - - auto funcattr = createAttributedFunction!foo(1, "2", 3.0); - - import std.typecons : tuple; - assert (tuple(funcattr.m_storedArgs) == tuple(1, "2", 3.0)); -} diff --git a/data/vibe/internal/meta/traits.d b/data/vibe/internal/meta/traits.d deleted file mode 100644 index 7b1df4002e..0000000000 --- a/data/vibe/internal/meta/traits.d +++ /dev/null @@ -1,472 +0,0 @@ -/** - Extensions to `std.traits` module of Phobos. Some may eventually make it into Phobos, - some are dirty hacks that work only for vibe.d - - Copyright: © 2012 Sönke Ludwig - License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. - Authors: Sönke Ludwig, Михаил Страшун -*/ - -module vibe.internal.meta.traits; - -import vibe.internal.meta.typetuple; - - -/** - Checks if given type is a getter function type - - Returns: `true` if argument is a getter - */ -template isPropertyGetter(T...) - if (T.length == 1) -{ - import std.traits : functionAttributes, FunctionAttribute, ReturnType, - isSomeFunction; - static if (isSomeFunction!(T[0])) { - enum isPropertyGetter = - (functionAttributes!(T[0]) & FunctionAttribute.property) != 0 - && !is(ReturnType!T == void); - } - else - enum isPropertyGetter = false; -} - -/// -unittest -{ - interface Test - { - @property int getter(); - @property void setter(int); - int simple(); - } - - static assert(isPropertyGetter!(typeof(&Test.getter))); - static assert(!isPropertyGetter!(typeof(&Test.setter))); - static assert(!isPropertyGetter!(typeof(&Test.simple))); - static assert(!isPropertyGetter!int); -} - -/** - Checks if given type is a setter function type - - Returns: `true` if argument is a setter - */ -template isPropertySetter(T...) - if (T.length == 1) -{ - import std.traits : functionAttributes, FunctionAttribute, ReturnType, - isSomeFunction; - - static if (isSomeFunction!(T[0])) { - enum isPropertySetter = - (functionAttributes!(T) & FunctionAttribute.property) != 0 - && is(ReturnType!(T[0]) == void); - } - else - enum isPropertySetter = false; -} - -/// -unittest -{ - interface Test - { - @property int getter(); - @property void setter(int); - int simple(); - } - - static assert(isPropertySetter!(typeof(&Test.setter))); - static assert(!isPropertySetter!(typeof(&Test.getter))); - static assert(!isPropertySetter!(typeof(&Test.simple))); - static assert(!isPropertySetter!int); -} - -/** - Deduces single base interface for a type. Multiple interfaces - will result in compile-time error. - - Params: - T = interface or class type - - Returns: - T if it is an interface. If T is a class, interface it implements. -*/ -template baseInterface(T) - if (is(T == interface) || is(T == class)) -{ - import std.traits : InterfacesTuple; - - static if (is(T == interface)) { - alias baseInterface = T; - } - else - { - alias Ifaces = InterfacesTuple!T; - static assert ( - Ifaces.length == 1, - "Type must be either provided as an interface or implement only one interface" - ); - alias baseInterface = Ifaces[0]; - } -} - -/// -unittest -{ - interface I1 { } - class A : I1 { } - interface I2 { } - class B : I1, I2 { } - - static assert (is(baseInterface!I1 == I1)); - static assert (is(baseInterface!A == I1)); - static assert (!is(typeof(baseInterface!B))); -} - - -/** - Determins if a member is a public, non-static data field. -*/ -template isRWPlainField(T, string M) -{ - static if (!isRWField!(T, M)) enum isRWPlainField = false; - else { - //pragma(msg, T.stringof~"."~M~":"~typeof(__traits(getMember, T, M)).stringof); - enum isRWPlainField = __traits(compiles, *(&__traits(getMember, Tgen!T(), M)) = *(&__traits(getMember, Tgen!T(), M))); - } -} - -/** - Determines if a member is a public, non-static, de-facto data field. - - In addition to plain data fields, R/W properties are also accepted. -*/ -template isRWField(T, string M) -{ - import std.traits; - import std.typetuple; - - static void testAssign()() { - static if (!isCopyable!T) T t; // for structs with disabled copy constructor - else T t = *(cast(T*)0); - __traits(getMember, t, M) = __traits(getMember, t, M); - } - - // reject type aliases - static if (is(TypeTuple!(__traits(getMember, T, M)))) enum isRWField = false; - // reject non-public members - else static if (!isPublicMember!(T, M)) enum isRWField = false; - // reject static members - else static if (!isNonStaticMember!(T, M)) enum isRWField = false; - // reject non-typed members - else static if (!is(typeof(__traits(getMember, T, M)))) enum isRWField = false; - // reject void typed members (includes templates) - else static if (is(typeof(__traits(getMember, T, M)) == void)) enum isRWField = false; - // reject non-assignable members - else static if (!__traits(compiles, testAssign!()())) enum isRWField = false; - else static if (anySatisfy!(isSomeFunction, __traits(getMember, T, M))) { - // If M is a function, reject if not @property or returns by ref - private enum FA = functionAttributes!(__traits(getMember, T, M)); - enum isRWField = (FA & FunctionAttribute.property) != 0; - } else { - enum isRWField = true; - } -} - -unittest { - import std.algorithm; - - static struct S { - alias a = int; // alias - int i; // plain RW field - enum j = 42; // manifest constant - static int k = 42; // static field - private int privateJ; // private RW field - - this(Args...)(Args args) {} - @disable this(this); - - // read-write property (OK) - @property int p1() { return privateJ; } - @property void p1(int j) { privateJ = j; } - // read-only property (NO) - @property int p2() { return privateJ; } - // write-only property (NO) - @property void p3(int value) { privateJ = value; } - // ref returning property (OK) - @property ref int p4() return { return i; } - // parameter-less template property (OK) - @property ref int p5()() { return i; } - // not treated as a property by DMD, so not a field - @property int p6()() { return privateJ; } - @property void p6(int j)() { privateJ = j; } - - static @property int p7() { return k; } - static @property void p7(int value) { k = value; } - - ref int f1() return { return i; } // ref returning function (no field) - - int f2(Args...)(Args args) { return i; } - - ref int f3(Args...)(Args args) { return i; } - - void someMethod() {} - - ref int someTempl()() { return i; } - } - - enum plainFields = ["i"]; - enum fields = ["i", "p1", "p4", "p5"]; - - foreach (mem; __traits(allMembers, S)) { - static if (isRWField!(S, mem)) static assert(fields.canFind(mem), mem~" detected as field."); - else static assert(!fields.canFind(mem), mem~" not detected as field."); - - static if (isRWPlainField!(S, mem)) static assert(plainFields.canFind(mem), mem~" not detected as plain field."); - else static assert(!plainFields.canFind(mem), mem~" not detected as plain field."); - } -} - -package T Tgen(T)(){ return T.init; } - - -/** - Tests if the protection of a member is public. -*/ -template isPublicMember(T, string M) -{ - import std.algorithm, std.typetuple : TypeTuple; - - static if (!__traits(compiles, TypeTuple!(__traits(getMember, T, M)))) enum isPublicMember = false; - else { - alias MEM = TypeTuple!(__traits(getMember, T, M)); - static if (__traits(compiles, __traits(getProtection, MEM))) - enum isPublicMember = __traits(getProtection, MEM).among("public", "export"); - else - enum isPublicMember = true; - } -} - -unittest { - class C { - int a; - export int b; - protected int c; - private int d; - package int e; - void f() {} - static void g() {} - private void h() {} - private static void i() {} - } - - static assert (isPublicMember!(C, "a")); - static assert (isPublicMember!(C, "b")); - static assert (!isPublicMember!(C, "c")); - static assert (!isPublicMember!(C, "d")); - static assert (!isPublicMember!(C, "e")); - static assert (isPublicMember!(C, "f")); - static assert (isPublicMember!(C, "g")); - static assert (!isPublicMember!(C, "h")); - static assert (!isPublicMember!(C, "i")); - - struct S { - int a; - export int b; - private int d; - package int e; - } - static assert (isPublicMember!(S, "a")); - static assert (isPublicMember!(S, "b")); - static assert (!isPublicMember!(S, "d")); - static assert (!isPublicMember!(S, "e")); - - S s; - s.a = 21; - assert(s.a == 21); -} - -/** - Tests if a member requires $(D this) to be used. -*/ -template isNonStaticMember(T, string M) -{ - import std.typetuple; - import std.traits; - - alias MF = TypeTuple!(__traits(getMember, T, M)); - static if (M.length == 0) { - enum isNonStaticMember = false; - } else static if (anySatisfy!(isSomeFunction, MF)) { - enum isNonStaticMember = !__traits(isStaticFunction, MF); - } else { - enum isNonStaticMember = !__traits(compiles, (){ auto x = __traits(getMember, T, M); }()); - } -} - -unittest { // normal fields - struct S { - int a; - static int b; - enum c = 42; - void f(); - static void g(); - ref int h() return { return a; } - static ref int i() { return b; } - } - static assert(isNonStaticMember!(S, "a")); - static assert(!isNonStaticMember!(S, "b")); - static assert(!isNonStaticMember!(S, "c")); - static assert(isNonStaticMember!(S, "f")); - static assert(!isNonStaticMember!(S, "g")); - static assert(isNonStaticMember!(S, "h")); - static assert(!isNonStaticMember!(S, "i")); -} - -unittest { // tuple fields - struct S(T...) { - T a; - static T b; - } - - alias T = S!(int, float); - auto p = T.b; - static assert(isNonStaticMember!(T, "a")); - static assert(!isNonStaticMember!(T, "b")); - - alias U = S!(); - static assert(!isNonStaticMember!(U, "a")); - static assert(!isNonStaticMember!(U, "b")); -} - - -/** - Tests if a Group of types is implicitly convertible to a Group of target types. -*/ -bool areConvertibleTo(alias TYPES, alias TARGET_TYPES)() - if (isGroup!TYPES && isGroup!TARGET_TYPES) -{ - static assert(TYPES.expand.length == TARGET_TYPES.expand.length); - foreach (i, V; TYPES.expand) - if (!is(V : TARGET_TYPES.expand[i])) - return false; - return true; -} - -/// Test if the type $(D DG) is a correct delegate for an opApply where the -/// key/index is of type $(D TKEY) and the value of type $(D TVALUE). -template isOpApplyDg(DG, TKEY, TVALUE) { - import std.traits; - static if (is(DG == delegate) && is(ReturnType!DG : int)) { - private alias PTT = ParameterTypeTuple!(DG); - private alias PSCT = ParameterStorageClassTuple!(DG); - private alias STC = ParameterStorageClass; - // Just a value - static if (PTT.length == 1) { - enum isOpApplyDg = (is(PTT[0] == TVALUE)); - } else static if (PTT.length == 2) { - enum isOpApplyDg = (is(PTT[0] == TKEY)) - && (is(PTT[1] == TVALUE)); - } else - enum isOpApplyDg = false; - } else { - enum isOpApplyDg = false; - } -} - -unittest { - static assert(isOpApplyDg!(int delegate(int, string), int, string)); - static assert(isOpApplyDg!(int delegate(ref int, ref string), int, string)); - static assert(isOpApplyDg!(int delegate(int, ref string), int, string)); - static assert(isOpApplyDg!(int delegate(ref int, string), int, string)); -} - -// Synchronized statements are logically nothrow but dmd still marks them as throwing. -// DMD#4115, Druntime#1013, Druntime#1021, Phobos#2704 -import core.sync.mutex : Mutex; -enum synchronizedIsNothrow = __traits(compiles, (Mutex m) nothrow { synchronized(m) {} }); - - -template StripHeadConst(T) { - static if (is(T == const(F), F)) alias StripHeadConst = StripHeadConst!F; - else static if (is(T == immutable(F), F)) alias StripHeadConst = StripHeadConst!F; - else static if (is(T == inout(F), F)) alias StripHeadConst = StripHeadConst!F; - else alias StripHeadConst = T; -} - -unittest { - static assert(is(StripHeadConst!(int) == int)); - static assert(is(StripHeadConst!(const(int)) == int)); - static assert(is(StripHeadConst!(immutable(int)) == int)); - static assert(is(StripHeadConst!(const(immutable(int))) == int)); - static assert(is(StripHeadConst!(const(int[])) == const(int)[])); -} - -template derivedMethod(C, alias method) -{ - import std.traits : FunctionTypeOf, MemberFunctionsTuple, ParameterTypeTuple; - import std.meta : AliasSeq; - - enum fname = __traits(identifier, method); - alias overloads = MemberFunctionsTuple!(C, fname); - alias PTypes = ParameterTypeTuple!method; - - template impl(size_t i) { - static if (i >= overloads.length) - alias impl = AliasSeq!(); - else { - alias FT = FunctionTypeOf!(overloads[i]); - static if (__traits(compiles, FT(PTypes.init))) - alias impl = overloads[i]; - else - alias impl = impl!(i+1); - } - } - - alias derivedMethod = impl!0; -} - -template RecursiveFunctionAttributes(alias func) -{ - import std.meta : AliasSeq, staticMap; - import std.traits : BaseTypeTuple; - - static if (is(AliasSeq!(__traits(parent, func))[0])) { - alias C = AliasSeq!(__traits(parent, func))[0]; - template rimpl(T) { - alias DF = derivedMethod!(T, func); - static if (AliasSeq!(DF).length > 0) - alias rimpl = RecursiveFunctionAttributes!DF; - else alias rimpl = AliasSeq!(); - } - alias RecursiveFunctionAttributes = AliasSeq!( - __traits(getAttributes, func), - staticMap!(rimpl, BaseTypeTuple!C) - ); - } else { - alias RecursiveFunctionAttributes = AliasSeq!(__traits(getAttributes, func)); - } -} - -unittest { - interface I { - @(1) void test(); - } - - interface J { - @(4) void test(int); - } - - class C : I, J { - override @(2) void test() {} - override void test(int) {} - } - - class D : C { - override @(3) void test() {} - } - - static assert([RecursiveFunctionAttributes!(D.test)] == [3, 2, 1]); -} diff --git a/data/vibe/internal/meta/typetuple.d b/data/vibe/internal/meta/typetuple.d deleted file mode 100644 index a7534dc7a6..0000000000 --- a/data/vibe/internal/meta/typetuple.d +++ /dev/null @@ -1,123 +0,0 @@ -/** - Additions to std.typetuple pending for inclusion into Phobos. - - Copyright: © 2013 Sönke Ludwig - License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. - Authors: Михаил Страшун -*/ - -module vibe.internal.meta.typetuple; - -import std.typetuple; -import std.traits; - -/** - TypeTuple which does not auto-expand. - - Useful when you need - to multiple several type tuples as different template argument - list parameters, without merging those. -*/ -template Group(T...) -{ - alias expand = T; -} - -/// -unittest -{ - alias group = Group!(int, double, string); - static assert (!is(typeof(group.length))); - static assert (group.expand.length == 3); - static assert (is(group.expand[1] == double)); -} - -/** -*/ -template isGroup(T...) -{ - static if (T.length != 1) enum isGroup = false; - else enum isGroup = - !is(T[0]) && is(typeof(T[0]) == void) // does not evaluate to something - && is(typeof(T[0].expand.length) : size_t) // expands to something with length - && !is(typeof(&(T[0].expand))); // expands to not addressable -} - -version (unittest) // NOTE: GDC complains about template definitions in unittest blocks -{ - alias group = Group!(int, double, string); - alias group2 = Group!(); - - template Fake(T...) - { - int[] expand; - } - alias fake = Fake!(int, double, string); - - alias fake2 = TypeTuple!(int, double, string); - - static assert (isGroup!group); - static assert (isGroup!group2); - static assert (!isGroup!fake); - static assert (!isGroup!fake2); -} - -/* Copied from Phobos as it is private there. - */ -private template isSame(ab...) - if (ab.length == 2) -{ - static if (is(ab[0]) && is(ab[1])) - { - enum isSame = is(ab[0] == ab[1]); - } - else static if (!is(ab[0]) && - !is(ab[1]) && - is(typeof(ab[0] == ab[1]) == bool) && - (ab[0] == ab[1])) - { - static if (!__traits(compiles, &ab[0]) || - !__traits(compiles, &ab[1])) - enum isSame = (ab[0] == ab[1]); - else - enum isSame = __traits(isSame, ab[0], ab[1]); - } - else - { - enum isSame = __traits(isSame, ab[0], ab[1]); - } -} - -/** - Compares two groups for element identity - - Params: - Group1, Group2 = any instances of `Group` - - Returns: - `true` if each element of Group1 is identical to - the one of Group2 at the same index -*/ -template Compare(alias Group1, alias Group2) - if (isGroup!Group1 && isGroup!Group2) -{ - private template implementation(size_t index) - { - static if (Group1.expand.length != Group2.expand.length) enum implementation = false; - else static if (index >= Group1.expand.length) enum implementation = true; - else static if (!isSame!(Group1.expand[index], Group2.expand[index])) enum implementation = false; - else enum implementation = implementation!(index+1); - } - - enum Compare = implementation!0; -} - -/// -unittest -{ - alias one = Group!(int, double); - alias two = Group!(int, double); - alias three = Group!(double, int); - static assert (Compare!(one, two)); - static assert (!Compare!(one, three)); -} diff --git a/data/vibe/internal/meta/uda.d b/data/vibe/internal/meta/uda.d deleted file mode 100644 index a58a834bf3..0000000000 --- a/data/vibe/internal/meta/uda.d +++ /dev/null @@ -1,239 +0,0 @@ -/** - Utility templates that help working with User Defined Attributes - - Copyright: © 2013 Sönke Ludwig - License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. - Authors: Sönke Ludwig, Михаил Страшун -*/ - -module vibe.internal.meta.uda; - -//import vibe.internal.meta.traits; - - -/** - Small convenience wrapper to find and extract certain UDA from given type. - Will stop on first element which is of required type. - - Params: - UDA = type or template to search for in UDA list - Symbol = symbol to query for UDA's - allow_types = if set to `false` considers attached `UDA` types an error - (only accepts instances/values) - - Returns: aggregated search result struct with 3 field. `value` aliases found UDA. - `found` is boolean flag for having a valid find. `index` is integer index in - attribute list this UDA was found at. -*/ -template findFirstUDA(alias UDA, alias Symbol, bool allow_types = false) if (!is(UDA)) -{ - enum findFirstUDA = findNextUDA!(UDA, Symbol, 0, allow_types); -} - -/// Ditto -template findFirstUDA(UDA, alias Symbol, bool allow_types = false) -{ - enum findFirstUDA = findNextUDA!(UDA, Symbol, 0, allow_types); -} - -private struct UdaSearchResult(alias UDA) -{ - alias value = UDA; - bool found = false; - long index = -1; -} - -/** - Small convenience wrapper to find and extract certain UDA from given type. - Will start at the given index and stop on the next element which is of required type. - - Params: - UDA = type or template to search for in UDA list - Symbol = symbol to query for UDA's - idx = 0-based index to start at. Should be positive, and under the total number of attributes. - allow_types = if set to `false` considers attached `UDA` types an error - (only accepts instances/values) - - Returns: aggregated search result struct with 3 field. `value` aliases found UDA. - `found` is boolean flag for having a valid find. `index` is integer index in - attribute list this UDA was found at. - */ -template findNextUDA(alias UDA, alias Symbol, long idx, bool allow_types = false) if (!is(UDA)) -{ - import std.traits : isInstanceOf; - import std.typetuple : TypeTuple; - - private alias udaTuple = TypeTuple!(__traits(getAttributes, Symbol)); - - static assert(idx >= 0, "Index given to findNextUDA can't be negative"); - static assert(idx <= udaTuple.length, "Index given to findNextUDA is above the number of attribute"); - - public template extract(size_t index, list...) - { - static if (!list.length) enum extract = UdaSearchResult!(null)(false, -1); - else { - static if (is(list[0])) { - static if (is(UDA) && is(list[0] == UDA) || !is(UDA) && isInstanceOf!(UDA, list[0])) { - static assert (allow_types, "findNextUDA is designed to look up values, not types"); - enum extract = UdaSearchResult!(list[0])(true, index); - } else enum extract = extract!(index + 1, list[1..$]); - } else { - static if (is(UDA) && is(typeof(list[0]) == UDA) || !is(UDA) && isInstanceOf!(UDA, typeof(list[0]))) { - import vibe.internal.meta.traits : isPropertyGetter; - static if (isPropertyGetter!(list[0])) { - enum value = list[0]; - enum extract = UdaSearchResult!(value)(true, index); - } else enum extract = UdaSearchResult!(list[0])(true, index); - } else enum extract = extract!(index + 1, list[1..$]); - } - } - } - - enum findNextUDA = extract!(idx, udaTuple[idx .. $]); -} -/// ditto -template findNextUDA(UDA, alias Symbol, long idx, bool allow_types = false) -{ - import std.traits : isInstanceOf; - import std.typetuple : TypeTuple; - - private alias udaTuple = TypeTuple!(__traits(getAttributes, Symbol)); - - static assert(idx >= 0, "Index given to findNextUDA can't be negative"); - static assert(idx <= udaTuple.length, "Index given to findNextUDA is above the number of attribute"); - - public template extract(size_t index, list...) - { - static if (!list.length) enum extract = UdaSearchResult!(null)(false, -1); - else { - static if (is(list[0])) { - static if (is(list[0] == UDA)) { - static assert (allow_types, "findNextUDA is designed to look up values, not types"); - enum extract = UdaSearchResult!(list[0])(true, index); - } else enum extract = extract!(index + 1, list[1..$]); - } else { - static if (is(typeof(list[0]) == UDA)) { - import vibe.internal.meta.traits : isPropertyGetter; - static if (isPropertyGetter!(list[0])) { - enum value = list[0]; - enum extract = UdaSearchResult!(value)(true, index); - } else enum extract = UdaSearchResult!(list[0])(true, index); - } else enum extract = extract!(index + 1, list[1..$]); - } - } - } - - enum findNextUDA = extract!(idx, udaTuple[idx .. $]); -} - - -/// -unittest -{ - struct Attribute { int x; } - - @("something", Attribute(42), Attribute(41)) - void symbol(); - - enum result0 = findNextUDA!(string, symbol, 0); - static assert (result0.found); - static assert (result0.index == 0); - static assert (result0.value == "something"); - - enum result1 = findNextUDA!(Attribute, symbol, 0); - static assert (result1.found); - static assert (result1.index == 1); - static assert (result1.value == Attribute(42)); - - enum result2 = findNextUDA!(int, symbol, 0); - static assert (!result2.found); - - enum result3 = findNextUDA!(Attribute, symbol, result1.index + 1); - static assert (result3.found); - static assert (result3.index == 2); - static assert (result3.value == Attribute(41)); -} - -unittest -{ - struct Attribute { int x; } - - @(Attribute) void symbol(); - - static assert (!is(findNextUDA!(Attribute, symbol, 0))); - - enum result0 = findNextUDA!(Attribute, symbol, 0, true); - static assert (result0.found); - static assert (result0.index == 0); - static assert (is(result0.value == Attribute)); -} - -unittest -{ - struct Attribute { int x; } - enum Dummy; - - @property static Attribute getter() - { - return Attribute(42); - } - - @Dummy @getter void symbol(); - - enum result0 = findNextUDA!(Attribute, symbol, 0); - static assert (result0.found); - static assert (result0.index == 1); - static assert (result0.value == Attribute(42)); -} - -/// Eager version of findNextUDA that represent all instances of UDA in a Tuple. -/// If one of the attribute is a type instead of an instance, compilation will fail. -template UDATuple(alias UDA, alias Sym) { - import std.typetuple : TypeTuple; - - private template extract(size_t maxSize, Founds...) - { - private alias LastFound = Founds[$ - 1]; - // No more to find - static if (!LastFound.found) - enum extract = Founds[0 .. $ - 1]; - else { - // For ease of use, this is a Tuple of UDA, not a tuple of UdaSearchResult!(...) - private alias Result = TypeTuple!(Founds[0 .. $ - 1], LastFound.value); - // We're at the last parameter - static if (LastFound.index == maxSize) - enum extract = Result; - else - enum extract = extract!(maxSize, Result, findNextUDA!(UDA, Sym, LastFound.index + 1)); - } - } - - private enum maxIndex = TypeTuple!(__traits(getAttributes, Sym)).length; - enum UDATuple = extract!(maxIndex, findNextUDA!(UDA, Sym, 0)); -} - -unittest -{ - import std.typetuple : TypeTuple; - - struct Attribute { int x; } - enum Dummy; - - @(Dummy, Attribute(21), Dummy, Attribute(42), Attribute(84)) void symbol() {} - @(Dummy, Attribute(21), Dummy, Attribute(42), Attribute) void wrong() {} - - alias Cmp = TypeTuple!(Attribute(21), Attribute(42), Attribute(84)); - static assert(Cmp == UDATuple!(Attribute, symbol)); - static assert(!is(UDATuple!(Attribute, wrong))); -} - -/// Avoid repeating the same error message again and again. -/// ---- -/// if (!__ctfe) -/// assert(0, onlyAsUda!func); -/// ---- -template onlyAsUda(string from /*= __FUNCTION__*/) -{ - // With default param, DMD think expression is void, even when writing 'enum string onlyAsUda = ...' - enum onlyAsUda = from~" must only be used as an attribute - not called as a runtime function."; -} diff --git a/data/vibe/internal/rangeutil.d b/data/vibe/internal/rangeutil.d deleted file mode 100644 index e90aa47bb3..0000000000 --- a/data/vibe/internal/rangeutil.d +++ /dev/null @@ -1,32 +0,0 @@ -module vibe.internal.rangeutil; - -struct RangeCounter { -@safe: - - import std.utf; - long* length; - - this(long* _captureLength) { length = _captureLength; } - - void put(char ch) { (*length)++; } - void put(scope const(char)[] str) { *length += str.length; } - void put(dchar ch) { *length += codeLength!char(ch); } - void put(scope const(dchar)[] str) { foreach (ch; str) put(ch); } -} - -@safe unittest { - static long writeLength(ARGS...)(ARGS args) { - long len = 0; - auto rng = RangeCounter(() @trusted { return &len; } ()); - foreach (a; args) rng.put(a); - return len; - } - assert(writeLength("hello", ' ', "world") == "hello world".length); - assert(writeLength("h\u00E4llo", ' ', "world") == "h\u00E4llo world".length); - assert(writeLength("hello", '\u00E4', "world") == "hello\u00E4world".length); - assert(writeLength("h\u00E4llo", ' ', "world") == "h\u00E4llo world".length); - assert(writeLength("h\u1000llo", '\u1000', "world") == "h\u1000llo\u1000world".length); - auto test = "häl"; - assert(test.length == 4); - assert(writeLength(test[0], test[1], test[2], test[3]) == test.length); -} diff --git a/run-ci.sh b/run-ci.sh index b1d48443bb..ce49aff641 100755 --- a/run-ci.sh +++ b/run-ci.sh @@ -25,7 +25,6 @@ if [[ $PARTS =~ (^|,)builds(,|$) ]]; then fi if [[ $PARTS =~ (^|,)unittests(,|$) ]]; then - dub test :data --compiler=$DC $DUB_ARGS dub test :mongodb --compiler=$DC $DUB_ARGS dub test :redis --compiler=$DC $DUB_ARGS dub test :web --compiler=$DC $DUB_ARGS From b0e627268b9d0b3982c6b64f2d0069fbbe08db77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Ludwig?= Date: Fri, 16 Feb 2024 22:50:43 +0100 Subject: [PATCH 2/2] Add a project structure paragraph to README.md. --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index d12a592b7c..83a0054fc4 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,25 @@ Download this file as `hello.d` and run it with [DUB](https://github.com/dlang/d Alternatively, you can quickstart with examples directly on [![Open on run.dlang.io](https://img.shields.io/badge/run.dlang.io-open-blue.svg)](https://run.dlang.io/is/qTsfv6). + +Project structure +----------------- + +The vibe.d project is structured into multiple repositories: + +- `vibe-d` (this repository): The high level web and REST framework, HTTP, STMP and Redis/MongoDB functionality +- [`vibe-core`](https://github.com/vibe-d/vibe-core): Fiber based I/O and concurrency implementation +- [`vibe-serialization`](https://github.com/vibe-d/vibe-serialization): Serialization and structured data format support +- [`vibe-container`](https://github.com/vibe-d/vibe-container): Container implementations used throughout the project +- [`vibe-sdl`](https://github.com/vibe-d/vibe-sdl): [SDLang](https://sdlang.org) serialization support based on [`sdlite`](https://github.com/s-ludwig/sdlite) +- [`vibe-compat`](https://github.com/vibe-d/vibe-compat): Legacy functionality +- [`observable`](https://github.com/vibe-d/observable): Observable, signal/slot and reactive value implementations +- [`diet`](https://github.com/rejectedsoftware/diet-ng): pug.js inspired compile-time HTML template system with `vibe.web` integration +- [`eventcore`](https://github.com/vibe-d/eventcore): Low-level abstraction over the operating system's asynchronous I/O facilities + +You can find an API overview of the whole project in the [API documentation](https://vibed.org/api/). + + Support -------