Skip to main content

wesley_core/adapters/
apollo.rs

1//! Apollo Parser implementation of the LoweringPort.
2
3use crate::domain::error::WesleyError;
4use crate::domain::ir::*;
5use crate::domain::operation::{
6    OperationArgument, OperationDirectiveArgs, OperationType, SchemaOperation,
7};
8use crate::domain::schema_delta::{diff_schema_ir, SchemaDelta};
9use crate::ports::lowering::LoweringPort;
10use apollo_parser::{cst, Parser};
11use async_trait::async_trait;
12use indexmap::IndexMap;
13use std::collections::{BTreeMap, HashMap};
14
15/// Adapter that uses `apollo-parser` to lower SDL to IR.
16pub struct ApolloLoweringAdapter {
17    _max_retries: usize,
18}
19
20impl ApolloLoweringAdapter {
21    /// Creates a new adapter.
22    pub fn new(max_retries: usize) -> Self {
23        Self {
24            _max_retries: max_retries,
25        }
26    }
27}
28
29#[async_trait]
30impl LoweringPort for ApolloLoweringAdapter {
31    async fn lower_sdl(&self, sdl: &str) -> Result<WesleyIR, WesleyError> {
32        self.parse_and_lower(sdl)
33    }
34}
35
36/// Lowers GraphQL SDL into the Wesley L1 IR using the Apollo parser adapter.
37pub fn lower_schema_sdl(sdl: &str) -> Result<WesleyIR, WesleyError> {
38    ApolloLoweringAdapter::new(0).parse_and_lower(sdl)
39}
40
41/// Computes the structural L1 delta between two GraphQL SDL documents.
42pub fn diff_schema_sdl(old_sdl: &str, new_sdl: &str) -> Result<SchemaDelta, WesleyError> {
43    let adapter = ApolloLoweringAdapter::new(0);
44    let old_ir = adapter.parse_and_lower(old_sdl)?;
45    let new_ir = adapter.parse_and_lower(new_sdl)?;
46
47    Ok(diff_schema_ir(&old_ir, &new_ir))
48}
49
50/// Lists schema root operations from GraphQL SDL.
51pub fn list_schema_operations_sdl(schema_sdl: &str) -> Result<Vec<SchemaOperation>, WesleyError> {
52    let parser = Parser::new(schema_sdl);
53    let cst = parser.parse();
54
55    let errors = cst.errors().collect::<Vec<_>>();
56    if !errors.is_empty() {
57        let err = &errors[0];
58        return Err(WesleyError::ParseError {
59            message: err.message().to_string(),
60            line: None,
61            column: None,
62        });
63    }
64
65    let doc = cst.document();
66    let mut root_types = RootTypes::default();
67    for def in doc.definitions() {
68        match def {
69            cst::Definition::SchemaDefinition(schema) => {
70                update_root_types(schema.root_operation_type_definitions(), &mut root_types)?;
71            }
72            cst::Definition::SchemaExtension(schema) => {
73                update_root_types(schema.root_operation_type_definitions(), &mut root_types)?;
74            }
75            _ => {}
76        }
77    }
78
79    let mut operations = Vec::new();
80    for def in doc.definitions() {
81        match def {
82            cst::Definition::ObjectTypeDefinition(node) => {
83                collect_schema_operations_from_object(
84                    node.name(),
85                    node.fields_definition(),
86                    &root_types,
87                    &mut operations,
88                )?;
89            }
90            cst::Definition::ObjectTypeExtension(node) => {
91                collect_schema_operations_from_object(
92                    node.name(),
93                    node.fields_definition(),
94                    &root_types,
95                    &mut operations,
96                )?;
97            }
98            _ => {}
99        }
100    }
101
102    Ok(operations)
103}
104
105/// Represents the consolidated parts of a single GraphQL Type.
106struct TypeAggregate {
107    name: String,
108    kind: TypeKind,
109    definitions: Vec<TypeDefinitionNode>,
110    extensions: Vec<TypeExtensionNode>,
111}
112
113enum TypeDefinitionNode {
114    Scalar(cst::ScalarTypeDefinition),
115    Object(cst::ObjectTypeDefinition),
116    Interface(cst::InterfaceTypeDefinition),
117    Union(cst::UnionTypeDefinition),
118    Enum(cst::EnumTypeDefinition),
119    InputObject(cst::InputObjectTypeDefinition),
120}
121
122impl TypeDefinitionNode {
123    fn name(&self) -> Option<cst::Name> {
124        match self {
125            TypeDefinitionNode::Scalar(node) => node.name(),
126            TypeDefinitionNode::Object(node) => node.name(),
127            TypeDefinitionNode::Interface(node) => node.name(),
128            TypeDefinitionNode::Union(node) => node.name(),
129            TypeDefinitionNode::Enum(node) => node.name(),
130            TypeDefinitionNode::InputObject(node) => node.name(),
131        }
132    }
133
134    fn description(&self) -> Option<cst::Description> {
135        match self {
136            TypeDefinitionNode::Scalar(node) => node.description(),
137            TypeDefinitionNode::Object(node) => node.description(),
138            TypeDefinitionNode::Interface(node) => node.description(),
139            TypeDefinitionNode::Union(node) => node.description(),
140            TypeDefinitionNode::Enum(node) => node.description(),
141            TypeDefinitionNode::InputObject(node) => node.description(),
142        }
143    }
144}
145
146enum TypeExtensionNode {
147    Scalar(cst::ScalarTypeExtension),
148    Object(cst::ObjectTypeExtension),
149    Interface(cst::InterfaceTypeExtension),
150    Union(cst::UnionTypeExtension),
151    Enum(cst::EnumTypeExtension),
152    InputObject(cst::InputObjectTypeExtension),
153}
154
155impl TypeExtensionNode {
156    fn name(&self) -> Option<cst::Name> {
157        match self {
158            TypeExtensionNode::Scalar(node) => node.name(),
159            TypeExtensionNode::Object(node) => node.name(),
160            TypeExtensionNode::Interface(node) => node.name(),
161            TypeExtensionNode::Union(node) => node.name(),
162            TypeExtensionNode::Enum(node) => node.name(),
163            TypeExtensionNode::InputObject(node) => node.name(),
164        }
165    }
166}
167
168impl ApolloLoweringAdapter {
169    fn parse_and_lower(&self, sdl: &str) -> Result<WesleyIR, WesleyError> {
170        let parser = Parser::new(sdl);
171        let cst = parser.parse();
172
173        let errors = cst.errors().collect::<Vec<_>>();
174        if !errors.is_empty() {
175            let err = &errors[0];
176            return Err(WesleyError::ParseError {
177                message: err.message().to_string(),
178                line: None,
179                column: None,
180            });
181        }
182
183        let doc = cst.document();
184        let mut aggregates: BTreeMap<String, TypeAggregate> = BTreeMap::new();
185
186        for def in doc.definitions() {
187            match def {
188                cst::Definition::ScalarTypeDefinition(node) => self.aggregate_definition(
189                    TypeDefinitionNode::Scalar(node),
190                    TypeKind::Scalar,
191                    &mut aggregates,
192                )?,
193                cst::Definition::ObjectTypeDefinition(node) => self.aggregate_definition(
194                    TypeDefinitionNode::Object(node),
195                    TypeKind::Object,
196                    &mut aggregates,
197                )?,
198                cst::Definition::InterfaceTypeDefinition(node) => self.aggregate_definition(
199                    TypeDefinitionNode::Interface(node),
200                    TypeKind::Interface,
201                    &mut aggregates,
202                )?,
203                cst::Definition::UnionTypeDefinition(node) => self.aggregate_definition(
204                    TypeDefinitionNode::Union(node),
205                    TypeKind::Union,
206                    &mut aggregates,
207                )?,
208                cst::Definition::EnumTypeDefinition(node) => self.aggregate_definition(
209                    TypeDefinitionNode::Enum(node),
210                    TypeKind::Enum,
211                    &mut aggregates,
212                )?,
213                cst::Definition::InputObjectTypeDefinition(node) => self.aggregate_definition(
214                    TypeDefinitionNode::InputObject(node),
215                    TypeKind::InputObject,
216                    &mut aggregates,
217                )?,
218                cst::Definition::ScalarTypeExtension(node) => self.aggregate_extension(
219                    TypeExtensionNode::Scalar(node),
220                    TypeKind::Scalar,
221                    &mut aggregates,
222                )?,
223                cst::Definition::ObjectTypeExtension(node) => self.aggregate_extension(
224                    TypeExtensionNode::Object(node),
225                    TypeKind::Object,
226                    &mut aggregates,
227                )?,
228                cst::Definition::InterfaceTypeExtension(node) => self.aggregate_extension(
229                    TypeExtensionNode::Interface(node),
230                    TypeKind::Interface,
231                    &mut aggregates,
232                )?,
233                cst::Definition::UnionTypeExtension(node) => self.aggregate_extension(
234                    TypeExtensionNode::Union(node),
235                    TypeKind::Union,
236                    &mut aggregates,
237                )?,
238                cst::Definition::EnumTypeExtension(node) => self.aggregate_extension(
239                    TypeExtensionNode::Enum(node),
240                    TypeKind::Enum,
241                    &mut aggregates,
242                )?,
243                cst::Definition::InputObjectTypeExtension(node) => self.aggregate_extension(
244                    TypeExtensionNode::InputObject(node),
245                    TypeKind::InputObject,
246                    &mut aggregates,
247                )?,
248                _ => {}
249            }
250        }
251
252        let mut types = Vec::new();
253        for agg in aggregates.values() {
254            types.push(self.build_type_from_aggregate(agg)?);
255        }
256
257        Ok(WesleyIR {
258            version: "1.0.0".to_string(),
259            metadata: None,
260            types,
261        })
262    }
263
264    fn aggregate_definition(
265        &self,
266        node: TypeDefinitionNode,
267        kind: TypeKind,
268        aggregates: &mut BTreeMap<String, TypeAggregate>,
269    ) -> Result<(), WesleyError> {
270        let name = type_node_name(node.name(), "Type definition missing name")?;
271        let agg = aggregate_for(aggregates, name, kind)?;
272        agg.definitions.push(node);
273        Ok(())
274    }
275
276    fn aggregate_extension(
277        &self,
278        node: TypeExtensionNode,
279        kind: TypeKind,
280        aggregates: &mut BTreeMap<String, TypeAggregate>,
281    ) -> Result<(), WesleyError> {
282        let name = type_node_name(node.name(), "Type extension missing name")?;
283        let agg = aggregate_for(aggregates, name, kind)?;
284        agg.extensions.push(node);
285        Ok(())
286    }
287
288    fn build_type_from_aggregate(
289        &self,
290        agg: &TypeAggregate,
291    ) -> Result<TypeDefinition, WesleyError> {
292        let mut directives = IndexMap::new();
293        let mut implements = Vec::new();
294        let mut fields = Vec::new();
295        let mut enum_values = Vec::new();
296        let mut union_members = Vec::new();
297        let mut description = None;
298
299        for def in &agg.definitions {
300            if description.is_none() {
301                description = description_from(def.description());
302            }
303            self.merge_definition(
304                def,
305                &mut directives,
306                &mut implements,
307                &mut fields,
308                &mut enum_values,
309                &mut union_members,
310            )?;
311        }
312
313        for ext in &agg.extensions {
314            self.merge_extension(
315                ext,
316                &mut directives,
317                &mut implements,
318                &mut fields,
319                &mut enum_values,
320                &mut union_members,
321            )?;
322        }
323
324        Ok(TypeDefinition {
325            name: agg.name.clone(),
326            kind: agg.kind,
327            description,
328            directives,
329            implements,
330            fields,
331            enum_values,
332            union_members,
333        })
334    }
335
336    fn merge_definition(
337        &self,
338        def: &TypeDefinitionNode,
339        directives: &mut IndexMap<String, serde_json::Value>,
340        implements: &mut Vec<String>,
341        fields: &mut Vec<Field>,
342        enum_values: &mut Vec<String>,
343        union_members: &mut Vec<String>,
344    ) -> Result<(), WesleyError> {
345        match def {
346            TypeDefinitionNode::Scalar(node) => {
347                if let Some(dirs) = node.directives() {
348                    self.extract_directives(dirs, directives)?;
349                }
350            }
351            TypeDefinitionNode::Object(node) => {
352                if let Some(interfaces) = node.implements_interfaces() {
353                    collect_implements(interfaces, implements)?;
354                }
355                if let Some(dirs) = node.directives() {
356                    self.extract_directives(dirs, directives)?;
357                }
358                if let Some(fields_def) = node.fields_definition() {
359                    self.collect_fields(fields_def, fields)?;
360                }
361            }
362            TypeDefinitionNode::Interface(node) => {
363                if let Some(interfaces) = node.implements_interfaces() {
364                    collect_implements(interfaces, implements)?;
365                }
366                if let Some(dirs) = node.directives() {
367                    self.extract_directives(dirs, directives)?;
368                }
369                if let Some(fields_def) = node.fields_definition() {
370                    self.collect_fields(fields_def, fields)?;
371                }
372            }
373            TypeDefinitionNode::Union(node) => {
374                if let Some(dirs) = node.directives() {
375                    self.extract_directives(dirs, directives)?;
376                }
377                if let Some(member_types) = node.union_member_types() {
378                    collect_union_members(member_types, union_members)?;
379                }
380            }
381            TypeDefinitionNode::Enum(node) => {
382                if let Some(dirs) = node.directives() {
383                    self.extract_directives(dirs, directives)?;
384                }
385                if let Some(values_def) = node.enum_values_definition() {
386                    collect_enum_values(values_def, enum_values)?;
387                }
388            }
389            TypeDefinitionNode::InputObject(node) => {
390                if let Some(dirs) = node.directives() {
391                    self.extract_directives(dirs, directives)?;
392                }
393                if let Some(fields_def) = node.input_fields_definition() {
394                    self.collect_input_fields(fields_def, fields)?;
395                }
396            }
397        }
398
399        Ok(())
400    }
401
402    fn merge_extension(
403        &self,
404        ext: &TypeExtensionNode,
405        directives: &mut IndexMap<String, serde_json::Value>,
406        implements: &mut Vec<String>,
407        fields: &mut Vec<Field>,
408        enum_values: &mut Vec<String>,
409        union_members: &mut Vec<String>,
410    ) -> Result<(), WesleyError> {
411        match ext {
412            TypeExtensionNode::Scalar(node) => {
413                if let Some(dirs) = node.directives() {
414                    self.extract_directives(dirs, directives)?;
415                }
416            }
417            TypeExtensionNode::Object(node) => {
418                if let Some(interfaces) = node.implements_interfaces() {
419                    collect_implements(interfaces, implements)?;
420                }
421                if let Some(dirs) = node.directives() {
422                    self.extract_directives(dirs, directives)?;
423                }
424                if let Some(fields_def) = node.fields_definition() {
425                    self.collect_fields(fields_def, fields)?;
426                }
427            }
428            TypeExtensionNode::Interface(node) => {
429                if let Some(interfaces) = node.implements_interfaces() {
430                    collect_implements(interfaces, implements)?;
431                }
432                if let Some(dirs) = node.directives() {
433                    self.extract_directives(dirs, directives)?;
434                }
435                if let Some(fields_def) = node.fields_definition() {
436                    self.collect_fields(fields_def, fields)?;
437                }
438            }
439            TypeExtensionNode::Union(node) => {
440                if let Some(dirs) = node.directives() {
441                    self.extract_directives(dirs, directives)?;
442                }
443                if let Some(member_types) = node.union_member_types() {
444                    collect_union_members(member_types, union_members)?;
445                }
446            }
447            TypeExtensionNode::Enum(node) => {
448                if let Some(dirs) = node.directives() {
449                    self.extract_directives(dirs, directives)?;
450                }
451                if let Some(values_def) = node.enum_values_definition() {
452                    collect_enum_values(values_def, enum_values)?;
453                }
454            }
455            TypeExtensionNode::InputObject(node) => {
456                if let Some(dirs) = node.directives() {
457                    self.extract_directives(dirs, directives)?;
458                }
459                if let Some(fields_def) = node.input_fields_definition() {
460                    self.collect_input_fields(fields_def, fields)?;
461                }
462            }
463        }
464
465        Ok(())
466    }
467
468    fn extract_directives(
469        &self,
470        dirs: cst::Directives,
471        map: &mut IndexMap<String, serde_json::Value>,
472    ) -> Result<(), WesleyError> {
473        for dir in dirs.directives() {
474            let dir_name = dir
475                .name()
476                .ok_or(WesleyError::LoweringError {
477                    message: "Directive missing name".to_string(),
478                    area: "directive".to_string(),
479                })?
480                .text()
481                .to_string();
482
483            let mut args_map = serde_json::Map::new();
484            if let Some(args) = dir.arguments() {
485                for arg in args.arguments() {
486                    let arg_name = arg.name().map(|n| n.text().to_string()).unwrap_or_default();
487                    if let Some(val) = arg.value() {
488                        args_map.insert(arg_name, directive_value_to_json(val)?);
489                    }
490                }
491            }
492
493            let val = if args_map.is_empty() {
494                serde_json::Value::Bool(true)
495            } else {
496                serde_json::Value::Object(args_map)
497            };
498
499            map.insert(dir_name, val);
500        }
501        Ok(())
502    }
503
504    fn collect_fields(
505        &self,
506        fields_def: cst::FieldsDefinition,
507        fields: &mut Vec<Field>,
508    ) -> Result<(), WesleyError> {
509        for field_def in fields_def.field_definitions() {
510            fields.push(self.build_field(field_def)?);
511        }
512
513        Ok(())
514    }
515
516    fn collect_input_fields(
517        &self,
518        fields_def: cst::InputFieldsDefinition,
519        fields: &mut Vec<Field>,
520    ) -> Result<(), WesleyError> {
521        for field_def in fields_def.input_value_definitions() {
522            fields.push(self.build_input_field(field_def)?);
523        }
524
525        Ok(())
526    }
527
528    fn build_field(&self, field_def: cst::FieldDefinition) -> Result<Field, WesleyError> {
529        let name = field_def
530            .name()
531            .ok_or(WesleyError::LoweringError {
532                message: "Field missing name".to_string(),
533                area: "field".to_string(),
534            })?
535            .text()
536            .to_string();
537
538        let type_node = field_def.ty().ok_or(WesleyError::LoweringError {
539            message: "Field missing type".to_string(),
540            area: "field".to_string(),
541        })?;
542
543        let mut field_directives = IndexMap::new();
544        if let Some(dirs) = field_def.directives() {
545            self.extract_directives(dirs, &mut field_directives)?;
546        }
547
548        Ok(Field {
549            name,
550            r#type: self.build_type_reference(type_node)?,
551            arguments: field_arguments_from_definition(field_def.arguments_definition())?,
552            directives: field_directives,
553            description: description_from(field_def.description()),
554        })
555    }
556
557    fn build_input_field(
558        &self,
559        field_def: cst::InputValueDefinition,
560    ) -> Result<Field, WesleyError> {
561        let name = field_def
562            .name()
563            .ok_or(WesleyError::LoweringError {
564                message: "Input field missing name".to_string(),
565                area: "field".to_string(),
566            })?
567            .text()
568            .to_string();
569
570        let type_node = field_def.ty().ok_or(WesleyError::LoweringError {
571            message: "Input field missing type".to_string(),
572            area: "field".to_string(),
573        })?;
574
575        let mut field_directives = IndexMap::new();
576        if let Some(dirs) = field_def.directives() {
577            self.extract_directives(dirs, &mut field_directives)?;
578        }
579
580        Ok(Field {
581            name,
582            r#type: self.build_type_reference(type_node)?,
583            arguments: Vec::new(),
584            directives: field_directives,
585            description: description_from(field_def.description()),
586        })
587    }
588
589    fn build_type_reference(&self, type_node: cst::Type) -> Result<TypeReference, WesleyError> {
590        type_reference_from_type(type_node, true)
591    }
592}
593
594fn aggregate_for(
595    aggregates: &mut BTreeMap<String, TypeAggregate>,
596    name: String,
597    kind: TypeKind,
598) -> Result<&mut TypeAggregate, WesleyError> {
599    use std::collections::btree_map::Entry;
600
601    match aggregates.entry(name.clone()) {
602        Entry::Vacant(entry) => Ok(entry.insert(TypeAggregate {
603            name,
604            kind,
605            definitions: Vec::new(),
606            extensions: Vec::new(),
607        })),
608        Entry::Occupied(entry) => {
609            let aggregate = entry.into_mut();
610            if aggregate.kind != kind {
611                return Err(lowering_error_value(
612                    "type",
613                    format!(
614                        "Type '{}' is declared as both {:?} and {:?}",
615                        aggregate.name, aggregate.kind, kind
616                    ),
617                ));
618            }
619            Ok(aggregate)
620        }
621    }
622}
623
624fn type_node_name(name: Option<cst::Name>, message: &str) -> Result<String, WesleyError> {
625    name.map(|name| name.text().to_string())
626        .ok_or_else(|| lowering_error_value("type", message.to_string()))
627}
628
629fn description_from(description: Option<cst::Description>) -> Option<String> {
630    description
631        .and_then(|description| description.string_value())
632        .map(String::from)
633}
634
635fn collect_implements(
636    interfaces: cst::ImplementsInterfaces,
637    implements: &mut Vec<String>,
638) -> Result<(), WesleyError> {
639    for named_type in interfaces.named_types() {
640        let name = named_type_name_for_lowering(named_type, "Implemented interface missing name")?;
641        push_unique(implements, name);
642    }
643
644    Ok(())
645}
646
647fn collect_union_members(
648    member_types: cst::UnionMemberTypes,
649    union_members: &mut Vec<String>,
650) -> Result<(), WesleyError> {
651    for named_type in member_types.named_types() {
652        let name = named_type_name_for_lowering(named_type, "Union member missing name")?;
653        push_unique(union_members, name);
654    }
655
656    Ok(())
657}
658
659fn collect_enum_values(
660    values_def: cst::EnumValuesDefinition,
661    enum_values: &mut Vec<String>,
662) -> Result<(), WesleyError> {
663    for value_def in values_def.enum_value_definitions() {
664        let name = value_def
665            .enum_value()
666            .and_then(|enum_value| enum_value.name())
667            .map(|name| name.text().to_string())
668            .ok_or_else(|| lowering_error_value("enum", "Enum value missing name".to_string()))?;
669        push_unique(enum_values, name);
670    }
671
672    Ok(())
673}
674
675fn named_type_name_for_lowering(
676    named_type: cst::NamedType,
677    message: &str,
678) -> Result<String, WesleyError> {
679    named_type
680        .name()
681        .map(|name| name.text().to_string())
682        .ok_or_else(|| lowering_error_value("type", message.to_string()))
683}
684
685#[derive(Debug)]
686struct TypeReferenceShape {
687    base: String,
688    nullable: bool,
689    list_wrappers: Vec<TypeListWrapper>,
690    leaf_nullable: bool,
691}
692
693fn type_reference_from_type(
694    type_node: cst::Type,
695    nullable: bool,
696) -> Result<TypeReference, WesleyError> {
697    let shape = type_reference_shape_from_type(type_node, nullable)?;
698    let is_list = !shape.list_wrappers.is_empty();
699    let list_item_nullable = if is_list {
700        Some(
701            shape
702                .list_wrappers
703                .get(1)
704                .map(|wrapper| wrapper.nullable)
705                .unwrap_or(shape.leaf_nullable),
706        )
707    } else {
708        None
709    };
710    let has_nested_lists = shape.list_wrappers.len() > 1;
711
712    Ok(TypeReference {
713        base: shape.base,
714        nullable: shape.nullable,
715        is_list,
716        list_item_nullable,
717        list_wrappers: if has_nested_lists {
718            shape.list_wrappers
719        } else {
720            Vec::new()
721        },
722        leaf_nullable: if has_nested_lists {
723            Some(shape.leaf_nullable)
724        } else {
725            None
726        },
727    })
728}
729
730fn type_reference_shape_from_type(
731    type_node: cst::Type,
732    nullable: bool,
733) -> Result<TypeReferenceShape, WesleyError> {
734    match type_node {
735        cst::Type::NamedType(named_type) => Ok(TypeReferenceShape {
736            base: named_type_name_for_lowering(named_type, "Type reference missing name")?,
737            nullable,
738            list_wrappers: Vec::new(),
739            leaf_nullable: nullable,
740        }),
741        cst::Type::ListType(list_type) => {
742            let item_type = list_type.ty().ok_or_else(|| {
743                lowering_error_value("type", "List type missing item type".to_string())
744            })?;
745            let item_ref = type_reference_shape_from_type(item_type, true)?;
746            let mut list_wrappers = vec![TypeListWrapper { nullable }];
747            list_wrappers.extend(item_ref.list_wrappers);
748
749            Ok(TypeReferenceShape {
750                base: item_ref.base,
751                nullable,
752                list_wrappers,
753                leaf_nullable: item_ref.leaf_nullable,
754            })
755        }
756        cst::Type::NonNullType(non_null_type) => {
757            if let Some(named_type) = non_null_type.named_type() {
758                Ok(TypeReferenceShape {
759                    base: named_type_name_for_lowering(
760                        named_type,
761                        "Non-null type reference missing name",
762                    )?,
763                    nullable: false,
764                    list_wrappers: Vec::new(),
765                    leaf_nullable: false,
766                })
767            } else if let Some(list_type) = non_null_type.list_type() {
768                let item_type = list_type.ty().ok_or_else(|| {
769                    lowering_error_value("type", "Non-null list type missing item type".to_string())
770                })?;
771                let item_ref = type_reference_shape_from_type(item_type, true)?;
772                let mut list_wrappers = vec![TypeListWrapper { nullable: false }];
773                list_wrappers.extend(item_ref.list_wrappers);
774
775                Ok(TypeReferenceShape {
776                    base: item_ref.base,
777                    nullable: false,
778                    list_wrappers,
779                    leaf_nullable: item_ref.leaf_nullable,
780                })
781            } else {
782                Err(lowering_error_value(
783                    "type",
784                    "Non-null type missing inner type".to_string(),
785                ))
786            }
787        }
788    }
789}
790
791fn field_arguments_from_definition(
792    arguments_definition: Option<cst::ArgumentsDefinition>,
793) -> Result<Vec<FieldArgument>, WesleyError> {
794    let Some(arguments_definition) = arguments_definition else {
795        return Ok(Vec::new());
796    };
797
798    arguments_definition
799        .input_value_definitions()
800        .map(field_argument_from_input_value)
801        .collect()
802}
803
804fn field_argument_from_input_value(
805    input_value: cst::InputValueDefinition,
806) -> Result<FieldArgument, WesleyError> {
807    let name = input_value
808        .name()
809        .map(|name| name.text().to_string())
810        .ok_or_else(|| {
811            lowering_error_value("field argument", "Field argument missing name".into())
812        })?;
813    let type_node = input_value.ty().ok_or_else(|| {
814        lowering_error_value(
815            "field argument",
816            format!("Field argument '{name}' missing type"),
817        )
818    })?;
819    let default_value = input_value
820        .default_value()
821        .and_then(|default_value| default_value.value())
822        .map(directive_value_to_json)
823        .transpose()?;
824
825    let mut directives = IndexMap::new();
826    if let Some(dirs) = input_value.directives() {
827        ApolloLoweringAdapter::new(0).extract_directives(dirs, &mut directives)?;
828    }
829
830    Ok(FieldArgument {
831        name,
832        description: description_from(input_value.description()),
833        r#type: type_reference_from_type(type_node, true)?,
834        default_value,
835        directives,
836    })
837}
838
839fn directive_value_to_json(value: cst::Value) -> Result<serde_json::Value, WesleyError> {
840    match value {
841        cst::Value::StringValue(value) => Ok(serde_json::Value::String(String::from(value))),
842        cst::Value::FloatValue(value) => {
843            let raw = value
844                .float_token()
845                .map(|token| token.text().to_string())
846                .unwrap_or_default();
847            let parsed = raw.parse::<f64>().map_err(|err| {
848                lowering_error_value(
849                    "directive",
850                    format!("Invalid float directive argument '{raw}': {err}"),
851                )
852            })?;
853            serde_json::Number::from_f64(parsed)
854                .map(serde_json::Value::Number)
855                .ok_or_else(|| {
856                    lowering_error_value(
857                        "directive",
858                        format!("Invalid finite float directive argument '{raw}'"),
859                    )
860                })
861        }
862        cst::Value::IntValue(value) => {
863            let raw = value
864                .int_token()
865                .map(|token| token.text().to_string())
866                .unwrap_or_default();
867            raw.parse::<i64>()
868                .map(|parsed| serde_json::Value::Number(parsed.into()))
869                .map_err(|err| {
870                    lowering_error_value(
871                        "directive",
872                        format!("Invalid integer directive argument '{raw}': {err}"),
873                    )
874                })
875        }
876        cst::Value::BooleanValue(value) => Ok(serde_json::Value::Bool(
877            value.true_token().is_some() && value.false_token().is_none(),
878        )),
879        cst::Value::NullValue(_) => Ok(serde_json::Value::Null),
880        cst::Value::EnumValue(value) => {
881            let name = value
882                .name()
883                .map(|name| name.text().to_string())
884                .ok_or_else(|| {
885                    lowering_error_value(
886                        "directive",
887                        "Enum directive value missing name".to_string(),
888                    )
889                })?;
890            Ok(serde_json::Value::String(name))
891        }
892        cst::Value::ListValue(list) => {
893            let mut values = Vec::new();
894            for value in list.values() {
895                values.push(directive_value_to_json(value)?);
896            }
897            Ok(serde_json::Value::Array(values))
898        }
899        cst::Value::ObjectValue(object) => {
900            let mut map = serde_json::Map::new();
901            for field in object.object_fields() {
902                let name = field
903                    .name()
904                    .map(|name| name.text().to_string())
905                    .ok_or_else(|| {
906                        lowering_error_value(
907                            "directive",
908                            "Object directive value field missing name".to_string(),
909                        )
910                    })?;
911                let value = field.value().ok_or_else(|| {
912                    lowering_error_value(
913                        "directive",
914                        format!("Object directive value field '{name}' missing value"),
915                    )
916                })?;
917                map.insert(name, directive_value_to_json(value)?);
918            }
919            Ok(serde_json::Value::Object(map))
920        }
921        cst::Value::Variable(variable) => Err(lowering_error_value(
922            "directive",
923            format!(
924                "Directive argument values cannot be variables: {}",
925                variable.text()
926            ),
927        )),
928    }
929}
930
931fn lowering_error_value(area: &str, message: String) -> WesleyError {
932    WesleyError::LoweringError {
933        message,
934        area: area.to_string(),
935    }
936}
937
938/// Resolves response-path field selections from a single GraphQL operation.
939pub fn resolve_operation_selections(operation_sdl: &str) -> Result<Vec<String>, WesleyError> {
940    let parsed = parse_operation_document(operation_sdl)?;
941    let op = parsed.only_operation()?;
942    let mut selections = Vec::new();
943
944    if let Some(selection_set) = op.selection_set() {
945        collect_selection_paths(
946            &selection_set,
947            "",
948            &parsed.fragments,
949            &mut Vec::new(),
950            &mut selections,
951        )?;
952    }
953
954    Ok(selections)
955}
956
957/// Resolves schema-coordinate field selections from a single GraphQL operation.
958pub fn resolve_operation_selections_with_schema(
959    schema_sdl: &str,
960    operation_sdl: &str,
961) -> Result<Vec<String>, WesleyError> {
962    let adapter = ApolloLoweringAdapter::new(0);
963    let ir = adapter.parse_and_lower(schema_sdl)?;
964    let root_types = extract_root_types(schema_sdl)?;
965
966    let parsed = parse_operation_document(operation_sdl)?;
967    let op = parsed.only_operation()?;
968    let mut selections = Vec::new();
969
970    if let Some(selection_set) = op.selection_set() {
971        let root_type = root_types.root_for_operation(op)?;
972        let schema = SchemaIndex::new(&ir);
973        collect_schema_coordinates(
974            &selection_set,
975            root_type,
976            &schema,
977            &parsed.fragments,
978            &mut Vec::new(),
979            &mut selections,
980        )?;
981    }
982
983    Ok(selections)
984}
985
986/// Extracts arguments from operation directives with the requested directive name.
987pub fn extract_operation_directive_args(
988    operation_sdl: &str,
989    directive_name: &str,
990) -> Result<Vec<OperationDirectiveArgs>, WesleyError> {
991    let parsed = parse_operation_document(operation_sdl)?;
992    let op = parsed.only_operation()?;
993    let mut directives = Vec::new();
994
995    let Some(operation_directives) = op.directives() else {
996        return Ok(directives);
997    };
998
999    for directive in operation_directives.directives() {
1000        let name = required_name(directive.name(), "Directive missing name")?;
1001        if name != directive_name {
1002            continue;
1003        }
1004
1005        directives.push(OperationDirectiveArgs {
1006            directive_name: name,
1007            arguments: extract_directive_arguments(directive.arguments())?,
1008        });
1009    }
1010
1011    Ok(directives)
1012}
1013
1014fn extract_directive_arguments(
1015    arguments: Option<cst::Arguments>,
1016) -> Result<IndexMap<String, serde_json::Value>, WesleyError> {
1017    let mut values = IndexMap::new();
1018
1019    let Some(arguments) = arguments else {
1020        return Ok(values);
1021    };
1022
1023    for argument in arguments.arguments() {
1024        let name = required_name(argument.name(), "Directive argument missing name")?;
1025        let value = argument.value().ok_or_else(|| {
1026            operation_error_value(format!("Directive argument '{name}' missing value"))
1027        })?;
1028        values.insert(name, directive_value_to_json(value)?);
1029    }
1030
1031    Ok(values)
1032}
1033
1034struct ParsedOperationDocument {
1035    operations: Vec<cst::OperationDefinition>,
1036    fragments: BTreeMap<String, cst::FragmentDefinition>,
1037}
1038
1039impl ParsedOperationDocument {
1040    fn only_operation(&self) -> Result<&cst::OperationDefinition, WesleyError> {
1041        match self.operations.len() {
1042            0 => operation_error("No GraphQL operation found".to_string()),
1043            1 => Ok(&self.operations[0]),
1044            count => operation_error(format!(
1045                "Expected exactly one GraphQL operation, found {count}"
1046            )),
1047        }
1048    }
1049}
1050
1051fn parse_operation_document(operation_sdl: &str) -> Result<ParsedOperationDocument, WesleyError> {
1052    let parser = Parser::new(operation_sdl);
1053    let cst = parser.parse();
1054
1055    let errors = cst.errors().collect::<Vec<_>>();
1056    if !errors.is_empty() {
1057        let err = &errors[0];
1058        return Err(WesleyError::ParseError {
1059            message: err.message().to_string(),
1060            line: None,
1061            column: None,
1062        });
1063    }
1064
1065    let doc = cst.document();
1066    let mut operations = Vec::new();
1067    let mut fragments = BTreeMap::new();
1068
1069    for def in doc.definitions() {
1070        match def {
1071            cst::Definition::OperationDefinition(op) => {
1072                operations.push(op);
1073            }
1074            cst::Definition::FragmentDefinition(fragment) => {
1075                let name = fragment_name(&fragment)?;
1076                if fragments.insert(name.clone(), fragment).is_some() {
1077                    return operation_error(format!("Duplicate fragment definition '{name}'"));
1078                }
1079            }
1080            _ => {}
1081        }
1082    }
1083
1084    Ok(ParsedOperationDocument {
1085        operations,
1086        fragments,
1087    })
1088}
1089
1090fn collect_selection_paths(
1091    selection_set: &cst::SelectionSet,
1092    prefix: &str,
1093    fragments: &BTreeMap<String, cst::FragmentDefinition>,
1094    active_fragments: &mut Vec<String>,
1095    actual_selections: &mut Vec<String>,
1096) -> Result<(), WesleyError> {
1097    for selection in selection_set.selections() {
1098        match selection {
1099            cst::Selection::Field(field) => {
1100                let field_name = required_name(field.name(), "Field selection missing name")?;
1101                let path = if prefix.is_empty() {
1102                    field_name
1103                } else {
1104                    format!("{prefix}.{field_name}")
1105                };
1106
1107                push_unique(actual_selections, path.clone());
1108
1109                if let Some(nested_selection_set) = field.selection_set() {
1110                    collect_selection_paths(
1111                        &nested_selection_set,
1112                        &path,
1113                        fragments,
1114                        active_fragments,
1115                        actual_selections,
1116                    )?;
1117                }
1118            }
1119            cst::Selection::FragmentSpread(spread) => {
1120                let name = spread
1121                    .fragment_name()
1122                    .and_then(|fragment_name| fragment_name.name())
1123                    .map(|name| name.text().to_string())
1124                    .ok_or_else(|| {
1125                        operation_error_value("Fragment spread missing name".to_string())
1126                    })?;
1127
1128                if active_fragments.contains(&name) {
1129                    return operation_error(format!(
1130                        "Cyclic fragment spread detected for fragment '{name}'"
1131                    ));
1132                }
1133
1134                let fragment = fragments.get(&name).ok_or_else(|| {
1135                    operation_error_value(format!("Unknown fragment spread '{name}'"))
1136                })?;
1137
1138                active_fragments.push(name);
1139                if let Some(fragment_selection_set) = fragment.selection_set() {
1140                    collect_selection_paths(
1141                        &fragment_selection_set,
1142                        prefix,
1143                        fragments,
1144                        active_fragments,
1145                        actual_selections,
1146                    )?;
1147                }
1148                active_fragments.pop();
1149            }
1150            cst::Selection::InlineFragment(fragment) => {
1151                if let Some(inline_selection_set) = fragment.selection_set() {
1152                    collect_selection_paths(
1153                        &inline_selection_set,
1154                        prefix,
1155                        fragments,
1156                        active_fragments,
1157                        actual_selections,
1158                    )?;
1159                }
1160            }
1161        }
1162    }
1163
1164    Ok(())
1165}
1166
1167fn collect_schema_coordinates(
1168    selection_set: &cst::SelectionSet,
1169    parent_type: &str,
1170    schema: &SchemaIndex<'_>,
1171    fragments: &BTreeMap<String, cst::FragmentDefinition>,
1172    active_fragments: &mut Vec<String>,
1173    actual_selections: &mut Vec<String>,
1174) -> Result<(), WesleyError> {
1175    for selection in selection_set.selections() {
1176        match selection {
1177            cst::Selection::Field(field) => {
1178                let field_name = required_name(field.name(), "Field selection missing name")?;
1179                let schema_field = schema.field(parent_type, &field_name)?;
1180                let coordinate = format!("{parent_type}.{field_name}");
1181                push_unique(actual_selections, coordinate);
1182
1183                if let Some(nested_selection_set) = field.selection_set() {
1184                    let nested_parent = schema_field.r#type.base.as_str();
1185                    schema.require_type(nested_parent)?;
1186                    collect_schema_coordinates(
1187                        &nested_selection_set,
1188                        nested_parent,
1189                        schema,
1190                        fragments,
1191                        active_fragments,
1192                        actual_selections,
1193                    )?;
1194                }
1195            }
1196            cst::Selection::FragmentSpread(spread) => {
1197                let name = spread
1198                    .fragment_name()
1199                    .and_then(|fragment_name| fragment_name.name())
1200                    .map(|name| name.text().to_string())
1201                    .ok_or_else(|| {
1202                        operation_error_value("Fragment spread missing name".to_string())
1203                    })?;
1204
1205                if active_fragments.contains(&name) {
1206                    return operation_error(format!(
1207                        "Cyclic fragment spread detected for fragment '{name}'"
1208                    ));
1209                }
1210
1211                let fragment = fragments.get(&name).ok_or_else(|| {
1212                    operation_error_value(format!("Unknown fragment spread '{name}'"))
1213                })?;
1214                let fragment_parent = fragment_type_condition(fragment)?;
1215                schema.require_type(&fragment_parent)?;
1216
1217                active_fragments.push(name);
1218                if let Some(fragment_selection_set) = fragment.selection_set() {
1219                    collect_schema_coordinates(
1220                        &fragment_selection_set,
1221                        &fragment_parent,
1222                        schema,
1223                        fragments,
1224                        active_fragments,
1225                        actual_selections,
1226                    )?;
1227                }
1228                active_fragments.pop();
1229            }
1230            cst::Selection::InlineFragment(fragment) => {
1231                let inline_parent = if let Some(type_condition) = fragment.type_condition() {
1232                    named_type_name(type_condition.named_type(), "Inline fragment missing type")?
1233                } else {
1234                    parent_type.to_string()
1235                };
1236                schema.require_type(&inline_parent)?;
1237
1238                if let Some(inline_selection_set) = fragment.selection_set() {
1239                    collect_schema_coordinates(
1240                        &inline_selection_set,
1241                        &inline_parent,
1242                        schema,
1243                        fragments,
1244                        active_fragments,
1245                        actual_selections,
1246                    )?;
1247                }
1248            }
1249        }
1250    }
1251
1252    Ok(())
1253}
1254
1255struct SchemaIndex<'a> {
1256    types: HashMap<&'a str, &'a TypeDefinition>,
1257}
1258
1259impl<'a> SchemaIndex<'a> {
1260    fn new(ir: &'a WesleyIR) -> Self {
1261        let types = ir
1262            .types
1263            .iter()
1264            .map(|type_def| (type_def.name.as_str(), type_def))
1265            .collect::<HashMap<_, _>>();
1266        Self { types }
1267    }
1268
1269    fn require_type(&self, name: &str) -> Result<&'a TypeDefinition, WesleyError> {
1270        self.types
1271            .get(name)
1272            .copied()
1273            .ok_or_else(|| operation_error_value(format!("Unknown selection parent type '{name}'")))
1274    }
1275
1276    fn field(&self, parent_type: &str, field_name: &str) -> Result<&'a Field, WesleyError> {
1277        let type_def = self.require_type(parent_type)?;
1278        type_def
1279            .fields
1280            .iter()
1281            .find(|field| field.name == field_name)
1282            .ok_or_else(|| {
1283                operation_error_value(format!(
1284                    "Type '{parent_type}' does not define selected field '{field_name}'"
1285                ))
1286            })
1287    }
1288}
1289
1290struct RootTypes {
1291    query: String,
1292    mutation: String,
1293    subscription: String,
1294}
1295
1296impl Default for RootTypes {
1297    fn default() -> Self {
1298        Self {
1299            query: "Query".to_string(),
1300            mutation: "Mutation".to_string(),
1301            subscription: "Subscription".to_string(),
1302        }
1303    }
1304}
1305
1306impl RootTypes {
1307    fn operation_types_for_type(&self, type_name: &str) -> Vec<OperationType> {
1308        let mut operation_types = Vec::new();
1309
1310        if self.query == type_name {
1311            operation_types.push(OperationType::Query);
1312        }
1313        if self.mutation == type_name {
1314            operation_types.push(OperationType::Mutation);
1315        }
1316        if self.subscription == type_name {
1317            operation_types.push(OperationType::Subscription);
1318        }
1319
1320        operation_types
1321    }
1322
1323    fn root_for_operation(&self, op: &cst::OperationDefinition) -> Result<&str, WesleyError> {
1324        let Some(operation_type) = op.operation_type() else {
1325            return Ok(self.query.as_str());
1326        };
1327
1328        if operation_type.query_token().is_some() {
1329            Ok(self.query.as_str())
1330        } else if operation_type.mutation_token().is_some() {
1331            Ok(self.mutation.as_str())
1332        } else if operation_type.subscription_token().is_some() {
1333            Ok(self.subscription.as_str())
1334        } else {
1335            operation_error("Unknown GraphQL operation type".to_string())
1336        }
1337    }
1338}
1339
1340fn extract_root_types(schema_sdl: &str) -> Result<RootTypes, WesleyError> {
1341    let parser = Parser::new(schema_sdl);
1342    let cst = parser.parse();
1343
1344    let errors = cst.errors().collect::<Vec<_>>();
1345    if !errors.is_empty() {
1346        let err = &errors[0];
1347        return Err(WesleyError::ParseError {
1348            message: err.message().to_string(),
1349            line: None,
1350            column: None,
1351        });
1352    }
1353
1354    let mut root_types = RootTypes::default();
1355
1356    for def in cst.document().definitions() {
1357        match def {
1358            cst::Definition::SchemaDefinition(schema) => {
1359                update_root_types(schema.root_operation_type_definitions(), &mut root_types)?;
1360            }
1361            cst::Definition::SchemaExtension(schema) => {
1362                update_root_types(schema.root_operation_type_definitions(), &mut root_types)?;
1363            }
1364            _ => {}
1365        }
1366    }
1367
1368    Ok(root_types)
1369}
1370
1371fn collect_schema_operations_from_object(
1372    name: Option<cst::Name>,
1373    fields_definition: Option<cst::FieldsDefinition>,
1374    root_types: &RootTypes,
1375    operations: &mut Vec<SchemaOperation>,
1376) -> Result<(), WesleyError> {
1377    let type_name = type_node_name(name, "Object type missing name")?;
1378    let operation_types = root_types.operation_types_for_type(&type_name);
1379    if operation_types.is_empty() {
1380        return Ok(());
1381    }
1382
1383    let Some(fields_definition) = fields_definition else {
1384        return Ok(());
1385    };
1386
1387    for field_def in fields_definition.field_definitions() {
1388        for operation_type in &operation_types {
1389            operations.push(schema_operation_from_field(
1390                *operation_type,
1391                &type_name,
1392                field_def.clone(),
1393            )?);
1394        }
1395    }
1396
1397    Ok(())
1398}
1399
1400fn schema_operation_from_field(
1401    operation_type: OperationType,
1402    root_type_name: &str,
1403    field_def: cst::FieldDefinition,
1404) -> Result<SchemaOperation, WesleyError> {
1405    let field_name = field_def
1406        .name()
1407        .map(|name| name.text().to_string())
1408        .ok_or_else(|| {
1409            lowering_error_value("schema operation", "Root field missing name".into())
1410        })?;
1411    let result_type = field_def.ty().ok_or_else(|| {
1412        lowering_error_value(
1413            "schema operation",
1414            format!("Root field '{field_name}' missing result type"),
1415        )
1416    })?;
1417
1418    let mut directives = IndexMap::new();
1419    if let Some(dirs) = field_def.directives() {
1420        ApolloLoweringAdapter::new(0).extract_directives(dirs, &mut directives)?;
1421    }
1422
1423    Ok(SchemaOperation {
1424        operation_type,
1425        root_type_name: root_type_name.to_string(),
1426        field_name,
1427        arguments: operation_arguments_from_definition(field_def.arguments_definition())?,
1428        result_type: type_reference_from_type(result_type, true)?,
1429        directives,
1430    })
1431}
1432
1433fn operation_arguments_from_definition(
1434    arguments_definition: Option<cst::ArgumentsDefinition>,
1435) -> Result<Vec<OperationArgument>, WesleyError> {
1436    let Some(arguments_definition) = arguments_definition else {
1437        return Ok(Vec::new());
1438    };
1439
1440    arguments_definition
1441        .input_value_definitions()
1442        .map(operation_argument_from_input_value)
1443        .collect()
1444}
1445
1446fn operation_argument_from_input_value(
1447    input_value: cst::InputValueDefinition,
1448) -> Result<OperationArgument, WesleyError> {
1449    let name = input_value
1450        .name()
1451        .map(|name| name.text().to_string())
1452        .ok_or_else(|| {
1453            lowering_error_value("schema operation", "Operation argument missing name".into())
1454        })?;
1455    let type_node = input_value.ty().ok_or_else(|| {
1456        lowering_error_value(
1457            "schema operation",
1458            format!("Operation argument '{name}' missing type"),
1459        )
1460    })?;
1461    let default_value = input_value
1462        .default_value()
1463        .and_then(|default_value| default_value.value())
1464        .map(directive_value_to_json)
1465        .transpose()?;
1466
1467    let mut directives = IndexMap::new();
1468    if let Some(dirs) = input_value.directives() {
1469        ApolloLoweringAdapter::new(0).extract_directives(dirs, &mut directives)?;
1470    }
1471
1472    Ok(OperationArgument {
1473        name,
1474        r#type: type_reference_from_type(type_node, true)?,
1475        default_value,
1476        directives,
1477    })
1478}
1479
1480fn update_root_types(
1481    root_defs: cst::CstChildren<cst::RootOperationTypeDefinition>,
1482    root_types: &mut RootTypes,
1483) -> Result<(), WesleyError> {
1484    for root_def in root_defs {
1485        let operation_type = root_def.operation_type().ok_or_else(|| {
1486            operation_error_value("Schema root operation missing operation type".to_string())
1487        })?;
1488        let named_type = named_type_name(
1489            root_def.named_type(),
1490            "Schema root operation missing named type",
1491        )?;
1492
1493        if operation_type.query_token().is_some() {
1494            root_types.query = named_type;
1495        } else if operation_type.mutation_token().is_some() {
1496            root_types.mutation = named_type;
1497        } else if operation_type.subscription_token().is_some() {
1498            root_types.subscription = named_type;
1499        }
1500    }
1501
1502    Ok(())
1503}
1504
1505fn push_unique(values: &mut Vec<String>, value: String) {
1506    if !values.contains(&value) {
1507        values.push(value);
1508    }
1509}
1510
1511fn fragment_name(fragment: &cst::FragmentDefinition) -> Result<String, WesleyError> {
1512    fragment
1513        .fragment_name()
1514        .and_then(|fragment_name| fragment_name.name())
1515        .map(|name| name.text().to_string())
1516        .ok_or_else(|| operation_error_value("Fragment definition missing name".to_string()))
1517}
1518
1519fn fragment_type_condition(fragment: &cst::FragmentDefinition) -> Result<String, WesleyError> {
1520    let type_condition = fragment.type_condition().ok_or_else(|| {
1521        operation_error_value("Fragment definition missing type condition".to_string())
1522    })?;
1523    named_type_name(
1524        type_condition.named_type(),
1525        "Fragment definition missing type condition",
1526    )
1527}
1528
1529fn named_type_name(name: Option<cst::NamedType>, message: &str) -> Result<String, WesleyError> {
1530    name.and_then(|named_type| named_type.name())
1531        .map(|name| name.text().to_string())
1532        .ok_or_else(|| operation_error_value(message.to_string()))
1533}
1534
1535fn required_name(name: Option<cst::Name>, message: &str) -> Result<String, WesleyError> {
1536    name.map(|name| name.text().to_string())
1537        .ok_or_else(|| operation_error_value(message.to_string()))
1538}
1539
1540fn operation_error<T>(message: String) -> Result<T, WesleyError> {
1541    Err(operation_error_value(message))
1542}
1543
1544fn operation_error_value(message: String) -> WesleyError {
1545    WesleyError::LoweringError {
1546        message,
1547        area: "operation".to_string(),
1548    }
1549}