juniper/types/
utilities.rs

1use std::collections::HashSet;
2
3use itertools::Itertools as _;
4
5use crate::{
6    ast::InputValue,
7    schema::{
8        meta::{Argument, EnumMeta, InputObjectMeta, MetaType},
9        model::{SchemaType, TypeType},
10    },
11    value::ScalarValue,
12};
13
14/// Common error messages used in validation and execution of GraphQL operations
15pub(crate) mod error {
16    use std::fmt::Display;
17
18    pub(crate) fn non_null(arg_type: impl Display) -> String {
19        format!("\"null\" specified for not nullable type \"{arg_type}\"")
20    }
21
22    pub(crate) fn enum_value(arg_value: impl Display, arg_type: impl Display) -> String {
23        format!("Invalid value \"{arg_value}\" for enum \"{arg_type}\"")
24    }
25
26    pub(crate) fn type_value(arg_value: impl Display, arg_type: impl Display) -> String {
27        format!("Invalid value \"{arg_value}\" for type \"{arg_type}\"")
28    }
29
30    pub(crate) fn parser(arg_type: impl Display, msg: impl Display) -> String {
31        format!("Parser error for \"{arg_type}\": {msg}")
32    }
33
34    pub(crate) fn not_input_object(arg_type: impl Display) -> String {
35        format!("\"{arg_type}\" is not an input object")
36    }
37
38    pub(crate) fn field(
39        arg_type: impl Display,
40        field_name: impl Display,
41        error_message: impl Display,
42    ) -> String {
43        format!("Error on \"{arg_type}\" field \"{field_name}\": {error_message}")
44    }
45
46    pub(crate) fn missing_fields(arg_type: impl Display, missing_fields: impl Display) -> String {
47        format!("\"{arg_type}\" is missing fields: {missing_fields}")
48    }
49
50    pub(crate) fn unknown_field(arg_type: impl Display, field_name: impl Display) -> String {
51        format!("Field \"{field_name}\" does not exist on type \"{arg_type}\"")
52    }
53
54    pub(crate) fn invalid_list_length(
55        arg_value: impl Display,
56        actual: usize,
57        expected: usize,
58    ) -> String {
59        format!("Expected list of length {expected}, but \"{arg_value}\" has length {actual}")
60    }
61}
62
63/// Validates the specified field of a GraphQL object and returns an error message if the field is
64/// invalid.
65fn validate_object_field<S>(
66    schema: &SchemaType<S>,
67    object_type: &TypeType<S>,
68    object_fields: &[Argument<S>],
69    field_value: &InputValue<S>,
70    field_key: &str,
71) -> Option<String>
72where
73    S: ScalarValue,
74{
75    let field_type = object_fields
76        .iter()
77        .filter(|f| f.name == field_key)
78        .map(|f| schema.make_type(&f.arg_type))
79        .next();
80
81    if let Some(field_arg_type) = field_type {
82        let error_message = validate_literal_value(schema, &field_arg_type, field_value);
83
84        error_message.map(|m| error::field(object_type, field_key, m))
85    } else {
86        Some(error::unknown_field(object_type, field_key))
87    }
88}
89
90/// Validates the specified GraphQL literal and returns an error message if it's invalid.
91pub fn validate_literal_value<S>(
92    schema: &SchemaType<S>,
93    arg_type: &TypeType<S>,
94    arg_value: &InputValue<S>,
95) -> Option<String>
96where
97    S: ScalarValue,
98{
99    match *arg_type {
100        TypeType::NonNull(ref inner) => {
101            if arg_value.is_null() {
102                Some(error::non_null(arg_type))
103            } else {
104                validate_literal_value(schema, inner, arg_value)
105            }
106        }
107        TypeType::List(ref inner, expected_size) => match *arg_value {
108            InputValue::Null | InputValue::Variable(_) => None,
109            InputValue::List(ref items) => {
110                if let Some(expected) = expected_size {
111                    if items.len() != expected {
112                        return Some(error::invalid_list_length(arg_value, items.len(), expected));
113                    }
114                }
115                items
116                    .iter()
117                    .find_map(|i| validate_literal_value(schema, inner, &i.item))
118            }
119            ref v => {
120                if let Some(expected) = expected_size {
121                    if expected != 1 {
122                        return Some(error::invalid_list_length(arg_value, 1, expected));
123                    }
124                }
125                validate_literal_value(schema, inner, v)
126            }
127        },
128        TypeType::Concrete(t) => {
129            // Even though InputValue::String can be parsed into an enum, they
130            // are not valid as enum *literals* in a GraphQL query.
131            if let (&InputValue::Scalar(_), Some(&MetaType::Enum(EnumMeta { .. }))) =
132                (arg_value, arg_type.to_concrete())
133            {
134                return Some(error::enum_value(arg_value, arg_type));
135            }
136
137            match *arg_value {
138                InputValue::Null | InputValue::Variable(_) => None,
139                ref v @ InputValue::Scalar(_) | ref v @ InputValue::Enum(_) => {
140                    if let Some(parse_fn) = t.input_value_parse_fn() {
141                        if parse_fn(v).is_ok() {
142                            None
143                        } else {
144                            Some(error::type_value(arg_value, arg_type))
145                        }
146                    } else {
147                        Some(error::parser(arg_type, "no parser present"))
148                    }
149                }
150                InputValue::List(_) => Some("Input lists are not literals".to_owned()),
151                InputValue::Object(ref obj) => {
152                    if let MetaType::InputObject(InputObjectMeta {
153                        ref input_fields, ..
154                    }) = *t
155                    {
156                        let mut remaining_required_fields = input_fields
157                            .iter()
158                            .filter_map(|f| {
159                                (f.arg_type.is_non_null() && f.default_value.is_none())
160                                    .then_some(f.name.as_str())
161                            })
162                            .collect::<HashSet<_>>();
163
164                        let error_message = obj.iter().find_map(|(key, value)| {
165                            remaining_required_fields.remove(key.item.as_str());
166                            validate_object_field(
167                                schema,
168                                arg_type,
169                                input_fields,
170                                &value.item,
171                                &key.item,
172                            )
173                        });
174
175                        if error_message.is_some() {
176                            return error_message;
177                        }
178
179                        if remaining_required_fields.is_empty() {
180                            None
181                        } else {
182                            let missing_fields = remaining_required_fields
183                                .into_iter()
184                                .format_with(", ", |s, f| f(&format_args!("\"{s}\"")));
185                            Some(error::missing_fields(arg_type, missing_fields))
186                        }
187                    } else {
188                        Some(error::not_input_object(arg_type))
189                    }
190                }
191            }
192        }
193    }
194}