1 module graphql.schema.resolver; 2 3 import std.array : empty; 4 import std.format : format; 5 import std.meta; 6 import std.traits; 7 import std.typecons : Nullable; 8 import std.experimental.logger; 9 import std.stdio; 10 import std.string : capitalize; 11 12 import vibe.data.json; 13 14 import graphql.schema.types; 15 import graphql.schema.typeconversions; 16 import graphql.helper; 17 import graphql.traits; 18 import graphql.constants; 19 import graphql.graphql; 20 21 @safe: 22 23 alias QueryResolver(Con) = Json delegate(string name, Json parent, 24 Json args, ref Con context) @safe; 25 26 QueryResolver!(Con) buildSchemaResolver(Type, Con)() { 27 return ret; 28 } 29 30 QueryResolver!(Con) buildTypeResolver(Type, Con)() { 31 return ret; 32 } 33 34 GQLDSchema!(Type) toSchema(Type)() { 35 typeof(return) ret = new GQLDSchema!Type(); 36 37 static foreach(qms; ["queryType", "mutationType", "subscriptionType"]) {{ 38 GQLDMap cur = new GQLDMap(); 39 cur.name = qms; 40 ret.member[qms] = cur; 41 if(qms == "queryType") { 42 cur.member["__schema"] = ret.__schema; 43 cur.member["__type"] = ret.__nonNullType; 44 } 45 static if(__traits(hasMember, Type, qms)) { 46 alias QMSType = typeof(__traits(getMember, Type, qms)); 47 static foreach(mem; __traits(allMembers, QMSType)) {{ 48 alias MemType = typeof(__traits(getMember, QMSType, mem)); 49 static if(isCallable!(MemType)) {{ 50 GQLDOperation op = qms == "queryType" 51 ? new GQLDQuery() 52 : qms == "mutationType" ? new GQLDMutation() 53 : qms == "subscriptionType" ? new GQLDSubscription() 54 : null; 55 cur.member[mem] = op; 56 assert(op !is null); 57 op.returnType = typeToGQLDType!(ReturnType!(MemType))( 58 ret 59 ); 60 61 alias paraNames = ParameterIdentifierTuple!( 62 __traits(getMember, QMSType, mem) 63 ); 64 alias paraTypes = Parameters!( 65 __traits(getMember, QMSType, mem) 66 ); 67 static foreach(idx; 0 .. paraNames.length) { 68 op.parameters[paraNames[idx]] = 69 typeToGQLDType!(paraTypes[idx])(ret); 70 } 71 }} 72 }} 73 } 74 }} 75 return ret; 76 } 77 78 enum __DirectiveLocation { 79 QUERY 80 , MUTATION 81 , SUBSCRIPTION 82 , FIELD 83 , FRAGMENT_DEFINITION 84 , FRAGMENT_SPREAD 85 , INLINE_FRAGMENT 86 , SCHEMA 87 , SCALAR 88 , OBJECT 89 , FIELD_DEFINITION 90 , ARGUMENT_DEFINITION 91 , INTERFACE 92 , UNION 93 , ENUM 94 , ENUM_VALUE 95 , INPUT_OBJECT 96 , INPUT_FIELD_DEFINITION 97 } 98 99 class __Directive { 100 string name; 101 Nullable!string description; 102 __DirectiveLocation[] locations; 103 __InputValue[] args; 104 } 105 106 enum __TypeKind { 107 SCALAR 108 , OBJECT 109 , INTERFACE 110 , UNION 111 , ENUM 112 , INPUT_OBJECT 113 , LIST 114 , NON_NULL 115 } 116 117 class __EnumValue { 118 string name; 119 Nullable!string description; 120 bool isDeprecated; 121 Nullable!string deprecationReason; 122 } 123 124 class __InputValue { 125 string name; 126 Nullable!string description; 127 __Type type; 128 Nullable!string defaultValue; 129 } 130 131 class __Field { 132 string name; 133 Nullable!string description; 134 __InputValue[] args; 135 __Type type; 136 bool isDeprecated; 137 Nullable!string deprecationReason; 138 } 139 140 class __Type { 141 __TypeKind kind; 142 string name; 143 Nullable!string description; 144 __Field[] fields(bool includeDeprecated = false) { assert(false); 145 } 146 Nullable!(__Type[]) interfaces; 147 Nullable!(__Type[]) possibleTypes; 148 Nullable!(__Field[]) enumValues(bool includeDeprecated = false) { 149 assert(false); 150 } 151 Nullable!(__InputValue[]) inputFields; 152 Nullable!(__Type) ofType; 153 } 154 155 class __Schema { 156 __Type types; 157 __Directive directives; 158 } 159 160 void setDefaultSchemaResolver(T, Con)(GraphQLD!(T,Con) graphql) { 161 162 Json typeResolverImpl(Type)(ref const(StringTypeStrip) stripType, 163 Json parent) 164 { 165 Json ret = Json.emptyObject(); 166 const bool inner = stripType.innerNotNull; 167 const bool outer = stripType.outerNotNull; 168 const bool arr = stripType.arr; 169 170 if(inner && outer && arr) { 171 alias PassType = Type[]; 172 ret["data"] = typeToJson!(PassType,T)(); 173 } else if(!inner && outer && arr) { 174 static if(!is(Type == void)) { 175 alias PassType = Nullable!(Type)[]; 176 ret["data"] = typeToJson!(PassType,T)(); 177 } else { 178 ret.insertError(format("invalid type %s in %s", 179 Type.stringof, 180 parent.toPrettyString())); 181 } 182 } else if(inner && !outer && arr) { 183 alias PassType = Nullable!(Type[]); 184 ret["data"] = typeToJson!(PassType,T)(); 185 } else if(!inner && !outer && arr) { 186 static if(!is(type == void)) { 187 alias PassType = Nullable!(Nullable!(Type)[]); 188 ret["data"] = typeToJson!(PassType,T)(); 189 } else { 190 ret.insertError(format("invalid type %s in %s", 191 Type.stringof, 192 parent.toPrettyString())); 193 } 194 } else if(!inner && !arr) { 195 static if(!is(Type == void)) { 196 alias PassType = Nullable!(Type); 197 ret["data"] = typeToJson!(PassType,T)(); 198 } else { 199 ret.insertError(format("invalid type %s in %s", 200 Type.stringof, 201 parent.toPrettyString())); 202 } 203 } else if(inner && !arr) { 204 alias PassType = Type; 205 ret["data"] = typeToJson!(PassType,T)(); 206 } else { 207 assert(false, format("%s", stripType)); 208 } 209 graphql.defaultResolverLog.logf("%s %s", stripType.str, ret["data"]); 210 return ret; 211 } 212 213 auto typeResolver = delegate(string name, Json parent, 214 Json args, ref Con context) @safe 215 { 216 graphql.defaultResolverLog.logf( 217 "TTTTTTRRRRRRR name %s args %s parent %s", name, args, 218 parent); 219 Json ret = Json.emptyObject(); 220 string typeName; 221 if(Constants.name in args) { 222 typeName = args[Constants.name].get!string(); 223 } 224 if(Constants.typenameOrig in parent) { 225 typeName = parent[Constants.typenameOrig].get!string(); 226 } else if(Constants.name in parent) { 227 typeName = parent[Constants.name].get!string(); 228 } 229 string typeCap; 230 string old; 231 StringTypeStrip stripType; 232 if(typeName.empty) { 233 ret.insertError("unknown type"); 234 goto retLabel; 235 } else { 236 typeCap = typeName; 237 old = typeName; 238 } 239 stripType = typeCap.stringTypeStrip(); 240 graphql.defaultResolverLog.logf("%s %s", __LINE__, stripType); 241 static foreach(type; collectTypes!(T)) {{ 242 enum typeConst = typeToTypeName!(type); 243 if(stripType.str == typeConst) { 244 static if(!is(type == void)) { 245 ret = typeResolverImpl!(type)(stripType, parent); 246 goto retLabel; 247 } 248 } else { 249 graphql.defaultResolverLog.logf("||||||||||| %s %s", 250 stripType.str, typeConst 251 ); 252 } 253 }} 254 if(stripType.str == "__Type") { 255 ret = typeResolverImpl!(__Type)(stripType, parent); 256 } else if(stripType.str == "__Field") { 257 ret = typeResolverImpl!(__Field)(stripType, parent); 258 } else if(stripType.str == "__InputValue") { 259 ret = typeResolverImpl!(__InputValue)(stripType, parent); 260 } else if(stripType.str == "__EnumValue") { 261 ret = typeResolverImpl!(__EnumValue)(stripType, parent); 262 } else if(stripType.str == "__TypeKind") { 263 ret = typeResolverImpl!(__TypeKind)(stripType, parent); 264 } else if(stripType.str == "__Directive") { 265 ret = typeResolverImpl!(__Directive)(stripType, parent); 266 } else if(stripType.str == "__DirectiveLocation") { 267 ret = typeResolverImpl!(__DirectiveLocation)(stripType, parent); 268 } 269 retLabel: 270 //graphql.defaultResolverLog.logf("%s", ret.toPrettyString()); 271 graphql.defaultResolverLog.logf("TTTTT____RRRR %s", 272 ret.toPrettyString()); 273 return ret; 274 }; 275 276 QueryResolver!(Con) schemaResolver = delegate(string name, Json parent, 277 Json args, ref Con context) @safe 278 { 279 //logf("%s %s %s", name, args, parent); 280 StringTypeStrip stripType; 281 Json ret = Json.emptyObject(); 282 ret["data"] = Json.emptyObject(); 283 ret["data"]["types"] = Json.emptyArray(); 284 ret["data"]["types"] ~= 285 typeResolverImpl!(__Type)(stripType, parent)["data"]; 286 ret["data"]["types"] ~= 287 typeResolverImpl!(__Field)(stripType, parent)["data"]; 288 ret["data"]["types"] ~= 289 typeResolverImpl!(__InputValue)(stripType, parent)["data"]; 290 ret["data"]["types"] ~= 291 typeResolverImpl!(__EnumValue)(stripType, parent)["data"]; 292 ret["data"]["types"] ~= 293 typeResolverImpl!(__TypeKind)(stripType, parent)["data"]; 294 ret["data"]["types"] ~= 295 typeResolverImpl!(__Directive)(stripType, parent)["data"]; 296 ret["data"]["types"] ~= 297 typeResolverImpl!(__DirectiveLocation)(stripType, parent) 298 ["data"]; 299 300 alias AllTypes = collectTypes!(T); 301 alias NoListOrArray = staticMap!(stripArrayAndNullable, AllTypes); 302 alias FixUp = staticMap!(fixupBasicTypes, NoListOrArray); 303 static if(hasMember!(T, Constants.directives)) { 304 alias NoDirectives = EraseAll!( 305 typeof(__traits(getMember, T, Constants.directives)), 306 FixUp 307 ); 308 } else { 309 alias NoDirectives = FixUp; 310 } 311 alias NoDup = NoDuplicates!(EraseAll!(T, NoDirectives)); 312 static foreach(type; NoDup) {{ 313 Json tmp = typeToJsonImpl!(type,T,type)(); 314 ret["data"]["types"] ~= tmp; 315 }} 316 static if(hasMember!(T, Constants.directives)) { 317 ret["data"][Constants.directives] = 318 directivesToJson!(typeof( 319 __traits(getMember, T, Constants.directives) 320 )); 321 } 322 323 static foreach(tName; ["subscriptionType", 324 "queryType", "mutationType"]) 325 { 326 static if(hasMember!(T, tName)) { 327 ret["data"][tName] = 328 typeToJsonImpl!(typeof(__traits(getMember, T, tName)), 329 T, typeof(__traits(getMember, T, tName))); 330 } 331 } 332 graphql.defaultResolverLog.logf("%s", ret.toPrettyString()); 333 return ret; 334 }; 335 336 graphql.setResolver("queryType", "__type", 337 delegate(string name, Json parent, Json args, ref Con context) @safe 338 { 339 graphql.defaultResolverLog.logf("%s %s %s", name, parent, args); 340 Json tr = typeResolver(name, parent, args, context); 341 Json ret = Json.emptyObject(); 342 ret["data"] = tr["data"]; 343 graphql.defaultResolverLog.logf("%s %s", tr.toPrettyString(), 344 ret.toPrettyString()); 345 return ret; 346 } 347 ); 348 349 graphql.setResolver("queryType", "__schema", schemaResolver); 350 351 graphql.setResolver("__Field", "type", 352 delegate(string name, Json parent, Json args, ref Con context) @safe 353 { 354 graphql.defaultResolverLog.logf("name %s, parent %s, args %s", 355 name, parent, args 356 ); 357 Json ret = typeResolver(name, parent, args, context); 358 graphql.defaultResolverLog.logf("FIELDDDDD TYPPPPPE %s", 359 ret.toPrettyString() 360 ); 361 return ret; 362 } 363 ); 364 365 graphql.setResolver("__InputValue", "type", 366 delegate(string name, Json parent, Json args, ref Con context) @safe 367 { 368 graphql.defaultResolverLog.logf("%s %s %s", name, parent, args); 369 Json tr = typeResolver(name, parent, args, context); 370 Json ret = Json.emptyObject(); 371 Json d = tr["data"]; 372 ret["data"] = d; 373 graphql.defaultResolverLog.logf("%s %s", tr.toPrettyString(), 374 ret.toPrettyString() 375 ); 376 return ret; 377 } 378 ); 379 380 graphql.setResolver("__Type", "ofType", 381 delegate(string name, Json parent, Json args, ref Con context) @safe 382 { 383 graphql.defaultResolverLog.logf("name %s, parent %s, args %s", 384 name, parent, args 385 ); 386 Json ret = Json.emptyObject(); 387 Json ofType; 388 if(parent.hasPathTo("ofType", ofType)) { 389 ret["data"] = ofType; 390 } else { 391 ret["data"] = Json(null); 392 } 393 graphql.defaultResolverLog.logf("%s", ret); 394 return ret; 395 } 396 ); 397 398 graphql.setResolver("__Type", "interfaces", 399 delegate(string name, Json parent, Json args, ref Con context) @safe 400 { 401 graphql.defaultResolverLog.logf("name %s, parent %s, args %s", 402 name, parent, args 403 ); 404 Json ret = Json.emptyObject(); 405 ret["data"] = Json.emptyObject(); 406 if("kind" in parent 407 && parent["kind"].get!string() == "OBJECT") 408 { 409 ret["data"] = Json.emptyArray(); 410 } 411 if("interfacesNames" !in parent) { 412 return ret; 413 } 414 Json interNames = parent["interfacesNames"]; 415 if(interNames.type == Json.Type.array) { 416 if(interNames.length > 0) { 417 assert(ret["data"].type == Json.Type.array, 418 format("%s", parent.toPrettyString()) 419 ); 420 ret["data"] = Json.emptyArray(); 421 foreach(Json it; interNames.byValue()) { 422 string typeName = it.get!string(); 423 string typeCap = capitalize(typeName); 424 l: switch(typeCap) { 425 static foreach(type; collectTypes!(T)) {{ 426 case typeToTypeName!(type): { 427 alias striped = 428 stripArrayAndNullable!type; 429 graphql.defaultResolverLog.logf("%s %s", 430 typeCap, striped.stringof 431 ); 432 ret["data"] ~= 433 typeToJsonImpl!(striped,T,type)(); 434 break l; 435 } 436 }} 437 default: break; 438 } 439 } 440 } 441 } 442 graphql.defaultResolverLog.logf("__Type.interfaces result %s", 443 ret 444 ); 445 return ret; 446 } 447 ); 448 449 graphql.setResolver("__Type", "possibleTypes", 450 delegate(string name, Json parent, Json args, ref Con context) @safe 451 { 452 graphql.defaultResolverLog.logf("name %s, parent %s, args %s", 453 name, parent, args 454 ); 455 Json ret = Json.emptyObject(); 456 if("possibleTypesNames" !in parent) { 457 ret["data"] = Json(null); 458 return ret; 459 } 460 Json pTypesNames = parent["possibleTypesNames"]; 461 if(pTypesNames.type == Json.Type.array) { 462 graphql.defaultResolverLog.log(); 463 ret["data"] = Json.emptyArray(); 464 foreach(Json it; pTypesNames.byValue()) { 465 string typeName = it.get!string(); 466 string typeCap = capitalize(typeName); 467 l: switch(typeCap) { 468 static foreach(type; collectTypes!(T)) { 469 case typeToTypeName!(type): { 470 alias striped = stripArrayAndNullable!type; 471 graphql.defaultResolverLog.logf("%s %s", 472 typeCap, striped.stringof 473 ); 474 ret["data"] ~= 475 typeToJsonImpl!(striped,T,type)(); 476 break l; 477 } 478 } 479 default: { 480 break; 481 } 482 } 483 } 484 } else { 485 graphql.defaultResolverLog.log(); 486 ret["data"] = Json(null); 487 } 488 return ret; 489 } 490 ); 491 }