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 }