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 }