1 /**
2   * Internal helper functions/templates.
3   *
4   * Authors: <a href="https://github.com/rcorre">rcorre</a>
5 	* License: <a href="http://opensource.org/licenses/MIT">MIT</a>
6 	* Copyright: Copyright © 2015, rcorre
7   * Date: 3/29/15
8   */
9 module jsonizer.internal.util;
10 
11 import std.typetuple;
12 import jsonizer.internal.attribute;
13 
14 /// Return members of T that could be serializeable.
15 /// Use `jsonizeKey` to reduce the result of `filteredMembers` to only members marked for
16 /// serialization
17 template filteredMembers(T) {
18   enum filteredMembers =
19     EraseAll!("_toJSON",
20     EraseAll!("_fromJSON",
21     EraseAll!("__ctor",
22       __traits(allMembers, T))));
23 }
24 
25 /// if member is marked with @jsonize("someName"), returns "someName".
26 /// if member is marked with @jsonize, returns the name of the member.
27 /// if member is not marked with @jsonize, returns null
28 /// example usage within a struct/class:
29 /// enum key = jsonizeKey!(__traits(getMember, this, member), member)
30 template jsonizeKey(alias member, string defaultName) {
31   alias found = findAttribute!(jsonize, member);
32 
33   static if (found.length == 0) {
34     // no @jsonize attribute found
35     enum jsonizeKey = null;
36   }
37   else static if (isValueAttribute!(found[$ - 1])) {
38     // most-nested attribute is @jsonize(args...), key is attr.key
39     enum key = found[$ - 1].key;
40     enum jsonizeKey = (key is null) ? defaultName : key;
41   }
42   else {
43     // most-nested attribute is @jsonize, no custom key specified
44     enum jsonizeKey = defaultName;
45   }
46 }
47 
48 /// Hack to catch and ignore aliased types.
49 /// for example, a class contains 'alias Integer = int',
50 /// it will be redirected to this template and return null (cannot be jsonized)
51 template jsonizeKey(T, string unused) {
52   enum jsonizeKey = null;
53 }
54 
55 /// return true if member is marked with @jsonize(JsonizeOptional.yes).
56 template isOptional(alias member) {
57   alias found = Filter!(isValueAttribute, findAttribute!(jsonize, member));
58 
59   // find an explicit JsonizeOptional parameter.
60   // start with the attribute closest to the member and move outwards.
61   template helper(attrs ...) {
62     static if (attrs.length == 0) {
63       // recursion endpoint, no JsonizeOptional found.
64       enum helper = false;
65     }
66     else static if (attrs[$ - 1].optional == JsonizeOptional.unspecified) {
67       // unspecified, recurse to less-nested attribute
68       enum helper = helper!(attrs[0 .. $ - 1]);
69     }
70     else {
71       // specified either yes or no, use that value
72       enum helper = (attrs[$ - 1].optional == JsonizeOptional.yes);
73     }
74   }
75 
76   enum isOptional = helper!(found);
77 }
78 
79 /// Get a tuple of all attributes on `sym` matching `attr`.
80 template findAttribute(alias attr, alias sym) {
81   static assert(__traits(compiles, __traits(getAttributes, sym)),
82       "cannot get attributes of " ~ sym.stringof);
83 
84   template match(alias a) {
85     enum match = (is(a == attr) || is(typeof(a) == attr));
86   }
87 
88   alias findAttribute = Filter!(match, __traits(getAttributes, sym));
89 }
90 
91 unittest {
92   struct attr { int i; }
93   struct junk { int i; }
94 
95   void fun0() { }
96   @attr void fun1() { }
97   @attr(2) void fun2() { }
98   @junk @attr void fun3() { }
99   @attr(3) @junk @attr void fun4() { }
100 
101   static assert(findAttribute!(attr, fun0).length == 0);
102   static assert(findAttribute!(attr, fun1).length == 1);
103   static assert(findAttribute!(attr, fun2).length == 1);
104   static assert(findAttribute!(attr, fun3).length == 1);
105   static assert(findAttribute!(attr, fun4).length == 2);
106 
107   struct S0 { }
108   @attr struct S1 { }
109   @junk @attr struct S { }
110   static assert(findAttribute!(attr, S).length == 1);
111 }
112 
113 /// True if `sym` has an attribute `attr`.
114 template hasAttribute(alias attr, alias sym) {
115   enum hasAttribute = findAttribute!(attr, sym).length > 0;
116 }
117 
118 /// `hasAttribute`
119 unittest {
120   enum attr;
121 
122   void fun0() { }
123   @attr void fun1() { }
124 
125   static assert(!hasAttribute!(attr, fun0));
126   static assert( hasAttribute!(attr, fun1));
127 }
128 
129 /// True if `attr` has a value (e.g. it is not a type).
130 template isValueAttribute(alias attr) {
131   enum isValueAttribute = is(typeof(attr));
132 }
133 
134 /// `isValueAttribute`
135 unittest {
136   struct attr { int i; }
137 
138   @attr @attr(3) void fun() { }
139 
140   static assert(!isValueAttribute!(__traits(getAttributes, fun)[0]));
141   static assert( isValueAttribute!(__traits(getAttributes, fun)[1]));
142 }
143 
144 auto getMember(string name)() {
145   return (x) => __traits(getMember, x, name);
146 }
147 
148 T construct(T, P, Params ...)(P parent, Params params) {
149   static if (!is(P == typeof(null))) {
150     return parent..new T(params);
151   }
152   else static if (is(typeof(T(params)) == T)) {
153     return T(params);
154   }
155   else static if (is(typeof(new T(params)) == T)) {
156     return new T(params);
157   }
158   else {
159     static assert(0, "Cannot construct");
160   }
161 }
162 
163 unittest {
164   static struct Foo {
165     this(int i) { this.i = i; }
166     int i;
167   }
168 
169   assert(construct!Foo(null).i == 0);
170   assert(construct!Foo(null, 4).i == 4);
171   assert(!__traits(compiles, construct!Foo("asd")));
172 }
173 
174 unittest {
175   static class Foo {
176     this(int i) { this.i = i; }
177 
178     this(int i, string s) {
179       this.i = i;
180       this.s = s;
181     }
182 
183     int i;
184     string s;
185   }
186 
187   assert(construct!Foo(null, 4).i == 4);
188   assert(construct!Foo(null, 4, "asdf").s == "asdf");
189   assert(!__traits(compiles, construct!Foo("asd")));
190 }
191 
192 unittest {
193   class Foo {
194     class Bar {
195       int i;
196       this(int i) { this.i = i; }
197     }
198   }
199 
200   auto f = new Foo;
201   assert(construct!(Foo.Bar)(f, 2).i == 2);
202 }
203 
204 template hasDefaultCtor(T) {
205   import std.traits : isNested;
206   static if (isNested!T) {
207     alias P = typeof(__traits(parent, T).init);
208     enum hasDefaultCtor = is(typeof(P.init..new T()) == T);
209   }
210   else {
211     enum hasDefaultCtor = is(typeof(T()) == T) || is(typeof(new T()) == T);
212   }
213 }
214 
215 version(unittest) {
216   struct S1 { }
217   struct S2 { this(int i) { } }
218   struct S3 { @disable this(); }
219 
220   class C1 { }
221   class C2 { this(string s) { } }
222   class C3 { class Inner { } }
223   class C4 { class Inner { this(int i); } }
224 }
225 
226 unittest {
227   static assert( hasDefaultCtor!S1);
228   static assert( hasDefaultCtor!S2);
229   static assert(!hasDefaultCtor!S3);
230 
231   static assert( hasDefaultCtor!C1);
232   static assert(!hasDefaultCtor!C2);
233 
234   static assert( hasDefaultCtor!C3);
235   static assert( hasDefaultCtor!(C3.Inner));
236   static assert(!hasDefaultCtor!(C4.Inner));
237 }
238 
239 bool hasCustomJsonCtor(T)() {
240   static if (__traits(hasMember, T, "__ctor")) {
241     alias Overloads = TypeTuple!(__traits(getOverloads, T, "__ctor"));
242     foreach(overload ; Overloads) {
243       static if (staticIndexOf!(jsonize, __traits(getAttributes, overload)) >= 0) {
244         return true;
245       }
246     }
247   }
248   return false;
249 }
250 
251 unittest {
252   static struct S1 { }
253   static struct S2 { this(int i); }
254   static struct S3 { @jsonize this(int i); }
255   static struct S4 { this(float f); @jsonize this(int i); }
256 
257   static assert(!hasCustomJsonCtor!S1);
258   static assert(!hasCustomJsonCtor!S2);
259   static assert( hasCustomJsonCtor!S3);
260   static assert( hasCustomJsonCtor!S4);
261 
262   static class C1 { }
263   static class C2 { this() {} }
264   static class C3 { @jsonize this() {} }
265   static class C4 { @jsonize this(int i); this(float f); }
266 
267   static assert(!hasCustomJsonCtor!C1);
268   static assert(!hasCustomJsonCtor!C2);
269   static assert( hasCustomJsonCtor!C3);
270   static assert( hasCustomJsonCtor!C4);
271 }