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}