trustfall_core/frontend/
validation.rs

1use std::sync::Arc;
2
3use async_graphql_parser::types::TypeKind;
4
5use crate::{
6    graphql_query::query::{FieldConnection, FieldNode, Query},
7    ir::TYPENAME_META_FIELD,
8    schema::Schema,
9};
10
11use super::{
12    error::{FrontendError, ValidationError},
13    util::get_underlying_named_type,
14};
15
16pub(super) fn validate_query_against_schema(
17    schema: &Schema,
18    query: &Query,
19) -> Result<(), FrontendError> {
20    let mut path = vec![];
21    validate_field(
22        schema,
23        schema.query_type_name(),
24        &mut path,
25        &query.root_connection,
26        &query.root_field,
27    )
28}
29
30fn validate_field<'a>(
31    schema: &Schema,
32    parent_type_name: &str,
33    path: &mut Vec<&'a str>,
34    connection: &FieldConnection,
35    node: &'a FieldNode,
36) -> Result<(), FrontendError> {
37    // TODO: Maybe consider a better representation that doesn't have this duplication?
38    assert_eq!(connection.name, node.name);
39    assert_eq!(connection.alias, node.alias);
40
41    if node.name.as_ref() == TYPENAME_META_FIELD {
42        // This is a meta field of scalar "String!" type that is guaranteed to exist.
43        // We just have to make sure that it's used as a property, and not as an edge.
44        if !node.connections.is_empty() {
45            return Err(FrontendError::PropertyMetaFieldUsedAsEdge(
46                TYPENAME_META_FIELD.to_string(),
47            ));
48        }
49
50        return Ok(());
51    }
52
53    let old_path_length = path.len();
54    let field_def = schema
55        .fields
56        .get(&(Arc::from(parent_type_name.to_string()), Arc::from(node.name.to_string())))
57        .ok_or_else(|| {
58            path.push(&node.name);
59            FrontendError::ValidationError(ValidationError::NonExistentPath(
60                path.iter().map(|x| x.to_string()).collect(),
61            ))
62        })?;
63
64    path.push(&node.name);
65
66    let pre_coercion_type_name = get_underlying_named_type(&field_def.ty.node).as_ref();
67    let field_type_name = if let Some(coerced) = &node.coerced_to {
68        let pre_coercion_type_definition = &schema.vertex_types[pre_coercion_type_name];
69        if let TypeKind::Interface(_) = &pre_coercion_type_definition.kind {
70        } else {
71            // Only interface types may be coerced into other types. This is not an interface.
72            return Err(FrontendError::ValidationError(
73                ValidationError::CannotCoerceNonInterfaceType(
74                    pre_coercion_type_name.to_string(),
75                    coerced.to_string(),
76                ),
77            ));
78        }
79
80        if let Some(post_coercion_type_definition) = schema.vertex_types.get(coerced) {
81            let implemented_interfaces = match &post_coercion_type_definition.kind {
82                TypeKind::Object(o) => &o.implements,
83                TypeKind::Interface(i) => &i.implements,
84                TypeKind::Scalar
85                | TypeKind::Union(_)
86                | TypeKind::Enum(_)
87                | TypeKind::InputObject(_) => unreachable!(),
88            };
89            if !implemented_interfaces.iter().any(|x| x.node.as_ref() == pre_coercion_type_name) {
90                // The specified coerced-to type does not implement the source interface.
91                return Err(FrontendError::ValidationError(
92                    ValidationError::CannotCoerceToUnrelatedType(
93                        pre_coercion_type_name.to_string(),
94                        coerced.to_string(),
95                    ),
96                ));
97            }
98        } else {
99            // The coerced-to type is not part of the schema.
100            return Err(FrontendError::ValidationError(ValidationError::NonExistentType(
101                coerced.to_string(),
102            )));
103        }
104
105        path.push(coerced);
106        coerced.as_ref()
107    } else {
108        pre_coercion_type_name
109    };
110
111    for (child_connection, child_node) in node.connections.iter() {
112        validate_field(schema, field_type_name, path, child_connection, child_node)?;
113    }
114
115    path.pop().unwrap();
116    if node.coerced_to.is_some() {
117        path.pop().unwrap();
118    }
119    assert_eq!(old_path_length, path.len());
120
121    Ok(())
122}