The OpenD Programming Language

1 /+
2     == serialization ==
3     Copyright Alexey Drozhzhin aka Grim Maple 2024
4     Distributed under the Boost Software License, Version 1.0.
5 +/
6 /++
7     This is a serialization module for OpenD programming language!
8     It contains an API for making serializers and includes JSON serializator.
9 
10     The JSON serializator is implemented via `std.json`.
11 
12     ## Usage examples
13 
14     ### Basic usage
15 
16     By-design, only fields marked as [serializable] would be visible to serializator
17     ---
18     struct Foo
19     {
20         @serializable int bar;
21         float bar;
22     }
23     ---
24     Using the code above, only `bar` would be "visible" for serialization.
25     Please not that `@serializable` can be applied to field, or getter/setter functions.
26     A getter function is a function that returns non-void and has no parameters.
27     A setter function is a function that returns void and has exactly 1 parameter.
28     $(NOTE
29         Only a single [serializable] UDA is allowed per member. Having more will result in
30         a compile-time error.
31     )
32 
33     Example usage with getter/setter functions:
34     ---
35     struct Foo
36     {
37         @serializable void foo(int bar) { }
38         @serialziable int foo() { }
39     }
40     ---
41 
42     To control how serizliation is done, `@serializable` attribute has a constructor
43     that accepts a string, which is then used as a name for serializtion
44     ---
45     struct Foo
46     {
47         // This field will be serialized with the name "Bar"
48         @serializable("Bar") int baz;
49     }
50     ---
51 
52     If serialization fails for any reason, [SerializationException] is used.
53 
54     ### Advanced usage
55 
56     A common desirable behavior is to serialize structs/classes that were not originally annotated
57     with [serializable]. For such use-case, it's advised to use getter/setter serialization to convert
58     such types to another type that is easily serializable:
59 
60     ---
61     struct Foo
62     {
63         @serializable("entry") string serEntry() { return entry.path; }
64         @serializable("entry") void serEntry(string path) { entry = DirEntry(path); }
65         DirEntry entry;
66     }
67     ---
68 
69     ### Writing serializers
70 
71     To make a custom serializer, use any of those helper templates:
72     [serializableFields], [writeableSerializables], [readableSerializables].
73     Usage should be quite obvious from their names! Here's a silly sample code that
74     serializes a custom type `T`:
75     ---
76     string serialize(T)(auto ref T t)
77     {
78         streing return;
79         foreach(alias prop; readableSerializables!T)
80         {
81             auto name = getSerializableName!prop;
82             auto val = __traits(child, obj, prop).to!string;
83             return ~= name ~ ":" ~ val ~ "\n";
84         }
85         return return;
86     }
87     ---
88 
89     Please refer to the provided JSON serialization module for a comprehensive example!
90 +/
91 
92 module odc.serialization;
93 
94 import std.meta : Filter, AliasSeq;
95 import std.traits;
96 
97 /**
98  * A UDA for marking fields as serializable.
99  *
100  * Use on any field to mark it as serializable. Only fields and getters/setters can
101  * be marked as `serializeable`.
102  */
103 struct serializable
104 {
105     /**
106      * Constructs new `serializable`
107      *
108      * Params
109      *     n = field name in json
110      */
111     this(string n) @safe @nogc nothrow 
112     {
113         name = n;
114     }
115 
116     /**
117      * Controls the field name in serialized object.
118      *
119      * If set to "" (default), the field name is the same as in D code.
120      */
121     private string name;
122 }
123 
124 /**
125  * Retreive all fields of type `T` that are $(LREF serializable)
126  */
127 template serializableFields(T)
128 {
129     alias serializableFields = getSymbolsByUDA!(T, serializable);
130 
131     // Compile-time errors generation
132     static foreach(alias prop; getSymbolsByUDA!(T, serializable))
133     {
134         static assert(getUDAs!(prop, serializable).length == 1,
135             "Only 1 `serializable` UDA is allowed per property. See field `" ~ fullyQualifiedName!prop ~ "`.");
136         static if(isFunction!prop)
137         {
138             static assert(isGetterFunction!(FunctionTypeOf!prop) || isSetterFunction!(FunctionTypeOf!prop),
139                 "Function `" ~ fullyQualifiedName!prop ~ "` is not a getter or setter");
140         }
141     }
142 }
143 unittest
144 {
145     struct A
146     {
147         @serializable int a;
148         @serializable int foo() { return 1; }
149         @serializable void bar(int a) { b = a; }
150         int b;
151     }
152 }
153 
154 /**
155  * Is `T` marked as $(LREF serializable)
156  */
157 template isSerializable(alias T)
158 {
159     enum bool isSerializable = getUDAs!(T, serializable).length == 1;
160 }
161 unittest
162 {
163     struct A
164     {
165         @serializable int a;
166         int b;
167     }
168 
169     assert(isSerializable!(A.a));
170     assert(!isSerializable!(A.b));
171 }
172 
173 /**
174  * Retreive all writeable serializables for `T`. This includes properties and setters.
175  */
176 template writeableSerializables(alias T)
177 {
178     alias writeableSerializables = Filter!(isSerializableWriteable, serializableFields!T);
179 }
180 ///
181 @safe unittest
182 {
183     static struct A
184     {
185         @serializable void a(int value) { _a = value; }
186         @serializable int b;
187         private int _a;
188     }
189 }
190 ///
191 @safe unittest
192 {
193     struct A
194     {
195         @serializable int a;
196         @serializable void foo(int s);
197     }
198 }
199 
200 /**
201  * Retreive all readable serializables for `T`. This includes properties and getters
202  */
203 template readableSerializables(alias T)
204 {
205     alias readableSerializables = Filter!(isSerializableReadable, serializableFields!T);
206 }
207 ///
208 @safe unittest
209 {
210     static struct A
211     {
212         @serializable void a(int value) { _a = value; }
213         @serializable int b;
214         private int _a;
215     }
216 }
217 
218 /**
219  * Retreive the name for this serializable
220  */
221 template getSerializableName(alias T) if(isSerializable!T)
222 {
223     static if(is(getUDAs!(T, serializable)[0] == struct))
224         enum string getSerializableName = __traits(identifier, T);
225     else
226         enum string getSerializableName = getUDAs!(T, serializable)[0].name == "" ? __traits(identifier, T) : getUDAs!(T, serializable)[0].name ;
227 }
228 ///
229 @safe @nogc unittest
230 {
231     struct A
232     {
233         @serializable int a;
234         @serializable() int b;
235         @serializable("test") int c;
236     }
237 
238     assert(getSerializableName!(A.a) == "a");
239     assert(getSerializableName!(A.b) == "b");
240     assert(getSerializableName!(A.c) == "test");
241 }
242 
243 /**
244  * Is this $(LREF serializable) readable
245  */
246 template isSerializableReadable(alias T) if(isSerializable!T)
247 {
248     static if (isFunction!T)
249         enum bool isSerializableReadable = isGetterFunction!T;
250     else
251         enum bool isSerializableReadable = true;
252 }
253 @safe @nogc unittest
254 {
255     struct A
256     {
257         @serializable void foo(int a) { }
258         @serializable int bar() { return 1; }
259         @serializable int a;
260         int b;
261     }
262 
263     assert(isSerializableReadable!(A.a));
264     assert(isSerializableReadable!(A.bar));
265     assert(!isSerializableReadable!(A.foo));
266 }
267 
268 /**
269  * Is this $(LREF serializable) writeable
270  */
271 template isSerializableWriteable(alias T) if(isSerializable!T)
272 {
273     static if(isFunction!T)
274         enum bool isSerializableWriteable = isSetterFunction!T;
275     else
276         enum bool isSerializableWriteable = true;
277 }
278 ///
279 @safe @nogc unittest
280 {
281     struct A
282     {
283         @serializable void foo(int a) { }
284         @serializable int a;
285         @serializable int bar() { return 1; }
286     }
287 
288     assert(isSerializableWriteable!(A.foo));
289     assert(isSerializableWriteable!(A.a));
290     assert(!isSerializableWriteable!(A.bar));
291 }
292 
293 /**
294  * A base exception class for all serialization exceptions.
295  */
296 class SerializationException : Exception
297 {
298     this(string message) @safe
299     {
300         super(message);
301     }
302 }
303 
304 // Internal stuff
305 private
306 {
307     template isSetterFunction(alias T)
308     {
309         enum bool isSetterFunction = isFunction!T && ((Parameters!T).length == 1) && is(ReturnType!T == void);
310     }
311     ///
312     @safe @nogc unittest
313     {
314         void foo(int b) { }
315         int fee() { return 0; }
316         int bar(int b) { return b; }
317         void baz(int a, int b) { }
318         assert(isSetterFunction!(FunctionTypeOf!foo));
319         assert(!isSetterFunction!(FunctionTypeOf!fee));
320         assert(!isSetterFunction!(FunctionTypeOf!bar));
321         assert(!isSetterFunction!(FunctionTypeOf!baz));
322     }
323 
324     template isGetterFunction(alias T)
325     {
326         enum bool isGetterFunction = isFunction!T && ((Parameters!T).length == 0) && !is(ReturnType!T == void);
327     }
328     ///
329     @safe @nogc unittest
330     {
331         void foo(int b) { }
332         int fee() { return 0; }
333         int bar(int b) { return b; }
334         void baz(int a, int b) { }
335         struct Test
336         {
337             int bar() { return 1; }
338             void baz() { }
339         }
340         assert(isGetterFunction!(FunctionTypeOf!fee));
341         assert(isGetterFunction!(Test.bar));
342         assert(!isGetterFunction!(Test.baz));
343         assert(!isGetterFunction!(FunctionTypeOf!foo));
344         assert(!isGetterFunction!(FunctionTypeOf!bar));
345         assert(!isGetterFunction!(FunctionTypeOf!baz));
346     }
347 }