Skip to main content

libgraphql_parser/compat/graphql_parser_v0_4/
to_query.rs

1//! Forward query conversion: libgraphql AST →
2//! `graphql_parser` v0.4 query `Document`.
3
4use crate::ast;
5use crate::compat::graphql_parser_v0_4::helpers::directives_to_gp;
6use crate::compat::graphql_parser_v0_4::helpers::end_pos_from_span;
7use crate::compat::graphql_parser_v0_4::helpers::pos_from_span;
8use crate::compat::graphql_parser_v0_4::helpers::type_annotation_to_gp;
9use crate::compat::graphql_parser_v0_4::helpers::value_to_gp;
10use crate::GraphQLParseError;
11use crate::GraphQLParseErrorKind;
12use crate::ParseResult;
13use crate::SourceSpan;
14
15fn selection_set_to_gp(
16    sel_set: &ast::SelectionSet<'_>,
17    errors: &mut Vec<GraphQLParseError>,
18    source_map: &crate::SourceMap<'_>,
19) -> graphql_parser::query::SelectionSet<'static, String>
20{
21    graphql_parser::query::SelectionSet {
22        span: (
23            pos_from_span(sel_set.span, source_map),
24            end_pos_from_span(
25                sel_set.span, source_map,
26            ),
27        ),
28        items: sel_set
29            .selections
30            .iter()
31            .map(|s| {
32                selection_to_gp(s, errors, source_map)
33            })
34            .collect(),
35    }
36}
37
38fn selection_to_gp(
39    sel: &ast::Selection<'_>,
40    errors: &mut Vec<GraphQLParseError>,
41    source_map: &crate::SourceMap<'_>,
42) -> graphql_parser::query::Selection<'static, String> {
43    use graphql_parser::query::Selection as GpSel;
44    match sel {
45        ast::Selection::Field(field) => {
46            GpSel::Field(
47                query_field_to_gp(
48                    field, errors, source_map,
49                ),
50            )
51        },
52        ast::Selection::FragmentSpread(frag_spread) => {
53            GpSel::FragmentSpread(
54                fragment_spread_to_gp(
55                    frag_spread, source_map,
56                ),
57            )
58        },
59        ast::Selection::InlineFragment(inline_frag) => {
60            GpSel::InlineFragment(
61                inline_fragment_to_gp(
62                    inline_frag,
63                    errors,
64                    source_map,
65                ),
66            )
67        },
68    }
69}
70
71fn query_field_to_gp(
72    field: &ast::FieldSelection<'_>,
73    errors: &mut Vec<GraphQLParseError>,
74    source_map: &crate::SourceMap<'_>,
75) -> graphql_parser::query::Field<'static, String> {
76    graphql_parser::query::Field {
77        position: pos_from_span(
78            field.span, source_map,
79        ),
80        alias: field
81            .alias
82            .as_ref()
83            .map(|a| a.value.to_string()),
84        name: field.name.value.to_string(),
85        arguments: field
86            .arguments
87            .iter()
88            .map(|arg| {
89                (
90                    arg.name.value.to_string(),
91                    value_to_gp(&arg.value),
92                )
93            })
94            .collect(),
95        directives: directives_to_gp(
96            &field.directives, source_map,
97        ),
98        selection_set: match &field.selection_set {
99            Some(sel_set) => {
100                selection_set_to_gp(
101                    sel_set, errors, source_map,
102                )
103            },
104            None => graphql_parser::query::SelectionSet {
105                span: (
106                    pos_from_span(
107                        field.span, source_map,
108                    ),
109                    pos_from_span(
110                        field.span, source_map,
111                    ),
112                ),
113                items: vec![],
114            },
115        },
116    }
117}
118
119fn fragment_spread_to_gp(
120    frag_spread: &ast::FragmentSpread<'_>,
121    source_map: &crate::SourceMap<'_>,
122) -> graphql_parser::query::FragmentSpread<
123    'static,
124    String,
125> {
126    graphql_parser::query::FragmentSpread {
127        // graphql_parser captures position() after
128        // consuming `...`, so position points at the
129        // fragment name — not the ellipsis.
130        position: pos_from_span(
131            frag_spread.name.span, source_map,
132        ),
133        fragment_name: frag_spread
134            .name
135            .value
136            .to_string(),
137        directives: directives_to_gp(
138            &frag_spread.directives, source_map,
139        ),
140    }
141}
142
143fn inline_fragment_to_gp(
144    inline_frag: &ast::InlineFragment<'_>,
145    errors: &mut Vec<GraphQLParseError>,
146    source_map: &crate::SourceMap<'_>,
147) -> graphql_parser::query::InlineFragment<
148    'static,
149    String,
150> {
151    graphql_parser::query::InlineFragment {
152        // graphql_parser captures position() after
153        // consuming `...`, so position points at the
154        // first token that follows: `on` keyword,
155        // first directive, or opening `{`.
156        position: if let Some(tc) =
157            &inline_frag.type_condition
158        {
159            pos_from_span(tc.span, source_map)
160        } else if let Some(dir) =
161            inline_frag.directives.first()
162        {
163            pos_from_span(dir.span, source_map)
164        } else {
165            pos_from_span(
166                inline_frag.selection_set.span,
167                source_map,
168            )
169        },
170        type_condition: inline_frag
171            .type_condition
172            .as_ref()
173            .map(type_condition_to_gp),
174        directives: directives_to_gp(
175            &inline_frag.directives, source_map,
176        ),
177        selection_set: selection_set_to_gp(
178            &inline_frag.selection_set,
179            errors,
180            source_map,
181        ),
182    }
183}
184
185fn type_condition_to_gp(
186    type_cond: &ast::TypeCondition<'_>,
187) -> graphql_parser::query::TypeCondition<
188    'static,
189    String,
190> {
191    graphql_parser::query::TypeCondition::On(
192        type_cond.named_type.value.to_string(),
193    )
194}
195
196fn variable_def_to_gp(
197    var_def: &ast::VariableDefinition<'_>,
198    errors: &mut Vec<GraphQLParseError>,
199    source_map: &crate::SourceMap<'_>,
200) -> graphql_parser::query::VariableDefinition<
201    'static,
202    String,
203> {
204    if !var_def.directives.is_empty() {
205        errors.push(GraphQLParseError::new(
206            "Variable directives cannot be \
207             represented in graphql_parser v0.4 AST",
208            GraphQLParseErrorKind::UnsupportedFeature {
209                feature: "variable directives"
210                    .to_string(),
211            },
212            source_map.resolve_span(var_def.span)
213                .unwrap_or_else(SourceSpan::zero),
214        ));
215    }
216    graphql_parser::query::VariableDefinition {
217        position: pos_from_span(
218            var_def.span, source_map,
219        ),
220        name: var_def.variable.value.to_string(),
221        var_type: type_annotation_to_gp(
222            &var_def.var_type,
223        ),
224        default_value: var_def
225            .default_value
226            .as_ref()
227            .map(value_to_gp),
228    }
229}
230
231fn fragment_def_to_gp(
232    frag_def: &ast::FragmentDefinition<'_>,
233    errors: &mut Vec<GraphQLParseError>,
234    source_map: &crate::SourceMap<'_>,
235) -> graphql_parser::query::FragmentDefinition<
236    'static,
237    String,
238> {
239    graphql_parser::query::FragmentDefinition {
240        position: pos_from_span(
241            frag_def.span, source_map,
242        ),
243        name: frag_def.name.value.to_string(),
244        type_condition: type_condition_to_gp(
245            &frag_def.type_condition,
246        ),
247        directives: directives_to_gp(
248            &frag_def.directives, source_map,
249        ),
250        selection_set: selection_set_to_gp(
251            &frag_def.selection_set,
252            errors,
253            source_map,
254        ),
255    }
256}
257
258fn operation_def_to_gp(
259    op_def: &ast::OperationDefinition<'_>,
260    errors: &mut Vec<GraphQLParseError>,
261    source_map: &crate::SourceMap<'_>,
262) -> graphql_parser::query::OperationDefinition<
263    'static,
264    String,
265> {
266    use graphql_parser::query::OperationDefinition
267        as GpOp;
268
269    let var_defs: Vec<_> = op_def
270        .variable_definitions
271        .iter()
272        .map(|var_def| {
273            variable_def_to_gp(
274                var_def, errors, source_map,
275            )
276        })
277        .collect();
278    let sel_set = selection_set_to_gp(
279        &op_def.selection_set,
280        errors,
281        source_map,
282    );
283    let dirs = directives_to_gp(
284        &op_def.directives, source_map,
285    );
286    let pos = pos_from_span(
287        op_def.span, source_map,
288    );
289    let name = op_def
290        .name
291        .as_ref()
292        .map(|n| n.value.to_string());
293
294    if op_def.shorthand {
295        return GpOp::SelectionSet(sel_set);
296    }
297
298    match op_def.operation_kind {
299        ast::OperationKind::Query => {
300            GpOp::Query(graphql_parser::query::Query {
301                position: pos,
302                name,
303                variable_definitions: var_defs,
304                directives: dirs,
305                selection_set: sel_set,
306            })
307        },
308        ast::OperationKind::Mutation => {
309            GpOp::Mutation(
310                graphql_parser::query::Mutation {
311                    position: pos,
312                    name,
313                    variable_definitions: var_defs,
314                    directives: dirs,
315                    selection_set: sel_set,
316                },
317            )
318        },
319        ast::OperationKind::Subscription => {
320            GpOp::Subscription(
321                graphql_parser::query::Subscription {
322                    position: pos,
323                    name,
324                    variable_definitions: var_defs,
325                    directives: dirs,
326                    selection_set: sel_set,
327                },
328            )
329        },
330    }
331}
332
333/// Convert a libgraphql AST `Document` to a
334/// `graphql_parser` query `Document`.
335///
336/// Returns `ParseResult` with errors for any features
337/// that `graphql_parser` cannot represent:
338/// - `VariableDefinition` with non-empty directives
339///   (directives dropped)
340///
341/// Type-system definitions (schema, types, directives,
342/// extensions) are silently skipped since they belong in
343/// `to_graphql_parser_schema_ast`.
344pub fn to_graphql_parser_query_ast<'a>(
345    doc: &ast::Document<'_>,
346    source_map: &crate::SourceMap<'a>,
347) -> ParseResult<
348    'a,
349    graphql_parser::query::Document<'static, String>,
350> {
351    let mut errors: Vec<GraphQLParseError> = Vec::new();
352    let mut definitions = Vec::new();
353
354    for def in &doc.definitions {
355        match def {
356            ast::Definition::FragmentDefinition(
357                frag_def,
358            ) => {
359                definitions.push(
360                    graphql_parser::query::Definition
361                        ::Fragment(
362                        fragment_def_to_gp(
363                            frag_def,
364                            &mut errors,
365                            source_map,
366                        ),
367                    ),
368                );
369            },
370            ast::Definition::OperationDefinition(
371                op_def,
372            ) => {
373                definitions.push(
374                    graphql_parser::query::Definition
375                        ::Operation(
376                        operation_def_to_gp(
377                            op_def,
378                            &mut errors,
379                            source_map,
380                        ),
381                    ),
382                );
383            },
384            // Type-system defs are skipped in query
385            // conversion
386            ast::Definition::DirectiveDefinition(_)
387            | ast::Definition::SchemaDefinition(_)
388            | ast::Definition::SchemaExtension(_)
389            | ast::Definition::TypeDefinition(_)
390            | ast::Definition::TypeExtension(_) => {},
391        }
392    }
393
394    let gp_doc =
395        graphql_parser::query::Document { definitions };
396
397    if errors.is_empty() {
398        ParseResult::new_ok(gp_doc, source_map.clone())
399    } else {
400        ParseResult::new_recovered(gp_doc, errors, source_map.clone())
401    }
402}