libgraphql_core/operation/
fragment_builder.rs

1use crate::operation::{Fragment, FragmentRegistry, Selection, SelectionSetBuildError, SelectionSetBuilder};
2use crate::types::{GraphQLType, GraphQLTypeKind, NamedGraphQLTypeRef};
3use crate::{ast, loc, DirectiveAnnotation, DirectiveAnnotationBuilder};
4use crate::schema::Schema;
5use std::path::Path;
6use thiserror::Error;
7
8type Result<T> = std::result::Result<T, FragmentBuildError>;
9
10#[derive(Clone, Debug, PartialEq)]
11pub struct FragmentBuilder<'schema: 'fragreg, 'fragreg> {
12    def_location: loc::SourceLocation,
13    directives: Vec<DirectiveAnnotation>,
14    name: Option<String>,
15    fragment_registry: &'fragreg FragmentRegistry<'schema>,
16    schema: &'schema Schema,
17    selection_set_builder: SelectionSetBuilder<'schema, 'fragreg>,
18    type_condition_ref: Option<NamedGraphQLTypeRef>,
19}
20
21impl<'schema: 'fragreg, 'fragreg> FragmentBuilder<'schema, 'fragreg> {
22    /// Add a [`DirectiveAnnotation`] after any previously added
23    /// `DirectiveAnnotation`s.
24    pub fn add_directive(
25        mut self,
26        annot: DirectiveAnnotation,
27    ) -> Result<Self> {
28        // TODO: Error if a non-repeatable directive is added twice
29        self.directives.push(annot);
30        Ok(self)
31    }
32
33    pub fn add_selection(
34        mut self,
35        selection: Selection<'schema>,
36    ) -> Result<Self> {
37        self.selection_set_builder =
38            self.selection_set_builder
39                .add_selection(selection)?;
40
41        Ok(self)
42    }
43
44    pub fn build(self) -> Result<Fragment<'schema>> {
45        // TODO: Verify that no fragment-spreads within this Fragment's
46        //       SelectionSetBuilder form any cycles.
47        //
48        //       https://spec.graphql.org/September2025/#sec-Fragment-Spreads-Must-Not-Form-Cycles
49
50        let fragment_name = self.name.ok_or(
51            FragmentBuildError::NoFragmentNameSpecified {
52                fragment_def_src_location: self.def_location.to_owned(),
53            }
54        )?;
55
56        let type_condition_ref = self.type_condition_ref.ok_or(
57            FragmentBuildError::NoTypeConditionSpecified {
58                fragment_name: fragment_name.to_owned(),
59                fragment_src_location: self.def_location.to_owned(),
60            }
61        )?;
62
63        Ok(Fragment {
64            directives: self.directives,
65            def_location: self.def_location,
66            name: fragment_name,
67            schema: self.schema,
68            selection_set: self.selection_set_builder.build()?,
69            type_condition_ref,
70        })
71    }
72
73    pub fn from_ast(
74        schema: &'schema Schema,
75        fragment_registry: &'fragreg FragmentRegistry<'schema>,
76        ast: &ast::operation::FragmentDefinition,
77        file_path: Option<&Path>,
78    ) -> Result<Self> {
79        let fragdef_srcloc = loc::SourceLocation::from_execdoc_ast_position(
80            file_path,
81            &ast.position,
82        );
83
84        let directives = DirectiveAnnotationBuilder::from_ast(
85            &fragdef_srcloc,
86            &ast.directives,
87        );
88
89        let type_condition_type_name = match &ast.type_condition {
90            ast::operation::TypeCondition::On(type_name) => type_name,
91        };
92        let type_condition_type =
93            schema.all_types()
94                .get(type_condition_type_name)
95                .ok_or_else(||
96                    FragmentBuildError::TypeConditionTypeDoesNotExistInSchema {
97                        fragment_name: ast.name.to_string(),
98                        fragment_src_location: fragdef_srcloc.to_owned(),
99                        type_condition_type_name:
100                            type_condition_type_name.to_owned(),
101                    }
102                )?;
103
104        let selection_set_builder = SelectionSetBuilder::from_ast(
105            schema,
106            fragment_registry,
107            type_condition_type,
108            &ast.selection_set,
109            file_path,
110        )?;
111
112        Ok(Self {
113            def_location: fragdef_srcloc.to_owned(),
114            directives,
115            fragment_registry,
116            name: Some(ast.name.to_string()),
117            schema,
118            selection_set_builder,
119            type_condition_ref: Some(NamedGraphQLTypeRef::new(
120                type_condition_type_name,
121                fragdef_srcloc.to_owned(),
122            )),
123        })
124    }
125
126    pub fn set_name(mut self, name: impl Into<String>) -> Result<Self> {
127        let _ = self.name.insert(name.into());
128        Ok(self)
129    }
130
131    pub fn set_type_condition(
132        mut self,
133        graphql_type: &'schema GraphQLType,
134    ) -> Result<Self> {
135        match graphql_type {
136            GraphQLType::Interface(_)
137                | GraphQLType::Object(_)
138                | GraphQLType::Union(_)
139                => (),
140
141            _ => return Err(FragmentBuildError::InvalidFragmentTypeConditionTypeKind {
142                fragment_def_src_location: self.def_location,
143                invalid_type_name: graphql_type.name().to_string(),
144                invalid_type_kind: graphql_type.into(),
145            }),
146        };
147
148        let _ = self.type_condition_ref.insert(NamedGraphQLTypeRef::new(
149            graphql_type.name(),
150            // TODO: Hmm... What if self.def_location is changed?
151            self.def_location.to_owned(),
152        ));
153
154        Ok(self)
155    }
156}
157
158#[derive(Clone, Debug, Error)]
159pub enum FragmentBuildError {
160    #[error("Invalid fragment type condition type: `{invalid_type_kind:?}`")]
161    InvalidFragmentTypeConditionTypeKind {
162        fragment_def_src_location: loc::SourceLocation,
163        invalid_type_name: String,
164        invalid_type_kind: GraphQLTypeKind,
165    },
166
167    #[error("All fragment definitions must include a name")]
168    NoFragmentNameSpecified {
169        fragment_def_src_location: loc::SourceLocation,
170    },
171
172    #[error(
173        "Fragments must specify the type for which they apply to, but none \
174        was specified for the `{fragment_name}` fragment."
175    )]
176    NoTypeConditionSpecified {
177        fragment_name: String,
178        fragment_src_location: loc::SourceLocation,
179    },
180
181    #[error("Failure to build the selection set for this fragment: $0")]
182    SelectionSetBuildErrors(Vec<SelectionSetBuildError>),
183
184    #[error(
185        "The `{fragment_name}` fragment declares its type condition as \
186        `{type_condition_type_name}`, but this type is not defined in \
187        the schema.
188    ")]
189    TypeConditionTypeDoesNotExistInSchema {
190        fragment_name: String,
191        fragment_src_location: loc::SourceLocation,
192        type_condition_type_name: String,
193    },
194}
195impl std::convert::From<Vec<SelectionSetBuildError>> for FragmentBuildError {
196    fn from(value: Vec<SelectionSetBuildError>) -> Self {
197        Self::SelectionSetBuildErrors(value)
198    }
199}