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 9 import vibe.data.json; 10 11 import graphql.visitor; 12 import graphql.ast; 13 import graphql.builder : FieldRangeItem; 14 15 @safe: 16 17 Json getArguments(Selections sels, Json variables) { 18 //writefln("%s", variables); 19 auto ae = new ArgumentExtractor(variables); 20 ae.accept(cast(const(Selections))sels); 21 return ae.arguments; 22 } 23 24 Json getArguments(InlineFragment ilf, Json variables) { 25 auto ae = new ArgumentExtractor(variables); 26 ae.accept(cast(const(InlineFragment))ilf); 27 return ae.arguments; 28 } 29 30 Json getArguments(FragmentSpread fs, Json variables) { 31 auto ae = new ArgumentExtractor(variables); 32 ae.accept(cast(const(FragmentSpread))fs); 33 return ae.arguments; 34 } 35 36 Json getArguments(const(Directive) dir, Json variables) { 37 auto ae = new ArgumentExtractor(variables); 38 ae.accept(dir); 39 return ae.arguments; 40 } 41 42 Json getArguments(FieldRangeItem item, Json variables) { 43 auto ae = new ArgumentExtractor(variables); 44 ae.accept(cast(const(Field))item.f); 45 return ae.arguments; 46 } 47 48 Json getArguments(Field field, Json variables) { 49 auto ae = new ArgumentExtractor(variables); 50 ae.accept(cast(const(Field))field); 51 return ae.arguments; 52 } 53 54 class ArgumentExtractor : ConstVisitor { 55 alias enter = ConstVisitor.enter; 56 alias exit = ConstVisitor.exit; 57 alias accept = ConstVisitor.accept; 58 59 Json arguments; 60 Json variables; 61 62 string[] curNames; 63 64 this(Json variables) { 65 this.variables = variables; 66 this.arguments = Json.emptyObject(); 67 } 68 69 void assign(Json toAssign) @trusted { 70 Json* arg = &this.arguments; 71 //logf("%(%s.%) %s %s", this.curNames, this.arguments, toAssign); 72 assert(!this.curNames.empty); 73 foreach(idx; 0 .. this.curNames.length - 1) { 74 enforce(arg !is null); 75 arg = &((*arg)[this.curNames[idx]]); 76 } 77 78 enforce(arg !is null); 79 80 if(this.curNames.back in (*arg) 81 && ((*arg)[this.curNames.back]).type == Json.Type.array) 82 { 83 ((*arg)[this.curNames.back]) ~= toAssign; 84 } else if((*arg).type == Json.Type.object) { 85 ((*arg)[this.curNames.back]) = toAssign; 86 } else { 87 ((*arg)[this.curNames.back]) = toAssign; 88 } 89 //logf("%s", this.arguments); 90 } 91 92 override void accept(const(Field) obj) { 93 final switch(obj.ruleSelection) { 94 case FieldEnum.FADS: 95 obj.args.visit(this); 96 obj.dirs.visit(this); 97 break; 98 case FieldEnum.FAS: 99 obj.args.visit(this); 100 break; 101 case FieldEnum.FAD: 102 obj.args.visit(this); 103 obj.dirs.visit(this); 104 break; 105 case FieldEnum.FDS: 106 obj.dirs.visit(this); 107 break; 108 case FieldEnum.FS: 109 break; 110 case FieldEnum.FD: 111 obj.dirs.visit(this); 112 break; 113 case FieldEnum.FA: 114 obj.args.visit(this); 115 break; 116 case FieldEnum.F: 117 break; 118 } 119 } 120 121 override void enter(const(Argument) arg) { 122 this.curNames ~= arg.name.value; 123 } 124 125 override void exit(const(Argument) arg) { 126 this.curNames.popBack(); 127 } 128 129 override void accept(const(ValueOrVariable) obj) { 130 import graphql.validation.exception : VariablesUseException; 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!VariablesUseException(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 case ValueEnum.N: 198 this.assign(Json(null)); 199 break; 200 } 201 } 202 } 203 204 import graphql.helper : lexAndParse; 205 206 unittest { 207 string s = ` 208 { 209 starships(overSize: 10) { 210 name 211 } 212 } 213 `; 214 215 const auto d = lexAndParse(s); 216 }