1 /**
2  * Intermediary, abstract format for serialization.
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.utils.serialization.store;
15 
16 import std.conv;
17 import std.exception;
18 import std.string;
19 import std.traits;
20 
21 import ae.utils.meta;
22 
23 /// A discriminated union type which can be used as both a serialization sink and source.
24 /// Similar to std.variant.Variant and std.json.JSONValue.
25 struct SerializedObject(C)
26 {
27 	alias S = C[];
28 
29 	enum Type
30 	{
31 		sNone,
32 		sNumeric,
33 		sString,
34 		sStringFragments,
35 		sNull,
36 		sBoolean,
37 		sArray,
38 		sObject,
39 	}
40 
41 	Type type;
42 
43 	union
44 	{
45 		S sNumeric;
46 		S sString;
47 		S[] sStringFragments;
48 		bool sBoolean;
49 		SerializedObject[] sArray;
50 		SerializedObject[S] sObject;
51 	}
52 
53 	// ***********************************************************************
54 
55 	this(T)(T v)
56 		if (is(typeof(this = v)))
57 	{
58 		this = v;
59 	}
60 
61 	void opAssign(T)(T v)
62 		if (is(isNumeric!T))
63 	{
64 		type = Type.sNumeric;
65 		sNumeric = v.numberToString.to!S;
66 	}
67 
68 	void opAssign(T)(T v)
69 		if (isSomeString!T)
70 	{
71 		type = Type.sString;
72 		sString = v.to!S;
73 	}
74 
75 	void opAssign(T)(T v)
76 		if (is(T == typeof(null)))
77 	{
78 		assert(v is null);
79 		type = Type.sNull;
80 	}
81 
82 	void opAssign(T)(T v)
83 		if (is(T == bool))
84 	{
85 		type = Type.sBoolean;
86 		sBoolean = v;
87 	}
88 
89 	void opAssign(T)(T v)
90 		if (is(T U : U[]) && !isSomeString!T)
91 	{
92 		type = Type.sArray;
93 		import std.range, std.array;
94 		sArray = v.map!(e => SerializedObject!C(e)).array;
95 	}
96 
97 	void opAssign(T)(T aa)
98 		if (is(T K : V[K], V) && isSomeString!K)
99 	{
100 		type = Type.sObject;
101 		sObject = null;
102 		foreach (k, ref v; aa)
103 			sObject[k.to!S] = SerializedObject!C(v);
104 	}
105 
106 	ref SerializedObject opIndex(size_t i)
107 	{
108 		enforce(type == Type.sArray, "SerializedObject is %s, not sArray".format(type));
109 		enforce(i < sArray.length, "SerializedObject sArray index %d out of bounds (0..%d)".format(i, sArray.length));
110 		return sArray[i];
111 	}
112 
113 	ref SerializedObject opIndex(S s)
114 	{
115 		enforce(type == Type.sObject, "SerializedObject is %s, not sObject".format(type));
116 		return sObject[s];
117 	}
118 
119 	// ***********************************************************************
120 
121 	enum isSerializationSink = true;
122 
123 	void handleNumeric(CC)(CC[] s)
124 	{
125 		assert(type == Type.sNone);
126 		type = Type.sNumeric;
127 		sNumeric = s.to!S;
128 	}
129 
130 	void handleString(CC)(CC[] s)
131 	{
132 		assert(type == Type.sNone);
133 		type = Type.sString;
134 		sString = s.to!S;
135 	}
136 
137 	void handleStringFragments(Reader)(Reader reader)
138 	{
139 		static struct StringFragmentSink
140 		{
141 			S[]* arr;
142 
143 			void handleStringFragment(CC)(CC[] s)
144 			{
145 				*arr ~= s.to!S;
146 			}
147 		}
148 
149 		assert(type == Type.sNone);
150 		type = Type.sStringFragments;
151 		reader(StringFragmentSink(&sStringFragments));
152 	}
153 
154 	void handleNull()
155 	{
156 		assert(type == Type.sNone);
157 		type = Type.sNull;
158 	}
159 
160 	void handleBoolean(bool value)
161 	{
162 		assert(type == Type.sNone);
163 		type = Type.sBoolean;
164 		sBoolean = value;
165 	}
166 
167 	void handleArray(Reader)(Reader reader)
168 	{
169 		static struct ArraySink
170 		{
171 			SerializedObject[]* arr;
172 
173 			alias handleStringFragments = opDispatch!"handleStringFragments";
174 			alias handleObject = opDispatch!"handleObject";
175 
176 			template opDispatch(string name)
177 			{
178 				void opDispatch(Args...)(auto ref Args args)
179 				{
180 					SerializedObject obj;
181 					mixin("obj." ~ name ~ "(args);");
182 					*arr ~= obj;
183 				}
184 			}
185 		}
186 
187 		assert(type == Type.sNone);
188 		type = Type.sArray;
189 		reader(ArraySink(&sArray));
190 	}
191 
192 	void handleObject(Reader)(Reader reader)
193 	{
194 		static struct ObjectSink
195 		{
196 			SerializedObject[S]* aa;
197 
198 			void handleField(NameReader, ValueReader)(NameReader nameReader, ValueReader valueReader)
199 			{
200 				static struct StringSink
201 				{
202 					S s;
203 					void handleString(CC)(CC[] s)
204 					{
205 						this.s = s.to!S;
206 					}
207 
208 					void handleStringFragments(Reader)(Reader reader)
209 					{
210 						reader(&this);
211 					}
212 
213 					void handleStringFragment(CC)(CC[] fragment)
214 					{
215 						s ~= fragment.to!S;
216 					}
217 
218 					void bad() { throw new Exception("String expected"); }
219 
220 					void handleNumeric(CC)(CC[] s) { bad(); }
221 					void handleNull() { bad(); }
222 					void handleBoolean(bool value) { bad(); }
223 					void handleArray(Reader)(Reader reader) { bad(); }
224 					void handleObject(Reader)(Reader reader) { bad(); }
225 				}
226 
227 				StringSink nameSink;
228 				nameReader(nameSink);
229 				SerializedObject value;
230 				valueReader(&value);
231 				(*aa)[nameSink.s] = value;
232 			}
233 		}
234 
235 		assert(type == Type.sNone);
236 		type = Type.sObject;
237 		reader(ObjectSink(&sObject));
238 	}
239 
240 	auto traverse(CC, Reader)(in CC[] name, Reader reader)
241 	{
242 		if (type == Type.sNone)
243 		{
244 			type = Type.sObject;
245 			sObject = null;
246 		}
247 		enforce(type == Type.sObject, "Can't traverse %s".format(type));
248 
249 		auto pv = name in sObject;
250 		if (!pv)
251 		{
252 			*p[name] = SerializedObject.init;
253 			pv = name in *p;
254 		}
255 		return reader(pv);
256 	}
257 
258 	// ***********************************************************************
259 
260 	void read(Sink)(Sink sink)
261 	{
262 		final switch (type)
263 		{
264 			case Type.sNone:
265 				assert(false, "Uninitialized SerializedObject");
266 			case Type.sNumeric:
267 				sink.handleNumeric(sNumeric);
268 				break;
269 			case Type.sString:
270 				sink.handleString(sString);
271 				break;
272 			case Type.sStringFragments:
273 				sink.handleStringFragments(boundFunctorOf!readStringFragments(&this));
274 				break;
275 			case Type.sNull:
276 				sink.handleNull();
277 				break;
278 			case Type.sBoolean:
279 				sink.handleBoolean(sBoolean);
280 				break;
281 			case Type.sArray:
282 				sink.handleArray(boundFunctorOf!readArray(&this));
283 				break;
284 			case Type.sObject:
285 				sink.handleObject(boundFunctorOf!readObject(&this));
286 				break;
287 		}
288 	}
289 
290 	void readStringFragments(Sink)(Sink sink)
291 	{
292 		assert(type == Type.sStringFragments);
293 		foreach (fragment; sStringFragments)
294 			sink.handleStringFragment(fragment);
295 	}
296 
297 	void readArray(Sink)(Sink sink)
298 	{
299 		assert(type == Type.sArray);
300 		foreach (el; sArray)
301 			el.read(sink);
302 	}
303 
304 	struct StringReader
305 	{
306 		S s;
307 		this(S s) { this.s = s; }
308 		void opCall(Sink)(Sink sink)
309 		{
310 			sink.handleString(s);
311 		}
312 	}
313 
314 	void readObject(Sink)(Sink sink)
315 	{
316 		assert(type == Type.sObject);
317 		foreach (name, ref value; sObject)
318 			sink.handleField(StringReader(name), boundFunctorOf!read(&value));
319 	}
320 }
321 
322 unittest
323 {
324 	SerializedObject!(immutable(char)) s1, s2;
325 	s1 = "aoeu";
326 	s1.read(&s2);
327 }
328 
329 unittest
330 {
331 	import ae.utils.serialization.json;
332 	auto s = jsonParse!(SerializedObject!(immutable(char)))(`null`);
333 	assert(s.type == s.Type.sNull);
334 }