Skip to main content

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<E: ExecutableDocument, VV: VariableValues> VariableValueError<'_, 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).result.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).result.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| assert!(errors.is_empty(), "Expected errors to be empty: {errors:?}",),
213        );
214        validate_variable_values(
215            r#"
216                query {
217                    noArgs
218                }
219            "#,
220            None,
221            &serde_json::json!({ "foo": "bar" }),
222            |errors| {
223                assert_eq!(
224                    errors,
225                    vec!["No variable definition for provided key `foo`"],
226                )
227            },
228        );
229    }
230
231    #[test]
232    fn test_optional_variables() {
233        validate_variable_values(
234            r#"
235                query($arg: String) {
236                    optionalArg(arg: $arg)
237                }
238            "#,
239            None,
240            &serde_json::json!({}),
241            |errors| assert!(errors.is_empty(), "Expected errors to be empty: {errors:?}",),
242        );
243        validate_variable_values(
244            r#"
245                query($arg: String) {
246                    optionalArg(arg: $arg)
247                }
248            "#,
249            None,
250            &serde_json::json!({ "arg": "value" }),
251            |errors| assert!(errors.is_empty(), "Expected errors to be empty: {errors:?}",),
252        );
253        validate_variable_values(
254            r#"
255                query($arg: String) {
256                    optionalArg(arg: $arg)
257                }
258            "#,
259            None,
260            &serde_json::json!({ "arg": null }),
261            |errors| assert!(errors.is_empty(), "Expected errors to be empty: {errors:?}",),
262        );
263        validate_variable_values(
264            r#"
265                query($arg: String) {
266                    optionalArg(arg: $arg)
267                }
268            "#,
269            None,
270            &serde_json::json!({ "arg": 1 }),
271            |errors| {
272                assert_eq!(
273                    errors,
274                    vec!["Invalid value for variable $arg:\n- No implicit conversion of integer to String"],
275                )
276            },
277        );
278    }
279
280    #[test]
281    fn test_required_variables() {
282        validate_variable_values(
283            r#"
284                query($arg: String!) {
285                    requiredArg(arg: $arg)
286                }
287            "#,
288            None,
289            &serde_json::json!({ "arg": "value" }),
290            |errors| assert!(errors.is_empty(), "Expected errors to be empty: {errors:?}",),
291        );
292        validate_variable_values(
293            r#"
294                query($arg: String!) {
295                    requiredArg(arg: $arg)
296                }
297            "#,
298            None,
299            &serde_json::json!({ "arg": null }),
300            |errors| {
301                assert_eq!(
302                    errors,
303                    vec!["Invalid value for variable $arg:\n- Got null when non-null value of type String! was expected"],
304                )
305            },
306        );
307        validate_variable_values(
308            r#"
309                query($arg: String!) {
310                    requiredArg(arg: $arg)
311                }
312            "#,
313            None,
314            &serde_json::json!({}),
315            |errors| assert_eq!(errors, vec!["Missing value for required variable $arg"],),
316        );
317    }
318
319    #[test]
320    fn test_variables_with_defaults() {
321        validate_variable_values(
322            r#"
323                query($arg: String = "default") {
324                    optionalArg(arg: $arg)
325                }
326            "#,
327            None,
328            &serde_json::json!({}),
329            |errors| assert!(errors.is_empty(), "Expected errors to be empty: {errors:?}",),
330        );
331        validate_variable_values(
332            r#"
333                query($arg: String! = "default") {
334                    optionalArg(arg: $arg)
335                }
336            "#,
337            None,
338            &serde_json::json!({}),
339            |errors| assert!(errors.is_empty(), "Expected errors to be empty: {errors:?}",),
340        );
341        validate_variable_values(
342            r#"
343                query($arg: String = "default") {
344                    optionalArg(arg: $arg)
345                }
346            "#,
347            None,
348            &serde_json::json!({ "arg": "value" }),
349            |errors| assert!(errors.is_empty(), "Expected errors to be empty: {errors:?}",),
350        );
351        validate_variable_values(
352            r#"
353                query($arg: String = "default") {
354                    optionalArg(arg: $arg)
355                }
356            "#,
357            None,
358            &serde_json::json!({ "arg": null }),
359            |errors| assert!(errors.is_empty(), "Expected errors to be empty: {errors:?}",),
360        );
361        validate_variable_values(
362            r#"
363                query($arg: String = "default") {
364                    optionalArg(arg: $arg)
365                }
366            "#,
367            None,
368            &serde_json::json!({ "arg": 1 }),
369            |errors| {
370                assert_eq!(
371                    errors,
372                    vec!["Invalid value for variable $arg:\n- No implicit conversion of integer to String"],
373                )
374            },
375        );
376    }
377}