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 }