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 graphql.schema.types; 15 import graphql.traits; 16 import graphql.uda; 17 import graphql.constants; 18 19 @safe: 20 21 template typeToTypeEnum(Type) { 22 static if(isAggregateType!Type && hasUDA!(Type, GQLDUdaData) 23 && getUDAs!(Type, GQLDUdaData)[0].typeKind != TypeKind.UNDEFINED) 24 { 25 enum udas = getUDAs!(Type, GQLDUdaData); 26 static assert(udas.length == 1); 27 enum GQLDUdaData u = udas[0]; 28 enum typeToTypeEnum = to!string(u.typeKind); 29 } else static if(is(Type == enum)) { 30 enum typeToTypeEnum = "ENUM"; 31 } else static if(is(Type == bool)) { 32 enum typeToTypeEnum = "SCALAR"; 33 } else static if(isFloatingPoint!(Type)) { 34 enum typeToTypeEnum = "SCALAR"; 35 } else static if(isIntegral!(Type)) { 36 enum typeToTypeEnum = "SCALAR"; 37 } else static if(isSomeString!Type) { 38 enum typeToTypeEnum = "SCALAR"; 39 } else static if(is(Type == void)) { 40 enum typeToTypeEnum = "SCALAR"; 41 } else static if(is(Type == union)) { 42 enum typeToTypeEnum = "UNION"; 43 } else static if(isAggregateType!Type) { 44 enum typeToTypeEnum = "OBJECT"; 45 } else { 46 static assert(false, Type.stringof ~ " not handled"); 47 } 48 } 49 50 template typeToTypeName(Type) { 51 static if(is(Type == enum)) { 52 enum typeToTypeName = Type.stringof; 53 } else static if(is(Type == bool)) { 54 enum typeToTypeName = "Bool"; 55 } else static if(isFloatingPoint!(Type)) { 56 enum typeToTypeName = "Float"; 57 } else static if(isIntegral!(Type)) { 58 enum typeToTypeName = "Int"; 59 } else static if(isSomeString!Type) { 60 enum typeToTypeName = "String"; 61 } else { 62 enum typeToTypeName = Type.stringof; 63 } 64 } 65 66 template isScalarType(Type) { 67 static if(is(Type == bool)) { 68 enum isScalarType = true; 69 } else static if(isFloatingPoint!(Type)) { 70 enum isScalarType = true; 71 } else static if(isIntegral!(Type)) { 72 enum isScalarType = true; 73 } else static if(isSomeString!Type) { 74 enum isScalarType = true; 75 } else static if(is(Type == enum)) { 76 enum isScalarType = true; 77 } else { 78 enum isScalarType = false; 79 } 80 } 81 82 template typeToFieldType(Type) { 83 static if(isArray!Type && !isSomeString!Type) { 84 enum typeToFieldType = "__listType"; 85 } else static if(is(Type : Nullable!F, F)) { 86 enum typeToFieldType = F.stringof; 87 } else { 88 enum typeToFieldType = "__nonNullType"; 89 } 90 } 91 92 Json typeFields(T)() { 93 static enum memsToIgnore = ["__ctor", "toString", "toHash", "opCmp", 94 "opEquals", "Monitor", "factory"]; 95 Json ret = Json.emptyArray(); 96 alias TplusParents = AliasSeq!(T, InheritedClasses!T); 97 static foreach(Type; TplusParents) {{ 98 static foreach(mem; __traits(allMembers, Type)) {{ 99 static if(!canFind(memsToIgnore, mem)) { 100 enum GQLDUdaData udaData = getUdaData!(Type, mem); 101 Json tmp = Json.emptyObject(); 102 tmp[Constants.name] = mem; 103 tmp[Constants.__typename] = "__Field"; // needed for interfacesForType 104 tmp[Constants.description] = udaData.description.text.empty 105 ? Json(null) 106 : Json(udaData.description.text); 107 108 tmp[Constants.isDeprecated] = 109 udaData.deprecationInfo.isDeprecated == IsDeprecated.yes 110 ? true 111 : false; 112 113 tmp[Constants.deprecationReason] = 114 udaData.deprecationInfo.isDeprecated == IsDeprecated.yes 115 ? Json(udaData.deprecationInfo.deprecationReason) 116 : Json(null); 117 118 tmp[Constants.args] = Json.emptyArray(); 119 static if(isCallable!(__traits(getMember, Type, mem))) { 120 alias RT = ReturnType!(__traits(getMember, Type, mem)); 121 alias RTS = stripArrayAndNullable!RT; 122 tmp[Constants.typenameOrig] = typeToTypeName!(RT); 123 124 // InputValue 125 alias paraNames = ParameterIdentifierTuple!( 126 __traits(getMember, Type, mem) 127 ); 128 alias paraTypes = Parameters!( 129 __traits(getMember, Type, mem) 130 ); 131 alias paraDefs = ParameterDefaults!( 132 __traits(getMember, Type, mem) 133 ); 134 static foreach(idx; 0 .. paraNames.length) {{ 135 Json iv = Json.emptyObject(); 136 iv[Constants.name] = paraNames[idx]; 137 // needed for interfacesForType 138 iv[Constants.__typename] = Constants.__InputValue; 139 iv[Constants.typenameOrig] = typeToTypeName!(paraTypes[idx]); 140 static if(!is(paraDefs[idx] == void)) { 141 iv[Constants.defaultValue] = serializeToJson(paraDefs[idx]) 142 .toString(); 143 } 144 tmp[Constants.args] ~= iv; 145 }} 146 } else { 147 tmp[Constants.typenameOrig] = typeToTypeName!( 148 typeof(__traits(getMember, Type, mem)) 149 ); 150 } 151 ret ~= tmp; 152 } 153 }} 154 }} 155 return ret; 156 } 157 158 Json inputFields(Type)() { 159 Json ret = Json.emptyArray(); 160 alias types = FieldTypeTuple!Type; 161 alias names = FieldNameTuple!Type; 162 static foreach(idx; 0 .. types.length) {{ 163 enum GQLDUdaData udaData = getUdaData!(types[idx]); 164 Json tmp = Json.emptyObject(); 165 tmp[Constants.name] = names[idx]; 166 tmp[Constants.description] = udaData.description.text.empty 167 ? Json(null) 168 : Json(udaData.description.text); 169 tmp[Constants.__typename] = Constants.__InputValue; // needed for interfacesForType 170 tmp[Constants.typenameOrig] = typeToTypeName!(types[idx]); 171 tmp[Constants.defaultValue] = serializeToJson( 172 __traits(getMember, Type.init, names[idx]) 173 ); 174 ret ~= tmp; 175 }} 176 return ret; 177 } 178 179 Json emptyType() { 180 Json ret = Json.emptyObject(); 181 ret[Constants.name] = Json(null); 182 ret[Constants.description] = Json(null); 183 ret[Constants.fields] = Json(null); 184 ret[Constants.interfacesNames] = Json(null); 185 ret[Constants.possibleTypesNames] = Json(null); 186 ret[Constants.enumValues] = Json(null); 187 ret["ofType"] = Json(null); 188 return ret; 189 } 190 191 // remove the top nullable to find out if we have a NON_NULL or not 192 Json typeToJson(Type,Schema)() { 193 static if(is(Type : Nullable!F, F)) { 194 return typeToJson1!(F,Schema,Type)(); 195 } else { 196 Json ret = emptyType(); 197 ret["kind"] = "NON_NULL"; 198 ret[Constants.__typename] = "__Type"; 199 ret["ofType"] = typeToJson1!(Type,Schema,Type)(); 200 return ret; 201 } 202 } 203 204 // remove the array is present 205 Json typeToJson1(Type,Schema,Orig)() { 206 static if(isArray!Type && !isSomeString!Type) { 207 Json ret = emptyType(); 208 ret["kind"] = "LIST"; 209 ret[Constants.__typename] = "__Type"; 210 ret["ofType"] = typeToJson2!(ElementEncodingType!Type, Schema, Orig)(); 211 return ret; 212 } else { 213 return typeToJsonImpl!(Type, Schema, Orig)(); 214 } 215 } 216 217 // remove another nullable 218 Json typeToJson2(Type,Schema,Orig)() { 219 static if(is(Type : Nullable!F, F)) { 220 return typeToJsonImpl!(F, Schema, Orig)(); 221 } else { 222 Json ret = emptyType(); 223 ret["kind"] = "NON_NULL"; 224 ret[Constants.__typename] = "__Type"; 225 ret["ofType"] = typeToJsonImpl!(Type, Schema, Orig)(); 226 return ret; 227 } 228 } 229 230 Json typeToJsonImpl(Type,Schema,Orig)() { 231 Json ret = Json.emptyObject(); 232 enum string kind = typeToTypeEnum!Type; 233 ret["kind"] = kind; 234 ret[Constants.__typename] = "__Type"; 235 ret[Constants.name] = typeToTypeName!Type; 236 237 enum GQLDUdaData udaData = getUdaData!(Orig); 238 ret[Constants.description] = udaData.description.text.empty 239 ? Json(null) 240 : Json(udaData.description.text); 241 242 ret[Constants.isDeprecated] = 243 udaData.deprecationInfo.isDeprecated == IsDeprecated.yes 244 ? true 245 : false; 246 247 ret[Constants.deprecationReason] = 248 udaData.deprecationInfo.isDeprecated == IsDeprecated.yes 249 ? Json(udaData.deprecationInfo.deprecationReason) 250 : Json(null); 251 252 // fields 253 static if((is(Type == class) || is(Type == interface) || is(Type == struct)) 254 && !is(Type : Nullable!K, K)) 255 { 256 ret[Constants.fields] = typeFields!Type(); 257 } else { 258 ret[Constants.fields] = Json(null); 259 } 260 261 // inputFields 262 static if(kind == Constants.INPUT_OBJECT) { 263 ret[Constants.inputFields] = inputFields!Type(); 264 } else { 265 ret[Constants.inputFields] = Json(null); 266 } 267 268 // needed to resolve interfaces 269 static if(is(Type == class) || is(Type == interface)) { 270 ret[Constants.interfacesNames] = Json.emptyArray(); 271 static foreach(interfaces; InheritedClasses!Type) {{ 272 ret[Constants.interfacesNames] ~= interfaces.stringof; 273 }} 274 } else { 275 ret[Constants.interfacesNames] = Json(null); 276 } 277 278 // needed to resolve possibleTypes 279 static if(is(Type == class) || is(Type == union) 280 || is(Type == interface)) 281 { 282 ret[Constants.possibleTypesNames] = Json.emptyArray(); 283 alias PT = PossibleTypes!(Type, Schema); 284 static foreach(pt; PT) { 285 ret[Constants.possibleTypesNames] ~= pt.stringof; 286 } 287 } else { 288 ret[Constants.possibleTypesNames] = Json(null); 289 } 290 291 // enumValues 292 static if(is(Type == enum)) { 293 ret[Constants.enumValues] = Json.emptyArray(); 294 static foreach(mem; EnumMembers!Type) {{ 295 Json tmp = Json.emptyObject(); 296 tmp[Constants.__TypeKind] = Constants.__EnumValue; 297 tmp[Constants.name] = Json(to!string(mem)); 298 tmp[Constants.description] = "ENUM_DESCRIPTION_TODO"; 299 tmp[Constants.isDeprecated] = false; 300 tmp[Constants.deprecationReason] = "ENUM_DEPRECATIONREASON_TODO"; 301 ret[Constants.enumValues] ~= tmp; 302 }} 303 } else { 304 ret[Constants.enumValues] = Json(null); 305 } 306 307 // needed to resolve ofType 308 static if(is(Type : Nullable!F, F)) { 309 ret[Constants.ofTypeName] = F.stringof; 310 } else static if(isArray!Type) { 311 ret[Constants.ofTypeName] = ElementEncodingType!(Type).stringof; 312 } 313 314 return ret; 315 } 316 317 Json directivesToJson(Directives)() { 318 import std.string : stripLeft; 319 Json ret = Json.emptyArray(); 320 static enum memsToIgnore = ["__ctor", "toString", "toHash", "opCmp", 321 "opEquals", "Monitor", "factory"]; 322 alias TplusParents = AliasSeq!(Directives, InheritedClasses!Directives); 323 static foreach(Type; TplusParents) {{ 324 static foreach(mem; __traits(allMembers, Type)) {{ 325 static if(!canFind(memsToIgnore, mem)) { 326 Json tmp = Json.emptyObject(); 327 enum GQLDUdaData udaData = getUdaData!(Type, mem); 328 tmp[Constants.name] = mem; 329 // needed for interfacesForType 330 tmp[Constants.__typename] = Constants.__Directive; 331 tmp[Constants.description] = udaData.description.text.empty 332 ? Json(null) 333 : Json(udaData.description.text); 334 335 tmp[Constants.isDeprecated] = 336 udaData.deprecationInfo.isDeprecated == IsDeprecated.yes 337 ? true 338 : false; 339 340 tmp[Constants.deprecationReason] = 341 udaData.deprecationInfo.isDeprecated == IsDeprecated.yes 342 ? Json(udaData.deprecationInfo.deprecationReason) 343 : Json(null); 344 345 //tmp[Constants.description] = Json(null); 346 tmp[Constants.locations] = Json.emptyArray(); 347 tmp[Constants.args] = Json.emptyArray(); 348 static if(isCallable!(__traits(getMember, Type, mem))) { 349 // InputValue 350 alias paraNames = ParameterIdentifierTuple!( 351 __traits(getMember, Type, mem) 352 ); 353 alias paraTypes = Parameters!( 354 __traits(getMember, Type, mem) 355 ); 356 alias paraDefs = ParameterDefaults!( 357 __traits(getMember, Type, mem) 358 ); 359 static foreach(idx; 0 .. paraNames.length) {{ 360 Json iv = Json.emptyObject(); 361 // TODO remove the strip left. Its in because the 362 // two default directives of GraphQL skip and include 363 // both have one parameter named "if". 364 iv[Constants.name] = stripLeft(paraNames[idx], "_"); 365 // needed for interfacesForType 366 iv[Constants.__typename] = Constants.__InputValue; 367 iv[Constants.typenameOrig] = typeToTypeName!(paraTypes[idx]); 368 static if(!is(paraDefs[idx] == void)) { 369 iv[Constants.defaultValue] = serializeToJson(paraDefs[idx]) 370 .toString(); 371 } 372 tmp[Constants.args] ~= iv; 373 }} 374 } 375 ret ~= tmp; 376 } 377 }} 378 }} 379 return ret; 380 }