1 module graphql.directives; 2 3 import std.format : format; 4 5 import vibe.data.json; 6 7 import graphql.ast; 8 import graphql.helper; 9 import graphql.astselector; 10 11 @safe: 12 13 private enum Include { 14 undefined = 0, 15 yes = 1, 16 no = 4 17 } 18 19 private enum Skip { 20 undefined = 0, 21 yes = 1, 22 no = 4 23 } 24 25 T add(T)(T a, T b) { 26 immutable int ai = a; 27 immutable int bi = b; 28 int r = ai + bi; 29 if(r == 0) { 30 return cast(T)r; 31 } else if(r == 1 || r == 2) { 32 return T.yes; 33 } else if(r == 4 || r == 8) { 34 return T.no; 35 } else { 36 throw new Exception(format("join conflict between '%s' and '%s'", 37 a, b)); 38 } 39 } 40 41 struct SkipInclude { 42 Include include; 43 Skip skip; 44 45 SkipInclude join(SkipInclude other) { 46 SkipInclude ret = this; 47 ret.include = add(ret.include, other.include); 48 ret.skip = add(ret.skip, other.skip); 49 return ret; 50 } 51 } 52 53 bool continueAfterDirectives(const(Directives) dirs, Json vars) { 54 import graphql.validation.exception; 55 56 SkipInclude si = extractSkipInclude(dirs, vars); 57 if(si.include == Include.undefined && si.skip == Skip.undefined) { 58 return true; 59 } else if(si.include == Include.no && si.skip == Skip.undefined) { 60 return false; 61 } else if(si.include == Include.yes 62 && (si.skip == Skip.undefined || si.skip == Skip.no)) 63 { 64 return true; 65 } else if(si.include == Include.undefined && si.skip == Skip.no) { 66 return true; 67 } else if(si.include == Include.undefined && si.skip == Skip.yes) { 68 return false; 69 } 70 throw new ContradictingDirectives(format( 71 "include %s and skip %s contridict each other", si.include, 72 si.skip), __FILE__, __LINE__); 73 } 74 75 SkipInclude extractSkipInclude(const(Directives) dirs, Json vars) { 76 import graphql.argumentextractor; 77 78 SkipInclude ret; 79 if(dirs is null) { 80 return ret; 81 } 82 Json args = getArguments(dirs.dir, vars); 83 immutable bool if_ = args.extract!bool("if"); 84 ret.include = dirs.dir.name.value == "include" 85 ? if_ 86 ? Include.yes 87 : Include.no 88 : Include.undefined; 89 ret.skip = dirs.dir.name.value == "skip" 90 ? if_ 91 ? Skip.yes 92 : Skip.no 93 : Skip.undefined; 94 95 return ret.join(extractSkipInclude(dirs.follow, vars)); 96 } 97 98 unittest { 99 string q = ` 100 query a($s: boolean) { 101 starships(overSize: 10) { 102 name @include(if: $s) 103 crew @skip(if: $s) { 104 id 105 } 106 } 107 }`; 108 109 Json vars = parseJsonString(`{ "s": false }`); 110 111 const(Document) doc = lexAndParse(q); 112 assert(doc !is null); 113 { 114 auto name = astSelect!Field(doc, "a.starships.name"); 115 assert(name !is null); 116 117 SkipInclude si = extractSkipInclude(name.dirs, vars); 118 assert(si.include == Include.no, format("%s", si.include)); 119 assert(si.skip == Skip.undefined, format("%s", si.skip)); 120 assert(!continueAfterDirectives(name.dirs, vars)); 121 } 122 { 123 auto crew = astSelect!Field(doc, "a.starships.crew"); 124 assert(crew !is null); 125 126 immutable SkipInclude si = extractSkipInclude(crew.dirs, vars); 127 assert(si.include == Include.undefined); 128 assert(si.skip == Skip.no); 129 assert(continueAfterDirectives(crew.dirs, vars)); 130 } 131 }