async_graphql/validation/
utils.rs

1use std::collections::HashSet;
2
3use async_graphql_value::{ConstValue, Value};
4
5use crate::{QueryPathSegment, context::QueryPathNode, registry};
6
7#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
8pub enum Scope<'a> {
9    Operation(Option<&'a str>),
10    Fragment(&'a str),
11}
12
13fn valid_error(path_node: &QueryPathNode, msg: String) -> String {
14    format!("\"{}\", {}", path_node, msg)
15}
16
17pub fn referenced_variables(value: &Value) -> Vec<&str> {
18    let mut vars = Vec::new();
19    referenced_variables_to_vec(value, &mut vars);
20    vars
21}
22
23fn referenced_variables_to_vec<'a>(value: &'a Value, vars: &mut Vec<&'a str>) {
24    match value {
25        Value::Variable(name) => {
26            vars.push(name);
27        }
28        Value::List(values) => values
29            .iter()
30            .for_each(|value| referenced_variables_to_vec(value, vars)),
31        Value::Object(obj) => obj
32            .values()
33            .for_each(|value| referenced_variables_to_vec(value, vars)),
34        _ => {}
35    }
36}
37
38pub fn is_valid_input_value(
39    registry: &registry::Registry,
40    type_name: &str,
41    value: &ConstValue,
42    path_node: QueryPathNode,
43) -> Option<String> {
44    match registry::MetaTypeName::create(type_name) {
45        registry::MetaTypeName::NonNull(type_name) => match value {
46            ConstValue::Null => Some(valid_error(
47                &path_node,
48                format!("expected type \"{}\"", type_name),
49            )),
50            _ => is_valid_input_value(registry, type_name, value, path_node),
51        },
52        registry::MetaTypeName::List(type_name) => match value {
53            ConstValue::List(elems) => elems.iter().enumerate().find_map(|(idx, elem)| {
54                is_valid_input_value(
55                    registry,
56                    type_name,
57                    elem,
58                    QueryPathNode {
59                        parent: Some(&path_node),
60                        segment: QueryPathSegment::Index(idx),
61                    },
62                )
63            }),
64            ConstValue::Null => None,
65            _ => is_valid_input_value(registry, type_name, value, path_node),
66        },
67        registry::MetaTypeName::Named(type_name) => {
68            if let ConstValue::Null = value {
69                return None;
70            }
71
72            match registry
73                .types
74                .get(type_name)
75                .unwrap_or_else(|| panic!("Type `{}` not defined", type_name))
76            {
77                registry::MetaType::Scalar {
78                    is_valid: Some(is_valid_fn),
79                    ..
80                } => {
81                    if (is_valid_fn)(&value) {
82                        None
83                    } else {
84                        Some(valid_error(
85                            &path_node,
86                            format!("expected type \"{}\"", type_name),
87                        ))
88                    }
89                }
90                registry::MetaType::Scalar { is_valid: None, .. } => None,
91                registry::MetaType::Enum {
92                    enum_values,
93                    name: enum_name,
94                    ..
95                } => match value {
96                    ConstValue::Enum(name) => {
97                        if !enum_values.contains_key(name.as_str()) {
98                            Some(valid_error(
99                                &path_node,
100                                format!(
101                                    "enumeration type \"{}\" does not contain the value \"{}\"",
102                                    enum_name, name
103                                ),
104                            ))
105                        } else {
106                            None
107                        }
108                    }
109                    ConstValue::String(name) => {
110                        if !enum_values.contains_key(name.as_str()) {
111                            Some(valid_error(
112                                &path_node,
113                                format!(
114                                    "enumeration type \"{}\" does not contain the value \"{}\"",
115                                    enum_name, name
116                                ),
117                            ))
118                        } else {
119                            None
120                        }
121                    }
122                    _ => Some(valid_error(
123                        &path_node,
124                        format!("expected type \"{}\"", type_name),
125                    )),
126                },
127                registry::MetaType::InputObject {
128                    input_fields,
129                    name: object_name,
130                    oneof,
131                    ..
132                } => match value {
133                    ConstValue::Object(values) => {
134                        if *oneof {
135                            if values.len() != 1 {
136                                return Some(valid_error(
137                                    &path_node,
138                                    "Oneof input objects requires have exactly one field"
139                                        .to_string(),
140                                ));
141                            }
142
143                            if let ConstValue::Null = values[0] {
144                                return Some(valid_error(
145                                    &path_node,
146                                    "Oneof Input Objects require that exactly one field must be supplied and that field must not be null"
147                                        .to_string(),
148                                ));
149                            }
150                        }
151
152                        let mut input_names =
153                            values.keys().map(AsRef::as_ref).collect::<HashSet<_>>();
154
155                        for field in input_fields.values() {
156                            input_names.remove(&*field.name);
157                            if let Some(value) = values.get(&*field.name) {
158                                if let Some(reason) = is_valid_input_value(
159                                    registry,
160                                    &field.ty,
161                                    value,
162                                    QueryPathNode {
163                                        parent: Some(&path_node),
164                                        segment: QueryPathSegment::Name(&field.name),
165                                    },
166                                ) {
167                                    return Some(reason);
168                                }
169                            } else if registry::MetaTypeName::create(&field.ty).is_non_null()
170                                && field.default_value.is_none()
171                            {
172                                return Some(valid_error(
173                                    &path_node,
174                                    format!(
175                                        r#"field "{}" of type "{}" is required but not provided"#,
176                                        field.name, field.ty,
177                                    ),
178                                ));
179                            }
180                        }
181
182                        if let Some(name) = input_names.iter().next() {
183                            return Some(valid_error(
184                                &path_node,
185                                format!("unknown field \"{}\" of type \"{}\"", name, object_name),
186                            ));
187                        }
188
189                        None
190                    }
191                    _ => None,
192                },
193                _ => None,
194            }
195        }
196    }
197}