1 module graphql.helper;
2 
3 import std.algorithm.searching : canFind;
4 import std.algorithm.iteration : splitter;
5 import std.exception : enforce;
6 import std.experimental.logger;
7 
8 import vibe.data.json;
9 
10 @safe:
11 
12 enum d = "data";
13 enum e = "error";
14 
15 string firstCharUpperCase(string input) {
16 	import std.conv : to;
17 	import std.uni : isUpper, toUpper;
18 	import std.array : front, popFront;
19 	if(isUpper(input.front)) {
20 		return input;
21 	}
22 
23 	dchar f = input.front;
24 	input.popFront();
25 
26 	return to!string(toUpper(f)) ~ input;
27 }
28 
29 Json returnTemplate() {
30 	Json ret = Json.emptyObject();
31 	ret["data"] = Json.emptyObject();
32 	ret["error"] = Json.emptyArray();
33 	return ret;
34 }
35 
36 void insertError(T)(ref Json result, T t) {
37 	Json err = serializeToJson(t);
38 	if(e !in result) {
39 		result[e] = Json.emptyArray();
40 	}
41 	enforce(result[e].type == Json.Type.array);
42 	if(!canFind(result[e].byValue(), err)) {
43 		result[e] ~= err;
44 	}
45 }
46 
47 void insertPayload(ref Json result, string field, Json data) {
48 	if(d in data) {
49 		if(d !in result) {
50 			result[d] = Json.emptyObject();
51 		}
52 		enforce(result[d].type == Json.Type.object);
53 		result[d][field] = data[d];
54 	}
55 	if(e in data) {
56 		if(e !in result) {
57 			result[e] = Json.emptyArray();
58 		}
59 		enforce(result[e].type == Json.Type.array);
60 		if(!canFind(result[e].byValue(), data[e])) {
61 			result[e] ~= data[e];
62 		}
63 	}
64 }
65 
66 bool isScalar(ref const(Json) data) {
67 	return data.type == Json.Type.bigInt
68 			|| data.type == Json.Type.bool_
69 			|| data.type == Json.Type.float_
70 			|| data.type == Json.Type.int_
71 			|| data.type == Json.Type..string;
72 }
73 
74 bool dataIsEmpty(ref const(Json) data) {
75 	import std.experimental.logger;
76 	if(data.type == Json.Type.object) {
77 		foreach(key, value; data.byKeyValue()) {
78 			if(key != "error" && !value.dataIsEmpty()) {
79 				return false;
80 			}
81 		}
82 		return true;
83 	} else if(data.type == Json.Type.null_
84 			|| data.type == Json.Type.undefined
85 		)
86 	{
87 		return true;
88 	} else if(data.type == Json.Type.array) {
89 		return data.length == 0;
90 	} else if(data.type == Json.Type.bigInt
91 			|| data.type == Json.Type.bool_
92 			|| data.type == Json.Type.float_
93 			|| data.type == Json.Type.int_
94 			|| data.type == Json.Type..string
95 		)
96 	{
97 		return false;
98 	}
99 
100 	return true;
101 }
102 
103 unittest {
104 	string t = `{
105               "kind": {},
106               "fields": null,
107               "name": {}
108             }`;
109 	Json j = parseJsonString(t);
110 	assert(j.dataIsEmpty());
111 }
112 
113 bool dataIsNull(ref const(Json) data) {
114 	import std.format : format;
115 	enforce(data.type == Json.Type.object, format("%s", data));
116 	if(const(Json)* d = "data" in data) {
117 		return d.type == Json.Type.null_;
118 	}
119 	return false;
120 }
121 
122 /** Merge two Json objects.
123 Values in a take precedence over values in b.
124 */
125 Json joinJson(Json a, Json b) {
126 	// we can not merge null or undefined values
127 	if(a.type == Json.Type.null_ || a.type == Json.Type.undefined) {
128 		return b;
129 	}
130 	if(b.type == Json.Type.null_ || b.type == Json.Type.undefined) {
131 		return a;
132 	}
133 
134 	// we need objects to merge
135 	if(a.type == Json.Type.object && b.type == Json.Type.object) {
136 		Json ret = a.clone();
137 		foreach(key, value; b.byKeyValue()) {
138 			if(key !in ret) {
139 				ret[key] = value;
140 			}
141 		}
142 		return ret;
143 	}
144 	return a;
145 }
146 
147 unittest {
148 	Json a = parseJsonString(`{"overSize":200}`);
149 	Json b = parseJsonString(`{}`);
150 	Json c = joinJson(b, a);
151 	assert(c == a);
152 }
153 
154 template toType(T) {
155 	import std.bigint : BigInt;
156 	import std.traits : isArray, isIntegral, isAggregateType, isFloatingPoint,
157 		   isSomeString;
158 	static if(is(T == bool)) {
159 		enum toType = Json.Type.bool_;
160 	} else static if(isIntegral!(T)) {
161 		enum toType = Json.Type.int_;
162 	} else static if(isFloatingPoint!(T)) {
163 		enum toType = Json.Type.float_;
164 	} else static if(isSomeString!(T)) {
165 		enum toType = Json.Type..string;
166 	} else static if(isArray!(T)) {
167 		enum toType = Json.Type.array;
168 	} else static if(isAggregateType!(T)) {
169 		enum toType = Json.Type.object;
170 	} else static if(is(T == BigInt)) {
171 		enum toType = Json.Type.bigint;
172 	} else {
173 		enum toType = Json.Type.undefined;
174 	}
175 }
176 
177 bool hasPathTo(T)(Json data, string path, ref T ret) {
178 	enum TT = toType!T;
179 	auto sp = path.splitter(".");
180 	string f;
181 	while(!sp.empty) {
182 		f = sp.front;
183 		sp.popFront();
184 		if(data.type != Json.Type.object || f !in data) {
185 			return false;
186 		} else {
187 			data = data[f];
188 		}
189 	}
190 	static if(is(T == Json)) {
191 		ret = data;
192 		return true;
193 	} else {
194 		if(data.type == TT) {
195 			ret = data.to!T();
196 			return true;
197 		}
198 		return false;
199 	}
200 }
201 
202 /**
203 params:
204 	path = A "." seperated path
205 */
206 T getWithDefault(T)(Json data, string[] paths...) {
207 	enum TT = toType!T;
208 	T ret = T.init;
209 	foreach(string path; paths) {
210 		if(hasPathTo!T(data, path, ret)) {
211 			return ret;
212 		}
213 	}
214 	return ret;
215 }
216 
217 unittest {
218 	Json d = parseJsonString(`{"error":[],"data":{"commanderId":8,
219 			"__typename":"Starship","series":["DeepSpaceNine",
220 			"TheOriginalSeries"],"id":43,"name":"Defiant","size":130,
221 			"crewIds":[9,10,11,1,12,13,8],"designation":"NX-74205"}}`);
222 	string r = d.getWithDefault!string("data.__typename");
223 	assert(r == "Starship", r);
224 }
225 
226 unittest {
227 	Json d = parseJsonString(`{"commanderId":8,
228 			"__typename":"Starship","series":["DeepSpaceNine",
229 			"TheOriginalSeries"],"id":43,"name":"Defiant","size":130,
230 			"crewIds":[9,10,11,1,12,13,8],"designation":"NX-74205"}`);
231 	string r = d.getWithDefault!string("data.__typename", "__typename");
232 	assert(r == "Starship", r);
233 }
234 
235 unittest {
236 	Json d = parseJsonString(`{"commanderId":8,
237 			"__typename":"Starship","series":["DeepSpaceNine",
238 			"TheOriginalSeries"],"id":43,"name":"Defiant","size":130,
239 			"crewIds":[9,10,11,1,12,13,8],"designation":"NX-74205"}`);
240 	string r = d.getWithDefault!string("__typename");
241 	assert(r == "Starship", r);
242 }
243 
244 // TODO should return ref
245 auto accessNN(string[] tokens,T)(T tmp0) {
246 	import std.array : back;
247 	import std.format : format;
248 	if(tmp0 !is null) {
249 		static foreach(idx, token; tokens) {
250 			mixin(format!
251 				`if(tmp%d is null) return null;
252 				auto tmp%d = tmp%d.%s;`(idx, idx+1, idx, token)
253 			);
254 		}
255 		return mixin(format("tmp%d", tokens.length));
256 	}
257 	return null;
258 }
259 
260 unittest {
261 	class A {
262 		int a;
263 	}
264 
265 	class B {
266 		A a;
267 	}
268 
269 	class C {
270 		B b;
271 	}
272 
273 	auto c1 = new C;
274 	assert(c1.accessNN!(["b", "a"]) is null);
275 
276 	c1.b = new B;
277 	assert(c1.accessNN!(["b"]) !is null);
278 
279 	assert(c1.accessNN!(["b", "a"]) is null);
280 	// TODO not sure why this is not a lvalue
281 	//c1.accessNN!(["b", "a"]) = new A;
282 	c1.b.a = new A;
283 	assert(c1.accessNN!(["b", "a"]) !is null);
284 }