1 module graphql.schema.typeconversions; 2 3 import std.array : empty; 4 import std.algorithm.searching : canFind; 5 import std.conv : to; 6 import std.stdio; 7 import std.traits; 8 import std.meta; 9 import std.typecons : Nullable, nullable; 10 import std.range : ElementEncodingType; 11 12 import vibe.data.json; 13 14 import nullablestore; 15 16 import graphql.schema.types; 17 import graphql.traits; 18 import graphql.uda; 19 import graphql.constants; 20 21 @safe: 22 23 template typeToTypeEnum(Type) { 24 static if(isAggregateType!Type && hasUDA!(Type, GQLDUdaData) 25 && getUDAs!(Type, GQLDUdaData)[0].typeKind != TypeKind.UNDEFINED) 26 { 27 enum udas = getUDAs!(Type, GQLDUdaData); 28 static assert(udas.length == 1); 29 enum GQLDUdaData u = udas[0]; 30 enum typeToTypeEnum = to!string(u.typeKind); 31 } else static if(is(Type == enum)) { 32 enum typeToTypeEnum = "ENUM"; 33 } else static if(is(Type == bool)) { 34 enum typeToTypeEnum = "SCALAR"; 35 } else static if(is(Type : GQLDCustomLeaf!F, F)) { 36 enum typeToTypeEnum = "SCALAR"; 37 } else static if(isFloatingPoint!(Type)) { 38 enum typeToTypeEnum = "SCALAR"; 39 } else static if(isIntegral!(Type)) { 40 enum typeToTypeEnum = "SCALAR"; 41 } else static if(isSomeString!Type) { 42 enum typeToTypeEnum = "SCALAR"; 43 } else static if(is(Type == void)) { 44 enum typeToTypeEnum = "SCALAR"; 45 } else static if(is(Type == union)) { 46 enum typeToTypeEnum = "UNION"; 47 } else static if(isAggregateType!Type) { 48 enum typeToTypeEnum = "OBJECT"; 49 } else { 50 static assert(false, Type.stringof ~ " not handled"); 51 } 52 } 53 54 template typeToTypeName(Type) { 55 import graphql.uda : GQLDCustomLeaf; 56 static if(is(Type == enum)) { 57 enum typeToTypeName = Type.stringof; 58 } else static if(is(Type == bool)) { 59 enum typeToTypeName = "Bool"; 60 } else static if(is(Type == GQLDCustomLeaf!Fs, Fs...)) { 61 enum typeToTypeName = Fs[0].stringof; 62 } else static if(isFloatingPoint!(Type)) { 63 enum typeToTypeName = "Float"; 64 } else static if(isIntegral!(Type)) { 65 enum typeToTypeName = "Int"; 66 } else static if(isSomeString!Type) { 67 enum typeToTypeName = "String"; 68 } else { 69 enum typeToTypeName = Type.stringof; 70 } 71 } 72 73 unittest { 74 import std.datetime : Date; 75 static assert(typeToTypeName!(GQLDCustomLeaf!Date) == "Date"); 76 } 77 78 template typeToParameterTypeName(Type) { 79 template level2(Type) { 80 static if(is(Type : Nullable!F, F)) { 81 enum level2 = typeToTypeName!F; 82 } else { 83 enum level2 = typeToTypeName!Type ~ "!"; 84 } 85 } 86 87 template level1(Type) { 88 static if(isArray!Type && !isSomeString!Type) { 89 enum level1 = "[" ~ level2!(ElementEncodingType!Type) ~ "]"; 90 } else { 91 enum level1 = typeToTypeName!Type; 92 } 93 } 94 95 template level0(Type) { 96 static if(is(Type : Nullable!F, F)) { 97 enum level0 = level1!F; 98 } else { 99 enum level0 = level1!Type ~ "!"; 100 } 101 } 102 103 enum typeToParameterTypeName = level0!Type; 104 } 105 106 unittest { 107 static assert(typeToParameterTypeName!(int) == "Int!"); 108 static assert(typeToParameterTypeName!(Nullable!int) == "Int"); 109 static assert(typeToParameterTypeName!(double) == "Float!"); 110 static assert(typeToParameterTypeName!(Nullable!double) == "Float"); 111 static assert(typeToParameterTypeName!(double[]) == "[Float!]!"); 112 static assert(typeToParameterTypeName!(Nullable!(double)[]) == "[Float]!"); 113 static assert(typeToParameterTypeName!(Nullable!(Nullable!(double)[])) == 114 "[Float]"); 115 } 116 117 template isScalarType(Type) { 118 static if(is(Type == bool)) { 119 enum isScalarType = true; 120 } else static if(is(Type == GQLDCustomLeaf!Fs, Fs...)) { 121 enum isScalarType = true; 122 } else static if(isFloatingPoint!(Type)) { 123 enum isScalarType = true; 124 } else static if(isIntegral!(Type)) { 125 enum isScalarType = true; 126 } else static if(isSomeString!Type) { 127 enum isScalarType = true; 128 } else static if(is(Type == enum)) { 129 enum isScalarType = true; 130 } else { 131 enum isScalarType = false; 132 } 133 } 134 135 template typeToFieldType(Type) { 136 static if(isArray!Type && !isSomeString!Type) { 137 enum typeToFieldType = "__listType"; 138 } else static if(is(Type : Nullable!F, F)) { 139 enum typeToFieldType = F.stringof; 140 } else static if(is(Type : NullableStore!F, F)) { 141 enum typeToFieldType = Type.TypeValue.stringof; 142 } else { 143 enum typeToFieldType = "__nonNullType"; 144 } 145 } 146 147 Json typeFields(T)() { 148 import graphql.uda; 149 static enum memsToIgnore = ["__ctor", "toString", "toHash", "opCmp", 150 "opEquals", "Monitor", "factory"]; 151 Json ret = Json.emptyArray(); 152 alias TplusParents = AliasSeq!(T, InheritedClasses!T); 153 static foreach(Type; TplusParents) {{ 154 static foreach(mem; __traits(allMembers, Type)) {{ 155 enum GQLDUdaData udaData = getUdaData!(Type, mem); 156 static if(!canFind(memsToIgnore, mem) 157 && udaData.ignore != Ignore.yes) 158 { 159 Json tmp = Json.emptyObject(); 160 tmp[Constants.name] = mem; 161 tmp[Constants.__typename] = "__Field"; // needed for interfacesForType 162 tmp[Constants.description] = udaData.description.text.empty 163 ? Json(null) 164 : Json(udaData.description.text); 165 166 tmp[Constants.isDeprecated] = 167 udaData.deprecationInfo.isDeprecated == IsDeprecated.yes 168 ? true 169 : false; 170 171 tmp[Constants.deprecationReason] = 172 udaData.deprecationInfo.isDeprecated == IsDeprecated.yes 173 ? Json(udaData.deprecationInfo.deprecationReason) 174 : Json(null); 175 176 tmp[Constants.args] = Json.emptyArray(); 177 static if(isCallable!(__traits(getMember, Type, mem))) { 178 alias RT = ReturnType!(__traits(getMember, Type, mem)); 179 alias RTS = stripArrayAndNullable!RT; 180 tmp[Constants.typenameOrig] = typeToTypeName!(RT); 181 182 // InputValue 183 alias paraNames = ParameterIdentifierTuple!( 184 __traits(getMember, Type, mem) 185 ); 186 alias paraTypes = Parameters!( 187 __traits(getMember, Type, mem) 188 ); 189 alias paraDefs = ParameterDefaults!( 190 __traits(getMember, Type, mem) 191 ); 192 static foreach(idx; 0 .. paraNames.length) {{ 193 Json iv = Json.emptyObject(); 194 iv[Constants.name] = paraNames[idx]; 195 // needed for interfacesForType 196 iv[Constants.__typename] = Constants.__InputValue; 197 iv[Constants.description] = Json(null); 198 iv[Constants.typenameOrig] = 199 typeToParameterTypeName!(paraTypes[idx]); 200 static if(!is(paraDefs[idx] == void)) { 201 iv[Constants.defaultValue] = serializeToJson(paraDefs[idx]) 202 .toString(); 203 } else { 204 iv[Constants.defaultValue] = Json(null); 205 } 206 tmp[Constants.args] ~= iv; 207 }} 208 } else { 209 tmp[Constants.typenameOrig] = typeToTypeName!( 210 typeof(__traits(getMember, Type, mem)) 211 ); 212 } 213 ret ~= tmp; 214 } 215 }} 216 }} 217 return ret; 218 } 219 220 Json inputFields(Type)() { 221 Json ret = Json.emptyArray(); 222 alias types = FieldTypeTuple!Type; 223 alias names = FieldNameTuple!Type; 224 static foreach(idx; 0 .. types.length) {{ 225 enum GQLDUdaData udaData = getUdaData!(types[idx]); 226 Json tmp = Json.emptyObject(); 227 tmp[Constants.name] = names[idx]; 228 tmp[Constants.description] = udaData.description.text.empty 229 ? Json(null) 230 : Json(udaData.description.text); 231 232 // needed for interfacesForType 233 tmp[Constants.__typename] = Constants.__InputValue; 234 235 tmp[Constants.typenameOrig] = typeToTypeName!(types[idx]); 236 tmp[Constants.defaultValue] = serializeToJson( 237 __traits(getMember, Type.init, names[idx]) 238 ); 239 ret ~= tmp; 240 }} 241 return ret; 242 } 243 244 Json emptyType() { 245 Json ret = Json.emptyObject(); 246 ret[Constants.name] = Json(null); 247 ret[Constants.description] = Json(null); 248 ret[Constants.fields] = Json(null); 249 ret[Constants.interfacesNames] = Json(null); 250 ret[Constants.possibleTypesNames] = Json(null); 251 ret[Constants.enumValues] = Json(null); 252 ret["ofType"] = Json(null); 253 return ret; 254 } 255 256 Json removeNonNullAndList(Json j) { 257 string t = j["kind"].get!string(); 258 if(t == "NON_NULL" || t == "LIST") { 259 return removeNonNullAndList(j["ofType"]); 260 } else { 261 return j; 262 } 263 } 264 265 // remove the top nullable to find out if we have a NON_NULL or not 266 Json typeToJson(Type,Schema)() { 267 static if(is(Type : Nullable!F, F)) { 268 return typeToJson1!(F,Schema,Type)(); 269 } else static if(is(Type : NullableStore!F, F)) { 270 return typeToJson1!(Type.TypeValue,Schema,Type)(); 271 } else { 272 Json ret = emptyType(); 273 ret["kind"] = "NON_NULL"; 274 ret[Constants.__typename] = "__Type"; 275 ret["ofType"] = typeToJson1!(Type,Schema,Type)(); 276 return ret; 277 } 278 } 279 280 // remove the array is present 281 Json typeToJson1(Type,Schema,Orig)() { 282 static if(isArray!Type && !isSomeString!Type && !is(Type == enum)) { 283 Json ret = emptyType(); 284 ret["kind"] = "LIST"; 285 ret[Constants.__typename] = "__Type"; 286 ret["ofType"] = typeToJson2!(ElementEncodingType!Type, Schema, Orig)(); 287 return ret; 288 } else { 289 return typeToJsonImpl!(Type, Schema, Orig)(); 290 } 291 } 292 293 // remove another nullable 294 Json typeToJson2(Type,Schema,Orig)() { 295 static if(is(Type : Nullable!F, F)) { 296 return typeToJsonImpl!(F, Schema, Orig)(); 297 } else static if(is(Type : NullableStore!F, F)) { 298 return typeToJsonImpl!(Type.TypeValue, Schema, Orig)(); 299 } else { 300 Json ret = emptyType(); 301 ret["kind"] = "NON_NULL"; 302 ret[Constants.__typename] = "__Type"; 303 ret["ofType"] = typeToJsonImpl!(Type, Schema, Orig)(); 304 return ret; 305 } 306 } 307 308 Json typeToJsonImpl(Type,Schema,Orig)() { 309 Json ret = Json.emptyObject(); 310 enum string kind = typeToTypeEnum!Type; 311 ret["kind"] = kind; 312 ret[Constants.__typename] = "__Type"; 313 ret[Constants.name] = typeToTypeName!Type; 314 315 enum GQLDUdaData udaData = getUdaData!(Orig); 316 ret[Constants.description] = udaData.description.text.empty 317 ? Json(null) 318 : Json(udaData.description.text); 319 320 ret[Constants.isDeprecated] = 321 udaData.deprecationInfo.isDeprecated == IsDeprecated.yes 322 ? true 323 : false; 324 325 ret[Constants.deprecationReason] = 326 udaData.deprecationInfo.isDeprecated == IsDeprecated.yes 327 ? Json(udaData.deprecationInfo.deprecationReason) 328 : Json(null); 329 330 // fields 331 static if((is(Type == class) || is(Type == interface) || is(Type == struct)) 332 && !is(Type : Nullable!K, K) && !is(Type : NullableStore!K, K) 333 && !is(Type : GQLDCustomLeaf!Ks, Ks...)) 334 { 335 ret[Constants.fields] = typeFields!Type(); 336 } else { 337 ret[Constants.fields] = Json(null); 338 } 339 340 // inputFields 341 static if(kind == Constants.INPUT_OBJECT) { 342 ret[Constants.inputFields] = inputFields!Type(); 343 } else { 344 ret[Constants.inputFields] = Json(null); 345 } 346 347 // needed to resolve interfaces 348 static if(is(Type == class) || is(Type == interface)) { 349 ret[Constants.interfacesNames] = Json.emptyArray(); 350 static foreach(interfaces; InheritedClasses!Type) {{ 351 ret[Constants.interfacesNames] ~= interfaces.stringof; 352 }} 353 } else { 354 ret[Constants.interfacesNames] = Json(null); 355 } 356 357 // needed to resolve possibleTypes 358 static if(is(Type == class) || is(Type == union) 359 || is(Type == interface)) 360 { 361 ret[Constants.possibleTypesNames] = Json.emptyArray(); 362 alias PT = PossibleTypes!(Type, Schema); 363 static foreach(pt; PT) { 364 ret[Constants.possibleTypesNames] ~= pt.stringof; 365 } 366 } else { 367 ret[Constants.possibleTypesNames] = Json(null); 368 } 369 370 // enumValues 371 static if(is(Type == enum)) { 372 ret[Constants.enumValues] = Json.emptyArray(); 373 static foreach(mem; EnumMembers!Type) {{ 374 Json tmp = Json.emptyObject(); 375 tmp[Constants.__TypeKind] = Constants.__EnumValue; 376 tmp[Constants.name] = Json(to!string(mem)); 377 tmp[Constants.description] = "ENUM_DESCRIPTION_TODO"; 378 tmp[Constants.isDeprecated] = false; 379 tmp[Constants.deprecationReason] = "ENUM_DEPRECATIONREASON_TODO"; 380 ret[Constants.enumValues] ~= tmp; 381 }} 382 } else { 383 ret[Constants.enumValues] = Json(null); 384 } 385 386 // needed to resolve ofType 387 static if(is(Type : Nullable!F, F) || is(Type : NullableStore!F, F) 388 || is(Type : GQLDCustomLeaf!Fs, Fs...)) 389 { 390 ret[Constants.ofTypeName] = Fs[0].stringof; 391 } else static if(isArray!Type) { 392 ret[Constants.ofTypeName] = ElementEncodingType!(Type).stringof; 393 } 394 395 return ret; 396 } 397 398 Json directivesToJson(Directives)() { 399 import std.string : stripLeft; 400 Json ret = Json.emptyArray(); 401 static enum memsToIgnore = ["__ctor", "toString", "toHash", "opCmp", 402 "opEquals", "Monitor", "factory"]; 403 alias TplusParents = AliasSeq!(Directives, InheritedClasses!Directives); 404 static foreach(Type; TplusParents) {{ 405 static foreach(mem; __traits(allMembers, Type)) {{ 406 static if(!canFind(memsToIgnore, mem)) { 407 Json tmp = Json.emptyObject(); 408 enum GQLDUdaData udaData = getUdaData!(Type, mem); 409 tmp[Constants.name] = mem; 410 // needed for interfacesForType 411 tmp[Constants.__typename] = Constants.__Directive; 412 tmp[Constants.description] = udaData.description.text.empty 413 ? Json(null) 414 : Json(udaData.description.text); 415 416 tmp[Constants.isDeprecated] = 417 udaData.deprecationInfo.isDeprecated == IsDeprecated.yes 418 ? true 419 : false; 420 421 tmp[Constants.deprecationReason] = 422 udaData.deprecationInfo.isDeprecated == IsDeprecated.yes 423 ? Json(udaData.deprecationInfo.deprecationReason) 424 : Json(null); 425 426 //tmp[Constants.description] = Json(null); 427 tmp[Constants.locations] = Json.emptyArray(); 428 tmp[Constants.args] = Json.emptyArray(); 429 static if(isCallable!(__traits(getMember, Type, mem))) { 430 // InputValue 431 alias paraNames = ParameterIdentifierTuple!( 432 __traits(getMember, Type, mem) 433 ); 434 alias paraTypes = Parameters!( 435 __traits(getMember, Type, mem) 436 ); 437 alias paraDefs = ParameterDefaults!( 438 __traits(getMember, Type, mem) 439 ); 440 static foreach(idx; 0 .. paraNames.length) {{ 441 Json iv = Json.emptyObject(); 442 // TODO remove the strip left. Its in because the 443 // two default directives of GraphQL skip and include 444 // both have one parameter named "if". 445 iv[Constants.name] = stripLeft(paraNames[idx], "_"); 446 iv[Constants.description] = Json(null); 447 // needed for interfacesForType 448 iv[Constants.__typename] = Constants.__InputValue; 449 iv[Constants.typenameOrig] = typeToTypeName!(paraTypes[idx]); 450 static if(!is(paraDefs[idx] == void)) { 451 iv[Constants.defaultValue] = serializeToJson(paraDefs[idx]) 452 .toString(); 453 } else { 454 iv[Constants.defaultValue] = Json(null); 455 } 456 tmp[Constants.args] ~= iv; 457 }} 458 } 459 ret ~= tmp; 460 } 461 }} 462 }} 463 return ret; 464 } 465 466 Json getField(Json j, string name) { 467 import graphql.constants; 468 469 if(j.type != Json.Type.object || Constants.fields !in j 470 || j[Constants.fields].type != Json.Type.array) 471 { 472 return Json.init; 473 } 474 475 foreach(it; j[Constants.fields].byValue) { 476 string itName = it[Constants.name].get!string(); 477 if(itName == name) { 478 return it; 479 } 480 } 481 return Json.init; 482 } 483 484 Json getIntrospectionField(string name) { 485 import std.format : format; 486 Json ret = Json.emptyObject(); 487 ret[Constants.typenameOrig] = name == Constants.__typename 488 ? "String" 489 : name == Constants.__schema 490 ? "__Schema" 491 : name == Constants.__type 492 ? "__Type" 493 : format("Not known introspection name '%s'", name); 494 return ret; 495 }