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}