bluejay_validator/executable/operation/analyzers/
variable_values_are_valid.rs

1use std::collections::HashMap;
2use std::marker::PhantomData;
3
4use crate::{
5    executable::{
6        operation::{Analyzer, VariableValues, Visitor},
7        Cache,
8    },
9    value::input_coercion::{CoerceInput, Error as CoerceInputError},
10};
11use bluejay_core::definition::SchemaDefinition;
12use bluejay_core::executable::{ExecutableDocument, VariableDefinition};
13
14pub struct VariableValuesAreValid<
15    'a,
16    E: ExecutableDocument,
17    S: SchemaDefinition,
18    VV: VariableValues,
19> {
20    executable_document: PhantomData<E>,
21    schema_definition: &'a S,
22    indexed_variable_values: HashMap<&'a str, (&'a VV::Key, &'a VV::Value)>,
23    cache: &'a Cache<'a, E, S>,
24    errors: Vec<VariableValueError<'a, E, VV>>,
25}
26
27impl<'a, E: ExecutableDocument, S: SchemaDefinition, VV: VariableValues> Visitor<'a, E, S, VV>
28    for VariableValuesAreValid<'a, E, S, VV>
29{
30    type ExtraInfo = ();
31
32    fn new(
33        _: &'a E::OperationDefinition,
34        schema_definition: &'a S,
35        variable_values: &'a VV,
36        cache: &'a Cache<'a, E, S>,
37        _: Self::ExtraInfo,
38    ) -> Self {
39        Self {
40            executable_document: PhantomData,
41            schema_definition,
42            indexed_variable_values: variable_values
43                .iter()
44                .map(|(key, value)| (key.as_ref(), (key, value)))
45                .collect(),
46            cache,
47            errors: Vec::new(),
48        }
49    }
50
51    fn visit_variable_definition(
52        &mut self,
53        variable_definition: &'a <E as ExecutableDocument>::VariableDefinition,
54    ) {
55        let key_and_value = self
56            .indexed_variable_values
57            .remove(variable_definition.variable());
58        let Some(variable_definition_input_type) = self
59            .cache
60            .variable_definition_input_type(variable_definition.r#type())
61        else {
62            return;
63        };
64        match key_and_value {
65            Some((_, value)) => {
66                if let Err(errors) = self.schema_definition.coerce_const_value(
67                    variable_definition_input_type,
68                    value,
69                    Default::default(),
70                ) {
71                    self.errors.push(VariableValueError::InvalidValue {
72                        variable_definition,
73                        value,
74                        errors,
75                    });
76                }
77            }
78            None => {
79                if variable_definition.is_required() {
80                    self.errors.push(VariableValueError::MissingValue {
81                        variable_definition,
82                    });
83                }
84            }
85        }
86    }
87}
88
89impl<'a, E: ExecutableDocument, S: SchemaDefinition, VV: VariableValues> Analyzer<'a, E, S, VV>
90    for VariableValuesAreValid<'a, E, S, VV>
91{
92    type Output = Vec<VariableValueError<'a, E, VV>>;
93
94    fn into_output(mut self) -> Self::Output {
95        self.errors.extend(
96            self.indexed_variable_values
97                .into_values()
98                .map(|(key, value)| VariableValueError::UnusedValue { key, value }),
99        );
100        self.errors
101    }
102}
103
104#[derive(Debug)]
105#[allow(clippy::enum_variant_names)]
106pub enum VariableValueError<'a, E: ExecutableDocument, VV: VariableValues> {
107    MissingValue {
108        variable_definition: &'a E::VariableDefinition,
109    },
110    InvalidValue {
111        variable_definition: &'a E::VariableDefinition,
112        value: &'a VV::Value,
113        errors: Vec<CoerceInputError<'a, true, <VV as VariableValues>::Value>>,
114    },
115    UnusedValue {
116        key: &'a VV::Key,
117        value: &'a VV::Value,
118    },
119}
120
121impl<'a, E: ExecutableDocument, VV: VariableValues> VariableValueError<'a, E, VV> {
122    pub fn message(&self) -> String {
123        match self {
124            Self::MissingValue {
125                variable_definition,
126            } => format!(
127                "Missing value for required variable ${}",
128                variable_definition.variable()
129            ),
130            Self::InvalidValue {
131                variable_definition,
132                errors,
133                ..
134            } => format!(
135                "Invalid value for variable ${}:\n- {}",
136                variable_definition.variable(),
137                errors
138                    .iter()
139                    .map(|error| error.message())
140                    .collect::<Vec<_>>()
141                    .join("\n- ")
142            ),
143            Self::UnusedValue { key, .. } => {
144                format!("No variable definition for provided key `{}`", key.as_ref())
145            }
146        }
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use crate::executable::{operation::Orchestrator, Cache};
153    use bluejay_parser::ast::{
154        definition::{DefinitionDocument, SchemaDefinition},
155        executable::ExecutableDocument,
156        Parse,
157    };
158    use once_cell::sync::Lazy;
159
160    use super::VariableValuesAreValid;
161
162    const TEST_SCHEMA_SDL: &str = r#"
163        type Query {
164            noArgs: String!
165            optionalArg(arg: String): String!
166            requiredArg(arg: String!): String!
167        }
168    "#;
169
170    static TEST_DEFINITION_DOCUMENT: Lazy<DefinitionDocument<'static>> =
171        Lazy::new(|| DefinitionDocument::parse(TEST_SCHEMA_SDL).unwrap());
172
173    static TEST_SCHEMA_DEFINITION: Lazy<SchemaDefinition<'static>> =
174        Lazy::new(|| SchemaDefinition::try_from(&*TEST_DEFINITION_DOCUMENT).unwrap());
175
176    fn validate_variable_values(
177        source: &str,
178        operation_name: Option<&str>,
179        variable_values: &serde_json::Value,
180        f: fn(Vec<String>),
181    ) {
182        let executable_document = ExecutableDocument::parse(source).unwrap();
183        let cache = Cache::new(&executable_document, &*TEST_SCHEMA_DEFINITION);
184        f(
185            Orchestrator::<_, _, _, VariableValuesAreValid<_, _, _>>::analyze(
186                &executable_document,
187                &*TEST_SCHEMA_DEFINITION,
188                operation_name,
189                variable_values
190                    .as_object()
191                    .expect("Variables must be an object"),
192                &cache,
193                (),
194            )
195            .unwrap()
196            .into_iter()
197            .map(|err| err.message())
198            .collect(),
199        );
200    }
201
202    #[test]
203    fn test_no_variables() {
204        validate_variable_values(
205            r#"
206                query {
207                    noArgs
208                }
209            "#,
210            None,
211            &serde_json::json!({}),
212            |errors| {
213                assert!(
214                    errors.is_empty(),
215                    "Expected errors to be empty: {:?}",
216                    errors
217                )
218            },
219        );
220        validate_variable_values(
221            r#"
222                query {
223                    noArgs
224                }
225            "#,
226            None,
227            &serde_json::json!({ "foo": "bar" }),
228            |errors| {
229                assert_eq!(
230                    errors,
231                    vec!["No variable definition for provided key `foo`"],
232                )
233            },
234        );
235    }
236
237    #[test]
238    fn test_optional_variables() {
239        validate_variable_values(
240            r#"
241                query($arg: String) {
242                    optionalArg(arg: $arg)
243                }
244            "#,
245            None,
246            &serde_json::json!({}),
247            |errors| {
248                assert!(
249                    errors.is_empty(),
250                    "Expected errors to be empty: {:?}",
251                    errors
252                )
253            },
254        );
255        validate_variable_values(
256            r#"
257                query($arg: String) {
258                    optionalArg(arg: $arg)
259                }
260            "#,
261            None,
262            &serde_json::json!({ "arg": "value" }),
263            |errors| {
264                assert!(
265                    errors.is_empty(),
266                    "Expected errors to be empty: {:?}",
267                    errors
268                )
269            },
270        );
271        validate_variable_values(
272            r#"
273                query($arg: String) {
274                    optionalArg(arg: $arg)
275                }
276            "#,
277            None,
278            &serde_json::json!({ "arg": null }),
279            |errors| {
280                assert!(
281                    errors.is_empty(),
282                    "Expected errors to be empty: {:?}",
283                    errors
284                )
285            },
286        );
287        validate_variable_values(
288            r#"
289                query($arg: String) {
290                    optionalArg(arg: $arg)
291                }
292            "#,
293            None,
294            &serde_json::json!({ "arg": 1 }),
295            |errors| {
296                assert_eq!(
297                    errors,
298                    vec!["Invalid value for variable $arg:\n- No implicit conversion of integer to String"],
299                )
300            },
301        );
302    }
303
304    #[test]
305    fn test_required_variables() {
306        validate_variable_values(
307            r#"
308                query($arg: String!) {
309                    requiredArg(arg: $arg)
310                }
311            "#,
312            None,
313            &serde_json::json!({ "arg": "value" }),
314            |errors| {
315                assert!(
316                    errors.is_empty(),
317                    "Expected errors to be empty: {:?}",
318                    errors
319                )
320            },
321        );
322        validate_variable_values(
323            r#"
324                query($arg: String!) {
325                    requiredArg(arg: $arg)
326                }
327            "#,
328            None,
329            &serde_json::json!({ "arg": null }),
330            |errors| {
331                assert_eq!(
332                    errors,
333                    vec!["Invalid value for variable $arg:\n- Got null when non-null value of type String! was expected"],
334                )
335            },
336        );
337        validate_variable_values(
338            r#"
339                query($arg: String!) {
340                    requiredArg(arg: $arg)
341                }
342            "#,
343            None,
344            &serde_json::json!({}),
345            |errors| assert_eq!(errors, vec!["Missing value for required variable $arg"],),
346        );
347    }
348
349    #[test]
350    fn test_variables_with_defaults() {
351        validate_variable_values(
352            r#"
353                query($arg: String = "default") {
354                    optionalArg(arg: $arg)
355                }
356            "#,
357            None,
358            &serde_json::json!({}),
359            |errors| {
360                assert!(
361                    errors.is_empty(),
362                    "Expected errors to be empty: {:?}",
363                    errors
364                )
365            },
366        );
367        validate_variable_values(
368            r#"
369                query($arg: String! = "default") {
370                    optionalArg(arg: $arg)
371                }
372            "#,
373            None,
374            &serde_json::json!({}),
375            |errors| {
376                assert!(
377                    errors.is_empty(),
378                    "Expected errors to be empty: {:?}",
379                    errors
380                )
381            },
382        );
383        validate_variable_values(
384            r#"
385                query($arg: String = "default") {
386                    optionalArg(arg: $arg)
387                }
388            "#,
389            None,
390            &serde_json::json!({ "arg": "value" }),
391            |errors| {
392                assert!(
393                    errors.is_empty(),
394                    "Expected errors to be empty: {:?}",
395                    errors
396                )
397            },
398        );
399        validate_variable_values(
400            r#"
401                query($arg: String = "default") {
402                    optionalArg(arg: $arg)
403                }
404            "#,
405            None,
406            &serde_json::json!({ "arg": null }),
407            |errors| {
408                assert!(
409                    errors.is_empty(),
410                    "Expected errors to be empty: {:?}",
411                    errors
412                )
413            },
414        );
415        validate_variable_values(
416            r#"
417                query($arg: String = "default") {
418                    optionalArg(arg: $arg)
419                }
420            "#,
421            None,
422            &serde_json::json!({ "arg": 1 }),
423            |errors| {
424                assert_eq!(
425                    errors,
426                    vec!["Invalid value for variable $arg:\n- No implicit conversion of integer to String"],
427                )
428            },
429        );
430    }
431}