1 module graphql.argumentextractor; 2 3 import std.array : back, empty, popBack; 4 import std.conv : to; 5 import std.format : format; 6 import std.exception : enforce; 7 import std.stdio : writefln; 8 import std.experimental.logger; 9 10 import vibe.data.json; 11 12 import graphql.visitor; 13 import graphql.ast; 14 import graphql.builder : FieldRangeItem; 15 16 @safe: 17 18 Json getArguments(Selections sels, Json variables) { 19 //writefln("%s", variables); 20 auto ae = new ArgumentExtractor(variables); 21 ae.accept(cast(const(Selections))sels); 22 return ae.arguments; 23 } 24 25 Json getArguments(InlineFragment ilf, Json variables) { 26 auto ae = new ArgumentExtractor(variables); 27 ae.accept(cast(const(InlineFragment))ilf); 28 return ae.arguments; 29 } 30 31 Json getArguments(FragmentSpread fs, Json variables) { 32 auto ae = new ArgumentExtractor(variables); 33 ae.accept(cast(const(FragmentSpread))fs); 34 return ae.arguments; 35 } 36 37 Json getArguments(const(Directive) dir, Json variables) { 38 auto ae = new ArgumentExtractor(variables); 39 ae.accept(dir); 40 return ae.arguments; 41 } 42 43 Json getArguments(FieldRangeItem item, Json variables) { 44 auto ae = new ArgumentExtractor(variables); 45 ae.accept(cast(const(Field))item.f); 46 return ae.arguments; 47 } 48 49 Json getArguments(Field field, Json variables) { 50 auto ae = new ArgumentExtractor(variables); 51 ae.accept(cast(const(Field))field); 52 return ae.arguments; 53 } 54 55 class ArgumentExtractor : ConstVisitor { 56 alias enter = ConstVisitor.enter; 57 alias exit = ConstVisitor.exit; 58 alias accept = ConstVisitor.accept; 59 60 Json arguments; 61 Json variables; 62 63 string[] curNames; 64 65 this(Json variables) { 66 this.variables = variables; 67 this.arguments = Json.emptyObject(); 68 } 69 70 void assign(Json toAssign) @trusted { 71 Json* arg = &this.arguments; 72 //logf("%(%s.%) %s %s", this.curNames, this.arguments, toAssign); 73 assert(!this.curNames.empty); 74 foreach(idx; 0 .. this.curNames.length - 1) { 75 enforce(arg !is null); 76 arg = &((*arg)[this.curNames[idx]]); 77 } 78 79 enforce(arg !is null); 80 81 if(this.curNames.back in (*arg) 82 && ((*arg)[this.curNames.back]).type == Json.Type.array) 83 { 84 ((*arg)[this.curNames.back]) ~= toAssign; 85 } else if((*arg).type == Json.Type.object) { 86 ((*arg)[this.curNames.back]) = toAssign; 87 } else { 88 ((*arg)[this.curNames.back]) = toAssign; 89 } 90 //logf("%s", this.arguments); 91 } 92 93 override void accept(const(Field) obj) { 94 final switch(obj.ruleSelection) { 95 case FieldEnum.FADS: 96 obj.args.visit(this); 97 obj.dirs.visit(this); 98 break; 99 case FieldEnum.FAS: 100 obj.args.visit(this); 101 break; 102 case FieldEnum.FAD: 103 obj.args.visit(this); 104 obj.dirs.visit(this); 105 break; 106 case FieldEnum.FDS: 107 obj.dirs.visit(this); 108 break; 109 case FieldEnum.FS: 110 break; 111 case FieldEnum.FD: 112 obj.dirs.visit(this); 113 break; 114 case FieldEnum.FA: 115 obj.args.visit(this); 116 break; 117 case FieldEnum.F: 118 break; 119 } 120 } 121 122 override void enter(const(Argument) arg) { 123 this.curNames ~= arg.name.value; 124 } 125 126 override void exit(const(Argument) arg) { 127 this.curNames.popBack(); 128 } 129 130 override void accept(const(ValueOrVariable) obj) { 131 final switch(obj.ruleSelection) { 132 case ValueOrVariableEnum.Val: 133 obj.val.visit(this); 134 break; 135 case ValueOrVariableEnum.Var: 136 string varName = obj.var.name.value; 137 enforce(varName in this.variables, 138 format("Variable with name '%s' required available '%s'", 139 varName, this.variables) 140 ); 141 //writefln("%s %s", varName, this.variables); 142 this.assign(this.variables[varName]); 143 break; 144 } 145 } 146 147 override void accept(const(ObjectValues) obj) { 148 enter(obj); 149 final switch(obj.ruleSelection) { 150 case ObjectValuesEnum.V: 151 this.curNames ~= obj.name.value; 152 obj.val.visit(this); 153 this.curNames.popBack(); 154 break; 155 case ObjectValuesEnum.Vsc: 156 this.curNames ~= obj.name.value; 157 obj.val.visit(this); 158 this.curNames.popBack(); 159 obj.follow.visit(this); 160 break; 161 case ObjectValuesEnum.Vs: 162 this.curNames ~= obj.name.value; 163 obj.val.visit(this); 164 this.curNames.popBack(); 165 obj.follow.visit(this); 166 break; 167 } 168 exit(obj); 169 } 170 171 override void enter(const(Value) val) { 172 final switch(val.ruleSelection) { 173 case ValueEnum.STR: 174 this.assign(Json(val.tok.value)); 175 break; 176 case ValueEnum.INT: 177 this.assign(Json(to!long(val.tok.value))); 178 break; 179 case ValueEnum.FLOAT: 180 this.assign(Json(to!double(val.tok.value))); 181 break; 182 case ValueEnum.T: 183 this.assign(Json(true)); 184 break; 185 case ValueEnum.F: 186 this.assign(Json(false)); 187 break; 188 case ValueEnum.ARR: 189 this.assign(Json.emptyArray()); 190 break; 191 case ValueEnum.O: 192 this.assign(Json.emptyObject()); 193 break; 194 case ValueEnum.E: 195 this.assign(Json(val.tok.value)); 196 break; 197 } 198 } 199 } 200 201 import graphql.helper : lexAndParse; 202 203 unittest { 204 string s = ` 205 { 206 starships(overSize: 10) { 207 name 208 } 209 } 210 `; 211 212 auto d = lexAndParse(s); 213 }