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 }