test_shisho_policy_sdk/gqlgen/query/
validation.rs

1use super::{full_path_prefix, BoundQuery, Query, QueryValidationError, Selection, SelectionId};
2use crate::gqlgen::schema::TypeId;
3
4pub(super) fn validate_typename_presence(
5    query: &BoundQuery<'_>,
6) -> Result<(), QueryValidationError> {
7    for fragment in query.query.fragments.iter() {
8        let type_id = match fragment.on {
9            id @ TypeId::Interface(_) | id @ TypeId::Union(_) => id,
10            _ => continue,
11        };
12
13        if !selection_set_contains_type_name(fragment.on, &fragment.selection_set, query.query) {
14            return Err(QueryValidationError::new(format!(
15                "The `{}` fragment uses `{}` but does not select `__typename` on it. graphql-client cannot generate code for it. Please add `__typename` to the selection.",
16                &fragment.name,
17                type_id.name(query.schema),
18            )));
19        }
20    }
21
22    let union_and_interface_field_selections =
23        query
24            .query
25            .selections()
26            .filter_map(|(selection_id, selection)| match selection {
27                Selection::Field(field) => match query.schema.get_field(field.field_id).r#type.id {
28                    id @ TypeId::Interface(_) | id @ TypeId::Union(_) => {
29                        Some((selection_id, id, &field.selection_set))
30                    }
31                    _ => None,
32                },
33                _ => None,
34            });
35
36    for selection in union_and_interface_field_selections {
37        if !selection_set_contains_type_name(selection.1, selection.2, query.query) {
38            return Err(QueryValidationError::new(format!(
39                "The query uses `{path}` at `{selected_type}` but does not select `__typename` on it. graphql-client cannot generate code for it. Please add `__typename` to the selection.",
40                path = full_path_prefix(selection.0, query),
41                selected_type = selection.1.name(query.schema)
42            )));
43        }
44    }
45
46    Ok(())
47}
48
49fn selection_set_contains_type_name(
50    parent_type_id: TypeId,
51    selection_set: &[SelectionId],
52    query: &Query,
53) -> bool {
54    for id in selection_set {
55        let selection = query.get_selection(*id);
56
57        match selection {
58            Selection::Typename => return true,
59            Selection::FragmentSpread(fragment_id) => {
60                let fragment = query.get_fragment(*fragment_id);
61                if fragment.on == parent_type_id
62                    && selection_set_contains_type_name(fragment.on, &fragment.selection_set, query)
63                {
64                    return true;
65                }
66            }
67            _ => (),
68        }
69    }
70
71    false
72}