1use crate::executable::{
2 operation::{Analyzer, VariableValues, Visitor},
3 Cache,
4};
5use bluejay_core::{
6 definition::SchemaDefinition,
7 executable::{ExecutableDocument, OperationDefinition, VariableDefinition},
8 Argument, AsIter, ObjectValue, Value, ValueReference, Variable,
9};
10
11#[derive(Clone)]
12pub struct Offender {
15 pub size: usize,
16 pub name: String,
17}
18
19#[derive(Clone)]
20pub struct InputSize<'a, E: ExecutableDocument, VV: VariableValues> {
25 offenders: Vec<Offender>,
26 max_length: usize,
27 variable_values: &'a VV,
28 variable_definitions: Option<&'a E::VariableDefinitions>,
29}
30
31impl<'a, E: ExecutableDocument, S: SchemaDefinition, VV: VariableValues> Visitor<'a, E, S, VV>
32 for InputSize<'a, E, VV>
33{
34 type ExtraInfo = usize;
35
36 fn new(
37 op: &'a E::OperationDefinition,
38 _s: &'a S,
39 variables: &'a VV,
40 _: &'a Cache<'a, E, S>,
41 max_length: Self::ExtraInfo,
42 ) -> Self {
43 Self {
44 max_length,
45 offenders: vec![],
46 variable_values: variables,
47 variable_definitions: op.as_ref().variable_definitions(),
48 }
49 }
50
51 fn visit_variable_argument(
52 &mut self,
53 argument: &'a <E as ExecutableDocument>::Argument<false>,
54 _input_value_definition: &'a S::InputValueDefinition,
55 ) {
56 find_input_size_offenders_arguments::<E, VV, false>(
57 self.max_length,
58 &mut self.offenders,
59 self.variable_values,
60 self.variable_definitions,
61 argument.name().to_string(),
62 argument.value(),
63 );
64 }
65}
66
67fn find_input_size_offenders_arguments<
73 E: ExecutableDocument,
74 VV: VariableValues,
75 const CONST: bool,
76>(
77 max_length: usize,
78 offenders: &mut Vec<Offender>,
79 variable_values: &VV,
80 variable_definitions: Option<&E::VariableDefinitions>,
81 argument_name: String,
82 argument_value: &<E as bluejay_core::executable::ExecutableDocument>::Value<CONST>,
83) {
84 match argument_value.as_ref() {
85 ValueReference::List(list) => {
86 let list_length = list.len();
87 if list_length > max_length {
88 offenders.push(Offender {
89 size: list_length,
90 name: argument_name,
91 })
92 } else {
93 list.iter().enumerate().for_each(|(index, item)| {
94 find_input_size_offenders_arguments::<E, VV, CONST>(
95 max_length,
96 offenders,
97 variable_values,
98 variable_definitions,
99 format!("{argument_name}.{index}"),
100 item,
101 );
102 })
103 }
104 }
105 ValueReference::Object(obj) => {
106 obj.iter().for_each(|(key, value)| {
107 find_input_size_offenders_arguments::<E, VV, CONST>(
108 max_length,
109 offenders,
110 variable_values,
111 variable_definitions,
112 format!("{}.{}", argument_name, key.as_ref()),
113 value,
114 );
115 });
116 }
117 ValueReference::Variable(var) => {
118 let name = var.name();
119 let variable = variable_values.get(name);
120 if let Some(value) = variable {
121 find_input_size_offenders_variables::<E, VV>(
122 max_length,
123 offenders,
124 argument_name,
125 value,
126 );
127 } else {
128 let variable_definition = variable_definitions.map(|variable_definitions| {
129 variable_definitions
130 .iter()
131 .find(|def| def.variable() == argument_name)
132 });
133 if let Some(Some(var_def)) = variable_definition {
134 let default_value = var_def.default_value();
135 if let Some(default_value) = default_value {
136 find_input_size_offenders_arguments::<E, VV, true>(
137 max_length,
138 offenders,
139 variable_values,
140 variable_definitions,
141 argument_name,
142 default_value,
143 );
144 }
145 }
146 }
147 }
148 _ => {}
149 };
150}
151
152fn find_input_size_offenders_variables<E: ExecutableDocument, VV: VariableValues>(
155 max_length: usize,
156 offenders: &mut Vec<Offender>,
157 argument_name: String,
158 argument_value: &VV::Value,
159) {
160 match argument_value.as_ref() {
161 ValueReference::List(list) => {
162 let list_length = list.len();
163 if list_length > max_length {
164 offenders.push(Offender {
165 size: list_length,
166 name: argument_name,
167 })
168 } else {
169 list.iter().enumerate().for_each(|(index, item)| {
170 find_input_size_offenders_variables::<E, VV>(
171 max_length,
172 offenders,
173 format!("{argument_name}.{index}"),
174 item,
175 );
176 })
177 }
178 }
179 ValueReference::Object(obj) => {
180 obj.iter().for_each(|(key, value)| {
181 find_input_size_offenders_variables::<E, VV>(
182 max_length,
183 offenders,
184 format!("{}.{}", argument_name, key.as_ref()),
185 value,
186 );
187 });
188 }
189 _ => {}
190 };
191}
192
193impl<'a, E: ExecutableDocument, S: SchemaDefinition, VV: VariableValues> Analyzer<'a, E, S, VV>
194 for InputSize<'a, E, VV>
195{
196 type Output = Vec<Offender>;
197
198 fn into_output(self) -> Self::Output {
199 self.offenders
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::{InputSize, Offender};
206 use crate::executable::{operation::Orchestrator, Cache};
207 use bluejay_parser::ast::{
208 definition::{
209 DefaultContext, DefinitionDocument, SchemaDefinition as ParserSchemaDefinition,
210 },
211 executable::ExecutableDocument as ParserExecutableDocument,
212 Parse,
213 };
214 use serde_json::{Map as JsonMap, Value as JsonValue};
215
216 const TEST_SCHEMA: &str = r#"
217 directive @test(y: [String]) on FIELD_DEFINITION
218
219 input ObjectList {
220 property: [String]
221 }
222 type Query {
223 simple(x: [String]): String!
224 object(x: ObjectList): String!
225 list_object(x: [ObjectList]): String!
226 }
227 schema {
228 query: Query
229 }
230 "#;
231
232 fn analyze_input_size(query: &str, variables: serde_json::Value) -> Vec<Offender> {
233 let definition_document: DefinitionDocument<'_, DefaultContext> =
234 DefinitionDocument::parse(TEST_SCHEMA)
235 .result
236 .expect("Schema had parse errors");
237 let schema_definition =
238 ParserSchemaDefinition::try_from(&definition_document).expect("Schema had errors");
239 let executable_document = ParserExecutableDocument::parse(query)
240 .result
241 .unwrap_or_else(|_| panic!("Document had parse errors"));
242 let cache = Cache::new(&executable_document, &schema_definition);
243 let variables = variables.as_object().expect("Variables must be an object");
244 Orchestrator::<_, _, JsonMap<String, JsonValue>, InputSize<_, _>>::analyze(
245 &executable_document,
246 &schema_definition,
247 None,
248 variables,
249 &cache,
250 1,
251 )
252 .unwrap()
253 }
254
255 #[test]
256 fn simple_size() {
257 let result =
258 analyze_input_size(r#"query { simple(x: ["x", "y"])} "#, serde_json::json!({}));
259 let result = result.first().unwrap();
260 assert_eq!(result.size, 2);
261 assert_eq!(result.name, "x");
262 }
263
264 #[test]
265 fn simple_directive_size() {
266 let result = analyze_input_size(
267 r#"query { simple(x: []) @test(y: ["x", "y"])} "#,
268 serde_json::json!({}),
269 );
270 let result = result.first().unwrap();
271 assert_eq!(result.size, 2);
272 assert_eq!(result.name, "y");
273 }
274
275 #[test]
276 fn simple_size_variable() {
277 let result = analyze_input_size(
278 r#"query ($x: [String]) { simple(x: $x)} "#,
279 serde_json::json!({ "x": ["x", "y"] }),
280 );
281 let result = result.first().unwrap();
282 assert_eq!(result.size, 2);
283 assert_eq!(result.name, "x");
284 }
285
286 #[test]
287 fn simple_size_variable_default_value() {
288 let result = analyze_input_size(
289 r#"query ($x: [String] = ["x", "y"]) { simple(x: $x)} "#,
290 serde_json::json!({}),
291 );
292 let result = result.first().unwrap();
293 assert_eq!(result.size, 2);
294 assert_eq!(result.name, "x");
295 }
296
297 #[test]
298 fn object_size() {
299 let result = analyze_input_size(
300 r#"query { object(x: { property: ["x", "y"] })} "#,
301 serde_json::json!({}),
302 );
303 let result = result.first().unwrap();
304 assert_eq!(result.size, 2);
305 assert_eq!(result.name, "x.property");
306 }
307
308 #[test]
309 fn object_size_variable() {
310 let result = analyze_input_size(
311 r#"query($x: ObjectList) { object(x: $x)} "#,
312 serde_json::json!({ "x": { "property": ["x", "y"] } }),
313 );
314 let result = result.first().unwrap();
315 assert_eq!(result.size, 2);
316 assert_eq!(result.name, "x.property");
317 }
318
319 #[test]
320 fn list_object_size() {
321 let result = analyze_input_size(
322 r#"query { list_object(x: [{ property: ["x", "y"] }, { property: ["x", "y"] }])} "#,
323 serde_json::json!({}),
324 );
325 assert_eq!(result.len(), 1);
326 let first = result.first().unwrap();
327 assert_eq!(first.size, 2);
328 assert_eq!(first.name, "x");
329 }
330
331 #[test]
332 fn list_object_size_variable() {
333 let result = analyze_input_size(
334 r#"query($x: [ObjectList]) { list_object(x: $x)} "#,
335 serde_json::json!({ "x": [{ "property": ["x", "y"] }, { "property": ["x", "y"] }] }),
336 );
337 let result = result.first().unwrap();
338 assert_eq!(result.size, 2);
339 assert_eq!(result.name, "x");
340 }
341
342 #[test]
343 fn list_nested_object_size() {
344 let result = analyze_input_size(
345 r#"query { list_object(x: [{ property: ["x", "y"] }])} "#,
346 serde_json::json!({}),
347 );
348 assert_eq!(result.len(), 1);
349 let first = result.first().unwrap();
350 assert_eq!(first.size, 2);
351 assert_eq!(first.name, "x.0.property");
352 }
353
354 #[test]
355 fn list_nested_object_size_variable() {
356 let result = analyze_input_size(
357 r#"query($x: [ObjectList]) { list_object(x: $x)} "#,
358 serde_json::json!({ "x": [{ "property": ["x", "y"] }] }),
359 );
360 let result = result.first().unwrap();
361 assert_eq!(result.size, 2);
362 assert_eq!(result.name, "x.0.property");
363 }
364}