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 import graphql.validation.exception : VariablesUseException; 132 final switch(obj.ruleSelection) { 133 case ValueOrVariableEnum.Val: 134 obj.val.visit(this); 135 break; 136 case ValueOrVariableEnum.Var: 137 string varName = obj.var.name.value; 138 enforce!VariablesUseException(varName in this.variables, 139 format("Variable with name '%s' required available '%s'", 140 varName, this.variables) 141 ); 142 //writefln("%s %s", varName, this.variables); 143 this.assign(this.variables[varName]); 144 break; 145 } 146 } 147 148 override void accept(const(ObjectValues) obj) { 149 enter(obj); 150 final switch(obj.ruleSelection) { 151 case ObjectValuesEnum.V: 152 this.curNames ~= obj.name.value; 153 obj.val.visit(this); 154 this.curNames.popBack(); 155 break; 156 case ObjectValuesEnum.Vsc: 157 this.curNames ~= obj.name.value; 158 obj.val.visit(this); 159 this.curNames.popBack(); 160 obj.follow.visit(this); 161 break; 162 case ObjectValuesEnum.Vs: 163 this.curNames ~= obj.name.value; 164 obj.val.visit(this); 165 this.curNames.popBack(); 166 obj.follow.visit(this); 167 break; 168 } 169 exit(obj); 170 } 171 172 override void enter(const(Value) val) { 173 final switch(val.ruleSelection) { 174 case ValueEnum.STR: 175 this.assign(Json(val.tok.value)); 176 break; 177 case ValueEnum.INT: 178 this.assign(Json(to!long(val.tok.value))); 179 break; 180 case ValueEnum.FLOAT: 181 this.assign(Json(to!double(val.tok.value))); 182 break; 183 case ValueEnum.T: 184 this.assign(Json(true)); 185 break; 186 case ValueEnum.F: 187 this.assign(Json(false)); 188 break; 189 case ValueEnum.ARR: 190 this.assign(Json.emptyArray()); 191 break; 192 case ValueEnum.O: 193 this.assign(Json.emptyObject()); 194 break; 195 case ValueEnum.E: 196 this.assign(Json(val.tok.value)); 197 break; 198 case ValueEnum.N: 199 this.assign(Json(null)); 200 break; 201 } 202 } 203 } 204 205 import graphql.helper : lexAndParse; 206 207 unittest { 208 string s = ` 209 { 210 starships(overSize: 10) { 211 name 212 } 213 } 214 `; 215 216 const auto d = lexAndParse(s); 217 }