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