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 void setDefaultSchemaResolver(T, Con)(GraphQLD!(T,Con) graphql) { 166 167 static Json typeResolverImpl(Type)(ref const(StringTypeStrip) stripType, 168 Json parent, GraphQLD!(T,Con) graphql) 169 { 170 Json ret = Json.emptyObject(); 171 const bool inner = stripType.innerNotNull; 172 const bool outer = stripType.outerNotNull; 173 const bool arr = stripType.arr; 174 175 if(inner && outer && arr) { 176 alias PassType = Type[]; 177 ret["data"] = typeToJson!(PassType,T)(); 178 } else if(!inner && outer && arr) { 179 static if(!is(Type == void)) { 180 alias PassType = Nullable!(Type)[]; 181 ret["data"] = typeToJson!(PassType,T)(); 182 } else { 183 ret.insertError(format("invalid type %s in %s", 184 Type.stringof, 185 parent.toPrettyString())); 186 } 187 } else if(inner && !outer && arr) { 188 alias PassType = Nullable!(Type[]); 189 ret["data"] = typeToJson!(PassType,T)(); 190 } else if(!inner && !outer && arr) { 191 static if(!is(type == void)) { 192 alias PassType = Nullable!(Nullable!(Type)[]); 193 ret["data"] = typeToJson!(PassType,T)(); 194 } else { 195 ret.insertError(format("invalid type %s in %s", 196 Type.stringof, 197 parent.toPrettyString())); 198 } 199 } else if(!inner && !arr) { 200 static if(!is(Type == void)) { 201 alias PassType = Nullable!(Type); 202 ret["data"] = typeToJson!(PassType,T)(); 203 } else { 204 ret.insertError(format("invalid type %s in %s", 205 Type.stringof, 206 parent.toPrettyString())); 207 } 208 } else if(inner && !arr) { 209 alias PassType = Type; 210 ret["data"] = typeToJson!(PassType,T)(); 211 } else { 212 assert(false, format("%s", stripType)); 213 } 214 graphql.defaultResolverLog.logf("%s %s", stripType.str, ret["data"]); 215 return ret; 216 } 217 218 alias ResolverFunction = 219 Json function(ref const(StringTypeStrip), Json, GraphQLD!(T,Con)) @safe; 220 221 static ResolverFunction[string] handlers; 222 223 if(handlers is null) { 224 static void processType(type)(ref ResolverFunction[string] handlers) { 225 static if(!is(type == void)) { 226 enum typeConst = typeToTypeName!(type); 227 handlers[typeConst] = &typeResolverImpl!(type); 228 } 229 } 230 231 execForAllTypes!(T, processType)(handlers); 232 233 foreach(t; AliasSeq!(__Type, __Field, __InputValue, 234 __EnumValue, __TypeKind, __Directive, __DirectiveLocation)) 235 { 236 handlers[t.stringof] = &typeResolverImpl!t; 237 } 238 } 239 240 auto typeResolver = delegate(string name, Json parent, 241 Json args, ref Con context) @safe 242 { 243 graphql.defaultResolverLog.logf( 244 "TTTTTTRRRRRRR name %s args %s parent %s", name, args, 245 parent); 246 Json ret = Json.emptyObject(); 247 string typeName; 248 if(Constants.name in args) { 249 typeName = args[Constants.name].get!string(); 250 } 251 if(Constants.typenameOrig in parent) { 252 typeName = parent[Constants.typenameOrig].get!string(); 253 } else if(Constants.name in parent) { 254 typeName = parent[Constants.name].get!string(); 255 } 256 string typeCap; 257 string old; 258 StringTypeStrip stripType; 259 if(typeName.empty) { 260 ret.insertError("unknown type"); 261 goto retLabel; 262 } else { 263 typeCap = typeName; 264 old = typeName; 265 } 266 stripType = typeCap.stringTypeStrip(); 267 graphql.defaultResolverLog.logf("%s %s", __LINE__, stripType); 268 269 if(auto h = stripType.str in handlers) { 270 ret = (*h)(stripType, parent, graphql); 271 } 272 retLabel: 273 //graphql.defaultResolverLog.logf("%s", ret.toPrettyString()); 274 graphql.defaultResolverLog.logf("TTTTT____RRRR %s", 275 ret.toPrettyString()); 276 return ret; 277 }; 278 279 QueryResolver!(Con) schemaResolver = delegate(string _unused, Json parent, 280 Json args, ref Con context) @safe 281 { 282 //logf("%s %s %s", name, args, parent); 283 StringTypeStrip stripType; 284 Json ret = Json.emptyObject(); 285 ret["data"] = Json.emptyObject(); 286 ret["data"]["types"] = Json.emptyArray(); 287 ret["data"]["types"] ~= 288 typeResolverImpl!(__Type)(stripType, parent, graphql)["data"]; 289 ret["data"]["types"] ~= 290 typeResolverImpl!(__Field)(stripType, parent, graphql)["data"]; 291 ret["data"]["types"] ~= 292 typeResolverImpl!(__InputValue)(stripType, parent, graphql)["data"]; 293 ret["data"]["types"] ~= 294 typeResolverImpl!(__EnumValue)(stripType, parent, graphql)["data"]; 295 ret["data"]["types"] ~= 296 typeResolverImpl!(__TypeKind)(stripType, parent, graphql)["data"]; 297 ret["data"]["types"] ~= 298 typeResolverImpl!(__Directive)(stripType, parent, graphql)["data"]; 299 ret["data"]["types"] ~= 300 typeResolverImpl!(__DirectiveLocation)(stripType, parent, 301 graphql)["data"]; 302 303 Json jsonTypes; 304 jsonTypes = Json.emptyArray; 305 static if(hasMember!(T, Constants.directives)) { 306 import graphql.schema.typeconversions : typeToTypeName; 307 immutable string directiveTypeName = 308 typeToTypeName!(typeof(__traits(getMember, T, 309 Constants.directives))); 310 } else { 311 immutable string directiveTypeName = ""; 312 } 313 foreach(n, ref tsn; SchemaReflection!T.instance.jsonTypes) { 314 if(n != directiveTypeName && tsn.canonical) 315 jsonTypes ~= tsn.typeJson.clone; 316 } 317 ret["data"]["types"] ~= jsonTypes; 318 static if(hasMember!(T, Constants.directives)) { 319 ret["data"][Constants.directives] = 320 directivesToJson!(typeof( 321 __traits(getMember, T, Constants.directives) 322 )); 323 } 324 325 static foreach(tName; ["subscriptionType", 326 "queryType", "mutationType"]) 327 { 328 static if(hasMember!(T, tName)) { 329 ret["data"][tName] = 330 typeToJsonImpl!(typeof(__traits(getMember, T, tName)), 331 T, typeof(__traits(getMember, T, tName))); 332 } 333 } 334 graphql.defaultResolverLog.logf("%s", ret.toPrettyString()); 335 return ret; 336 }; 337 338 graphql.setResolver("queryType", "__type", 339 delegate(string name, Json parent, Json args, ref Con context) @safe 340 { 341 graphql.defaultResolverLog.logf("%s %s %s", name, parent, args); 342 Json tr = typeResolver(name, parent, args, context); 343 Json ret = Json.emptyObject(); 344 ret["data"] = tr["data"]; 345 graphql.defaultResolverLog.logf("%s %s", tr.toPrettyString(), 346 ret.toPrettyString()); 347 return ret; 348 } 349 ); 350 351 graphql.setResolver("queryType", "__schema", schemaResolver); 352 353 graphql.setResolver("__Field", "type", 354 delegate(string name, Json parent, Json args, ref Con context) @safe 355 { 356 graphql.defaultResolverLog.logf("name %s, parent %s, args %s", 357 name, parent, args 358 ); 359 Json ret = typeResolver(name, parent, args, context); 360 graphql.defaultResolverLog.logf("FIELDDDDD TYPPPPPE %s", 361 ret.toPrettyString() 362 ); 363 return ret; 364 } 365 ); 366 367 graphql.setResolver("__InputValue", "type", 368 delegate(string name, Json parent, Json args, ref Con context) @safe 369 { 370 graphql.defaultResolverLog.logf("%s %s %s", name, parent, args); 371 Json tr = typeResolver(name, parent, args, context); 372 Json ret = Json.emptyObject(); 373 Json d = tr["data"]; 374 ret["data"] = d; 375 graphql.defaultResolverLog.logf("%s %s", tr.toPrettyString(), 376 ret.toPrettyString() 377 ); 378 return ret; 379 } 380 ); 381 382 graphql.setResolver("__Type", "ofType", 383 delegate(string name, Json parent, Json args, ref Con context) @safe 384 { 385 graphql.defaultResolverLog.logf("name %s, parent %s, args %s", 386 name, parent, args 387 ); 388 Json ret = Json.emptyObject(); 389 Json ofType; 390 if(parent.hasPathTo("ofType", ofType)) { 391 ret["data"] = ofType; 392 } else { 393 ret["data"] = Json(null); 394 } 395 graphql.defaultResolverLog.logf("%s", ret); 396 return ret; 397 } 398 ); 399 400 graphql.setResolver("__Type", "interfaces", 401 delegate(string name, Json parent, Json args, ref Con context) @safe 402 { 403 graphql.defaultResolverLog.logf("name %s, parent %s, args %s", 404 name, parent, args 405 ); 406 Json ret = Json.emptyObject(); 407 ret["data"] = Json.emptyObject(); 408 if("kind" in parent 409 && parent["kind"].get!string() == "OBJECT") 410 { 411 ret["data"] = Json.emptyArray(); 412 } 413 if("interfacesNames" !in parent) { 414 return ret; 415 } 416 Json interNames = parent["interfacesNames"]; 417 if(interNames.type == Json.Type.array) { 418 if(interNames.length > 0) { 419 assert(ret["data"].type == Json.Type.array, 420 format("%s", parent.toPrettyString()) 421 ); 422 ret["data"] = Json.emptyArray(); 423 foreach(Json it; interNames.byValue()) { 424 string typeName = it.get!string(); 425 string typeCap = capitalize(typeName); 426 if(auto v = typeCap in 427 SchemaReflection!T.instance.jsonTypes) 428 { 429 ret["data"] ~= v.typeJson.clone; 430 graphql.defaultResolverLog.logf("%s %s", typeCap, v.name); 431 } 432 } 433 } 434 } 435 graphql.defaultResolverLog.logf("__Type.interfaces result %s", 436 ret 437 ); 438 return ret; 439 } 440 ); 441 442 graphql.setResolver("__Type", "possibleTypes", 443 delegate(string name, Json parent, Json args, ref Con context) @safe 444 { 445 graphql.defaultResolverLog.logf("name %s, parent %s, args %s", 446 name, parent, args 447 ); 448 Json ret = Json.emptyObject(); 449 if("possibleTypesNames" !in parent) { 450 ret["data"] = Json(null); 451 return ret; 452 } 453 Json pTypesNames = parent["possibleTypesNames"]; 454 if(pTypesNames.type == Json.Type.array) { 455 graphql.defaultResolverLog.log(); 456 ret["data"] = Json.emptyArray(); 457 foreach(Json it; pTypesNames.byValue()) { 458 string typeName = it.get!string(); 459 string typeCap = capitalize(typeName); 460 if(auto v = typeCap in 461 SchemaReflection!T.instance.jsonTypes) 462 { 463 graphql.defaultResolverLog.logf("%s %s", 464 typeCap, v.name); 465 ret["data"] ~= v.typeJson.clone; 466 } 467 } 468 } else { 469 graphql.defaultResolverLog.log(); 470 ret["data"] = Json(null); 471 } 472 return ret; 473 } 474 ); 475 }