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 }