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