1 module siryul.common; 2 import std.meta : templateAnd, templateNot, templateOr; 3 import std.range : isInputRange; 4 import std.traits : arity, getSymbolsByUDA, getUDAs, hasUDA, isArray, isIterable, isSomeString, TemplateArgsOf, TemplateOf; 5 import std.typecons : Nullable, NullableRef; 6 ///Serialization options 7 enum Siryulize { 8 none, ///Default behaviour 9 omitNulls = 1 << 0, ///Omit null values from output 10 omitInits = 1 << 1 ///Omit values == type.init from output 11 } 12 ///Deserialization options 13 enum DeSiryulize { 14 none, ///Default behaviour 15 optionalByDefault = 1 << 0 ///All members will be considered to be @Optional 16 } 17 /++ 18 + Thrown when an error occurs 19 +/ 20 class SiryulException : Exception { 21 package this(string msg, string file = __FILE__, size_t line = __LINE__) @safe pure nothrow { 22 super(msg, file, line); 23 } 24 } 25 /++ 26 + Thrown when a serialization error occurs 27 +/ 28 class SerializeException : SiryulException { 29 package this(string msg, string file = __FILE__, size_t line = __LINE__) @safe pure nothrow { 30 super(msg, file, line); 31 } 32 } 33 /++ 34 + Thrown when a deserialization error occurs 35 +/ 36 class DeserializeException : SiryulException { 37 package this(string msg, string file = __FILE__, size_t line = __LINE__) @safe pure nothrow { 38 super(msg, file, line); 39 } 40 } 41 42 package template isNullable(T) { 43 static if(__traits(compiles, TemplateArgsOf!T) && __traits(compiles, Nullable!(TemplateArgsOf!T)) && is(T == Nullable!(TemplateArgsOf!T))) 44 enum isNullable = true; 45 else 46 enum isNullable = false; 47 } 48 static assert(isNullable!(Nullable!int)); 49 static assert(isNullable!(Nullable!(int, 0))); 50 static assert(!isNullable!int); 51 52 /++ 53 + (De)serialize field using a different name. 54 + 55 + Especially useful for fields that happen to use D keywords. 56 +/ 57 struct SiryulizeAs { 58 ///Serialized field name 59 string name; 60 } 61 /++ 62 + Used when nonpresence of field is not an error. The field will be set to its 63 + .init value. If being able to detect nonpresence is desired, ensure that 64 + the default value cannot appear in the data or use a Nullable type. 65 +/ 66 enum Optional; 67 /++ 68 + Use custom parser functions for a given field. 69 + 70 + The function names must exist as methods in the struct. Any (de)serializable 71 + type may be used for fromFunc's argument and toFunc's return value, but 72 + fromFunc's return type and toFunc's argument type must be the same as the 73 + field's type. 74 +/ 75 struct CustomParser { 76 ///Function to be used in deserialization 77 string fromFunc; 78 ///Function to be used in serialization 79 string toFunc; 80 } 81 ///Write field as string 82 enum AsString; 83 ///Write field as binary (NYI) 84 enum AsBinary; 85 ///Errors are ignored; value will be .init 86 enum IgnoreErrors; 87 ///Marks a method for use in serialization 88 enum SerializationMethod; 89 ///Marks a method for use in deserialization 90 enum DeserializationMethod; 91 92 enum hasSerializationMethod(T) = getSymbolsByUDA!(T, SerializationMethod).length == 1; 93 alias serializationMethod(T) = getSymbolsByUDA!(T, SerializationMethod)[0]; 94 enum hasDeserializationMethod(T) = getSymbolsByUDA!(T, DeserializationMethod).length == 1; 95 alias deserializationMethod(T) = getSymbolsByUDA!(T, DeserializationMethod)[0]; 96 97 alias isSimpleList = templateAnd!(isIterable, templateNot!isSomeString); 98 static assert(isSimpleList!(int[])); 99 static assert(isSimpleList!(string[])); 100 static assert(!isSimpleList!(string)); 101 static assert(!isSimpleList!(char[])); 102 static assert(!isSimpleList!(int)); 103 104 package template getMemberName(alias T) { 105 static if (hasUDA!(T, SiryulizeAs)) { 106 enum getMemberName = getUDAs!(T, SiryulizeAs)[0].name; 107 } else 108 enum getMemberName = T.stringof; 109 } 110 unittest { 111 struct TestStruct { 112 string something; 113 @SiryulizeAs("Test") string somethingElse; 114 } 115 assert(getMemberName!(__traits(getMember, TestStruct, "something")) == "something"); 116 assert(getMemberName!(__traits(getMember, TestStruct, "somethingElse")) == "Test"); 117 } 118 template hasConvertToFunc(T, alias member) { 119 static if (hasUDA!(member, CustomParser)) { 120 enum hasConvertToFunc = true; 121 } else static if (is(typeof(T.toSiryulHelper!(member.stringof)))) { 122 enum hasConvertToFunc = true; 123 } else { 124 enum hasConvertToFunc = false; 125 } 126 } 127 package template getConvertToFunc(T, alias member) { 128 static if (hasUDA!(member, CustomParser)) { 129 import std.meta : AliasSeq; 130 alias getConvertToFunc = AliasSeq!(__traits(getMember, T, getUDAs!(member, CustomParser)[0].toFunc))[0]; 131 } else static if (is(typeof(T.toSiryulHelper!(member.stringof)))) { 132 alias getConvertToFunc = T.toSiryulHelper!(member.stringof); 133 } else { 134 alias getConvertToFunc = (const(typeof(member)) v) { return v; }; 135 } 136 static assert(arity!getConvertToFunc == 1, "Arity of conversion function must be exactly 1"); 137 } 138 version(unittest) { 139 import std.datetime : SysTime; 140 struct TimeTest { 141 @CustomParser("fromJunk", "toJunk") SysTime time; 142 string nothing; 143 static SysTime fromJunk(string) { 144 return SysTime.min; 145 } 146 static string toJunk(SysTime) { 147 return "this has nothing to do with time."; 148 } 149 } 150 struct TimeTest2 { 151 SysTime time; 152 string nothing; 153 static auto toSiryulHelper(string T)(SysTime) if(T == "time") { 154 return "this has nothing to do with time."; 155 } 156 static auto fromSiryulHelper(string T)(string) if (T == "time") { 157 return SysTime.min; 158 } 159 } 160 } 161 unittest { 162 import std.datetime : SysTime; 163 assert(getConvertToFunc!(TimeTest, TimeTest.time)(SysTime.min) == "this has nothing to do with time."); 164 assert(getConvertToFunc!(TimeTest, TimeTest.nothing)("test") == "test"); 165 assert(getConvertToFunc!(TimeTest2, TimeTest2.time)(SysTime.min) == "this has nothing to do with time."); 166 assert(getConvertToFunc!(TimeTest2, TimeTest2.nothing)("test") == "test"); 167 } 168 package template getConvertFromFunc(T, alias member) { 169 static if (hasUDA!(member, CustomParser)) { 170 import std.meta : AliasSeq; 171 alias getConvertFromFunc = AliasSeq!(__traits(getMember, T, getUDAs!(member, CustomParser)[0].fromFunc))[0]; 172 } else static if (is(typeof(T.fromSiryulHelper!(member.stringof)))) { 173 alias getConvertFromFunc = T.fromSiryulHelper!(member.stringof); 174 } else { 175 alias getConvertFromFunc = (typeof(member) v) { return v; }; 176 } 177 static assert(arity!getConvertFromFunc == 1, "Arity of conversion function must be exactly 1"); 178 } 179 unittest { 180 import std.datetime : SysTime; 181 assert(getConvertFromFunc!(TimeTest, TimeTest.time)("yep") == SysTime.min); 182 assert(getConvertFromFunc!(TimeTest, TimeTest.nothing)("test") == "test"); 183 assert(getConvertFromFunc!(TimeTest2, TimeTest2.time)("yep") == SysTime.min); 184 assert(getConvertFromFunc!(TimeTest2, TimeTest2.nothing)("test") == "test"); 185 }