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).expect("Schema had parse errors");
235 let schema_definition =
236 ParserSchemaDefinition::try_from(&definition_document).expect("Schema had errors");
237 let executable_document = ParserExecutableDocument::parse(query)
238 .unwrap_or_else(|_| panic!("Document had parse errors"));
239 let cache = Cache::new(&executable_document, &schema_definition);
240 let variables = variables.as_object().expect("Variables must be an object");
241 Orchestrator::<_, _, JsonMap<String, JsonValue>, InputSize<_, _>>::analyze(
242 &executable_document,
243 &schema_definition,
244 None,
245 variables,
246 &cache,
247 1,
248 )
249 .unwrap()
250 }
251
252 #[test]
253 fn simple_size() {
254 let result =
255 analyze_input_size(r#"query { simple(x: ["x", "y"])} "#, serde_json::json!({}));
256 let result = result.first().unwrap();
257 assert_eq!(result.size, 2);
258 assert_eq!(result.name, "x");
259 }
260
261 #[test]
262 fn simple_directive_size() {
263 let result = analyze_input_size(
264 r#"query { simple(x: []) @test(y: ["x", "y"])} "#,
265 serde_json::json!({}),
266 );
267 let result = result.first().unwrap();
268 assert_eq!(result.size, 2);
269 assert_eq!(result.name, "y");
270 }
271
272 #[test]
273 fn simple_size_variable() {
274 let result = analyze_input_size(
275 r#"query ($x: [String]) { simple(x: $x)} "#,
276 serde_json::json!({ "x": ["x", "y"] }),
277 );
278 let result = result.first().unwrap();
279 assert_eq!(result.size, 2);
280 assert_eq!(result.name, "x");
281 }
282
283 #[test]
284 fn simple_size_variable_default_value() {
285 let result = analyze_input_size(
286 r#"query ($x: [String] = ["x", "y"]) { simple(x: $x)} "#,
287 serde_json::json!({}),
288 );
289 let result = result.first().unwrap();
290 assert_eq!(result.size, 2);
291 assert_eq!(result.name, "x");
292 }
293
294 #[test]
295 fn object_size() {
296 let result = analyze_input_size(
297 r#"query { object(x: { property: ["x", "y"] })} "#,
298 serde_json::json!({}),
299 );
300 let result = result.first().unwrap();
301 assert_eq!(result.size, 2);
302 assert_eq!(result.name, "x.property");
303 }
304
305 #[test]
306 fn object_size_variable() {
307 let result = analyze_input_size(
308 r#"query($x: ObjectList) { object(x: $x)} "#,
309 serde_json::json!({ "x": { "property": ["x", "y"] } }),
310 );
311 let result = result.first().unwrap();
312 assert_eq!(result.size, 2);
313 assert_eq!(result.name, "x.property");
314 }
315
316 #[test]
317 fn list_object_size() {
318 let result = analyze_input_size(
319 r#"query { list_object(x: [{ property: ["x", "y"] }, { property: ["x", "y"] }])} "#,
320 serde_json::json!({}),
321 );
322 assert_eq!(result.len(), 1);
323 let first = result.first().unwrap();
324 assert_eq!(first.size, 2);
325 assert_eq!(first.name, "x");
326 }
327
328 #[test]
329 fn list_object_size_variable() {
330 let result = analyze_input_size(
331 r#"query($x: [ObjectList]) { list_object(x: $x)} "#,
332 serde_json::json!({ "x": [{ "property": ["x", "y"] }, { "property": ["x", "y"] }] }),
333 );
334 let result = result.first().unwrap();
335 assert_eq!(result.size, 2);
336 assert_eq!(result.name, "x");
337 }
338
339 #[test]
340 fn list_nested_object_size() {
341 let result = analyze_input_size(
342 r#"query { list_object(x: [{ property: ["x", "y"] }])} "#,
343 serde_json::json!({}),
344 );
345 assert_eq!(result.len(), 1);
346 let first = result.first().unwrap();
347 assert_eq!(first.size, 2);
348 assert_eq!(first.name, "x.0.property");
349 }
350
351 #[test]
352 fn list_nested_object_size_variable() {
353 let result = analyze_input_size(
354 r#"query($x: [ObjectList]) { list_object(x: $x)} "#,
355 serde_json::json!({ "x": [{ "property": ["x", "y"] }] }),
356 );
357 let result = result.first().unwrap();
358 assert_eq!(result.size, 2);
359 assert_eq!(result.name, "x.0.property");
360 }
361}