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::normalized_sdl::{render_normalized_sdl, DirectiveArgumentTypes};
6use crate::domain::operation::{
7    OperationArgument, OperationDirectiveArgs, OperationType, SchemaOperation,
8};
9use crate::domain::operation_artifact::{
10    CodecField, CodecShape, CompiledOperation, DirectiveRecord, EvidenceKind, Footprint,
11    IdentityRequirement, LawClaimTemplate, OperationArtifact, OperationKind,
12    OperationRegistrationDescriptor, OperationRequirements, OperationRequirementsArtifact,
13    PermissionAction, PermissionRequirement, RootArgumentBinding, SelectionArgumentBinding,
14    OPERATION_REQUIREMENTS_ARTIFACT_CODEC,
15};
16use crate::domain::schema_delta::{diff_schema_ir, SchemaDelta};
17use crate::ports::lowering::LoweringPort;
18use apollo_parser::{cst, Error as ApolloParserError, Parser};
19use async_trait::async_trait;
20use indexmap::IndexMap;
21use std::collections::{BTreeMap, BTreeSet, HashMap};
22
23/// Adapter that uses `apollo-parser` to lower SDL to IR.
24pub struct ApolloLoweringAdapter {
25    _max_retries: usize,
26}
27
28impl ApolloLoweringAdapter {
29    /// Creates a new adapter.
30    pub fn new(max_retries: usize) -> Self {
31        Self {
32            _max_retries: max_retries,
33        }
34    }
35}
36
37#[async_trait]
38impl LoweringPort for ApolloLoweringAdapter {
39    async fn lower_sdl(&self, sdl: &str) -> Result<WesleyIR, WesleyError> {
40        self.parse_and_lower(sdl)
41    }
42}
43
44/// Lowers GraphQL SDL into the Wesley L1 IR using the Apollo parser adapter.
45pub fn lower_schema_sdl(sdl: &str) -> Result<WesleyIR, WesleyError> {
46    ApolloLoweringAdapter::new(0).parse_and_lower(sdl)
47}
48
49/// Renders GraphQL SDL as a deterministic Rust-core semantic view.
50pub fn normalize_schema_sdl(sdl: &str) -> Result<String, WesleyError> {
51    let directive_argument_types = directive_argument_types_from_sdl(sdl)?;
52    let ir = lower_schema_sdl(sdl)?;
53
54    Ok(render_normalized_sdl(&ir, &directive_argument_types))
55}
56
57fn directive_argument_types_from_sdl(sdl: &str) -> Result<DirectiveArgumentTypes, WesleyError> {
58    let parser = Parser::new(sdl);
59    let cst = parser.parse();
60
61    let errors = cst.errors().collect::<Vec<_>>();
62    if !errors.is_empty() {
63        let err = &errors[0];
64        return Err(parse_error_from_apollo(sdl, err));
65    }
66
67    let mut definitions = DirectiveArgumentTypes::new();
68    for definition in cst.document().definitions() {
69        let cst::Definition::DirectiveDefinition(directive) = definition else {
70            continue;
71        };
72
73        let name = directive
74            .name()
75            .map(|name| name.text().to_string())
76            .ok_or_else(|| {
77                lowering_error_value("directive", "Directive definition missing name".to_string())
78            })?;
79        let canonical_name = canonical_core_directive_name(&name)
80            .unwrap_or(name.as_str())
81            .to_string();
82        let mut arguments = BTreeMap::new();
83        if let Some(arguments_definition) = directive.arguments_definition() {
84            for input_value in arguments_definition.input_value_definitions() {
85                let argument_name = input_value
86                    .name()
87                    .map(|name| name.text().to_string())
88                    .ok_or_else(|| {
89                        lowering_error_value(
90                            "directive",
91                            format!("Directive '@{name}' argument missing name"),
92                        )
93                    })?;
94                let argument_type = input_value.ty().ok_or_else(|| {
95                    lowering_error_value(
96                        "directive",
97                        format!("Directive '@{name}' argument '{argument_name}' missing type"),
98                    )
99                })?;
100                arguments.insert(
101                    argument_name,
102                    type_reference_from_type(argument_type, true)?,
103                );
104            }
105        }
106        definitions.insert(canonical_name, arguments);
107    }
108
109    Ok(definitions)
110}
111
112/// Computes the structural L1 delta between two GraphQL SDL documents.
113pub fn diff_schema_sdl(old_sdl: &str, new_sdl: &str) -> Result<SchemaDelta, WesleyError> {
114    let adapter = ApolloLoweringAdapter::new(0);
115    let old_ir = adapter.parse_and_lower(old_sdl)?;
116    let new_ir = adapter.parse_and_lower(new_sdl)?;
117
118    Ok(diff_schema_ir(&old_ir, &new_ir))
119}
120
121/// Lists schema root operations from GraphQL SDL.
122pub fn list_schema_operations_sdl(schema_sdl: &str) -> Result<Vec<SchemaOperation>, WesleyError> {
123    let parser = Parser::new(schema_sdl);
124    let cst = parser.parse();
125
126    let errors = cst.errors().collect::<Vec<_>>();
127    if !errors.is_empty() {
128        let err = &errors[0];
129        return Err(parse_error_from_apollo(schema_sdl, err));
130    }
131
132    let doc = cst.document();
133    let repeatable_directives = repeatable_directive_names_from_document(&doc)?;
134    let mut root_types = RootTypes::default();
135    for def in doc.definitions() {
136        match def {
137            cst::Definition::SchemaDefinition(schema) => {
138                update_root_types(schema.root_operation_type_definitions(), &mut root_types)?;
139            }
140            cst::Definition::SchemaExtension(schema) => {
141                update_root_types(schema.root_operation_type_definitions(), &mut root_types)?;
142            }
143            _ => {}
144        }
145    }
146
147    let mut operations = Vec::new();
148    for def in doc.definitions() {
149        match def {
150            cst::Definition::ObjectTypeDefinition(node) => {
151                collect_schema_operations_from_object(
152                    node.name(),
153                    node.fields_definition(),
154                    &root_types,
155                    &repeatable_directives,
156                    &mut operations,
157                )?;
158            }
159            cst::Definition::ObjectTypeExtension(node) => {
160                collect_schema_operations_from_object(
161                    node.name(),
162                    node.fields_definition(),
163                    &root_types,
164                    &repeatable_directives,
165                    &mut operations,
166                )?;
167            }
168            _ => {}
169        }
170    }
171
172    Ok(operations)
173}
174
175/// Represents the consolidated parts of a single GraphQL Type.
176struct TypeAggregate {
177    name: String,
178    kind: TypeKind,
179    definitions: Vec<TypeDefinitionNode>,
180    extensions: Vec<TypeExtensionNode>,
181}
182
183#[derive(Default)]
184struct TypeBuildAccumulator {
185    directives: IndexMap<String, serde_json::Value>,
186    implements: Vec<String>,
187    fields: Vec<Field>,
188    enum_values: Vec<String>,
189    union_members: Vec<String>,
190}
191
192enum TypeDefinitionNode {
193    Scalar(cst::ScalarTypeDefinition),
194    Object(cst::ObjectTypeDefinition),
195    Interface(cst::InterfaceTypeDefinition),
196    Union(cst::UnionTypeDefinition),
197    Enum(cst::EnumTypeDefinition),
198    InputObject(cst::InputObjectTypeDefinition),
199}
200
201impl TypeDefinitionNode {
202    fn name(&self) -> Option<cst::Name> {
203        match self {
204            TypeDefinitionNode::Scalar(node) => node.name(),
205            TypeDefinitionNode::Object(node) => node.name(),
206            TypeDefinitionNode::Interface(node) => node.name(),
207            TypeDefinitionNode::Union(node) => node.name(),
208            TypeDefinitionNode::Enum(node) => node.name(),
209            TypeDefinitionNode::InputObject(node) => node.name(),
210        }
211    }
212
213    fn description(&self) -> Option<cst::Description> {
214        match self {
215            TypeDefinitionNode::Scalar(node) => node.description(),
216            TypeDefinitionNode::Object(node) => node.description(),
217            TypeDefinitionNode::Interface(node) => node.description(),
218            TypeDefinitionNode::Union(node) => node.description(),
219            TypeDefinitionNode::Enum(node) => node.description(),
220            TypeDefinitionNode::InputObject(node) => node.description(),
221        }
222    }
223}
224
225enum TypeExtensionNode {
226    Scalar(cst::ScalarTypeExtension),
227    Object(cst::ObjectTypeExtension),
228    Interface(cst::InterfaceTypeExtension),
229    Union(cst::UnionTypeExtension),
230    Enum(cst::EnumTypeExtension),
231    InputObject(cst::InputObjectTypeExtension),
232}
233
234impl TypeExtensionNode {
235    fn name(&self) -> Option<cst::Name> {
236        match self {
237            TypeExtensionNode::Scalar(node) => node.name(),
238            TypeExtensionNode::Object(node) => node.name(),
239            TypeExtensionNode::Interface(node) => node.name(),
240            TypeExtensionNode::Union(node) => node.name(),
241            TypeExtensionNode::Enum(node) => node.name(),
242            TypeExtensionNode::InputObject(node) => node.name(),
243        }
244    }
245}
246
247impl ApolloLoweringAdapter {
248    fn parse_and_lower(&self, sdl: &str) -> Result<WesleyIR, WesleyError> {
249        let parser = Parser::new(sdl);
250        let cst = parser.parse();
251
252        let errors = cst.errors().collect::<Vec<_>>();
253        if !errors.is_empty() {
254            let err = &errors[0];
255            return Err(parse_error_from_apollo(sdl, err));
256        }
257
258        let doc = cst.document();
259        let repeatable_directives = repeatable_directive_names_from_document(&doc)?;
260        let mut aggregates: BTreeMap<String, TypeAggregate> = BTreeMap::new();
261
262        for def in doc.definitions() {
263            match def {
264                cst::Definition::ScalarTypeDefinition(node) => self.aggregate_definition(
265                    TypeDefinitionNode::Scalar(node),
266                    TypeKind::Scalar,
267                    &mut aggregates,
268                )?,
269                cst::Definition::ObjectTypeDefinition(node) => self.aggregate_definition(
270                    TypeDefinitionNode::Object(node),
271                    TypeKind::Object,
272                    &mut aggregates,
273                )?,
274                cst::Definition::InterfaceTypeDefinition(node) => self.aggregate_definition(
275                    TypeDefinitionNode::Interface(node),
276                    TypeKind::Interface,
277                    &mut aggregates,
278                )?,
279                cst::Definition::UnionTypeDefinition(node) => self.aggregate_definition(
280                    TypeDefinitionNode::Union(node),
281                    TypeKind::Union,
282                    &mut aggregates,
283                )?,
284                cst::Definition::EnumTypeDefinition(node) => self.aggregate_definition(
285                    TypeDefinitionNode::Enum(node),
286                    TypeKind::Enum,
287                    &mut aggregates,
288                )?,
289                cst::Definition::InputObjectTypeDefinition(node) => self.aggregate_definition(
290                    TypeDefinitionNode::InputObject(node),
291                    TypeKind::InputObject,
292                    &mut aggregates,
293                )?,
294                cst::Definition::ScalarTypeExtension(node) => self.aggregate_extension(
295                    TypeExtensionNode::Scalar(node),
296                    TypeKind::Scalar,
297                    &mut aggregates,
298                )?,
299                cst::Definition::ObjectTypeExtension(node) => self.aggregate_extension(
300                    TypeExtensionNode::Object(node),
301                    TypeKind::Object,
302                    &mut aggregates,
303                )?,
304                cst::Definition::InterfaceTypeExtension(node) => self.aggregate_extension(
305                    TypeExtensionNode::Interface(node),
306                    TypeKind::Interface,
307                    &mut aggregates,
308                )?,
309                cst::Definition::UnionTypeExtension(node) => self.aggregate_extension(
310                    TypeExtensionNode::Union(node),
311                    TypeKind::Union,
312                    &mut aggregates,
313                )?,
314                cst::Definition::EnumTypeExtension(node) => self.aggregate_extension(
315                    TypeExtensionNode::Enum(node),
316                    TypeKind::Enum,
317                    &mut aggregates,
318                )?,
319                cst::Definition::InputObjectTypeExtension(node) => self.aggregate_extension(
320                    TypeExtensionNode::InputObject(node),
321                    TypeKind::InputObject,
322                    &mut aggregates,
323                )?,
324                _ => {}
325            }
326        }
327
328        let mut types = Vec::new();
329        for agg in aggregates.values() {
330            types.push(self.build_type_from_aggregate(agg, &repeatable_directives)?);
331        }
332
333        Ok(WesleyIR {
334            version: "1.0.0".to_string(),
335            metadata: None,
336            types,
337        })
338    }
339
340    fn aggregate_definition(
341        &self,
342        node: TypeDefinitionNode,
343        kind: TypeKind,
344        aggregates: &mut BTreeMap<String, TypeAggregate>,
345    ) -> Result<(), WesleyError> {
346        let name = type_node_name(node.name(), "Type definition missing name")?;
347        let agg = aggregate_for(aggregates, name, kind)?;
348        agg.definitions.push(node);
349        Ok(())
350    }
351
352    fn aggregate_extension(
353        &self,
354        node: TypeExtensionNode,
355        kind: TypeKind,
356        aggregates: &mut BTreeMap<String, TypeAggregate>,
357    ) -> Result<(), WesleyError> {
358        let name = type_node_name(node.name(), "Type extension missing name")?;
359        let agg = aggregate_for(aggregates, name, kind)?;
360        agg.extensions.push(node);
361        Ok(())
362    }
363
364    fn build_type_from_aggregate(
365        &self,
366        agg: &TypeAggregate,
367        repeatable_directives: &BTreeSet<String>,
368    ) -> Result<TypeDefinition, WesleyError> {
369        let mut acc = TypeBuildAccumulator::default();
370        let mut description = None;
371
372        for def in &agg.definitions {
373            if description.is_none() {
374                description = description_from(def.description());
375            }
376            self.merge_definition(def, &mut acc, repeatable_directives)?;
377        }
378
379        for ext in &agg.extensions {
380            self.merge_extension(ext, &mut acc, repeatable_directives)?;
381        }
382
383        Ok(TypeDefinition {
384            name: agg.name.clone(),
385            kind: agg.kind,
386            description,
387            directives: acc.directives,
388            implements: acc.implements,
389            fields: acc.fields,
390            enum_values: acc.enum_values,
391            union_members: acc.union_members,
392        })
393    }
394
395    fn merge_definition(
396        &self,
397        def: &TypeDefinitionNode,
398        acc: &mut TypeBuildAccumulator,
399        repeatable_directives: &BTreeSet<String>,
400    ) -> Result<(), WesleyError> {
401        match def {
402            TypeDefinitionNode::Scalar(node) => {
403                Self::extract_optional_directives(
404                    node.directives(),
405                    &mut acc.directives,
406                    repeatable_directives,
407                )?;
408            }
409            TypeDefinitionNode::Object(node) => {
410                if let Some(interfaces) = node.implements_interfaces() {
411                    collect_implements(interfaces, &mut acc.implements)?;
412                }
413                Self::extract_optional_directives(
414                    node.directives(),
415                    &mut acc.directives,
416                    repeatable_directives,
417                )?;
418                if let Some(fields_def) = node.fields_definition() {
419                    self.collect_fields(fields_def, &mut acc.fields, repeatable_directives)?;
420                }
421            }
422            TypeDefinitionNode::Interface(node) => {
423                if let Some(interfaces) = node.implements_interfaces() {
424                    collect_implements(interfaces, &mut acc.implements)?;
425                }
426                Self::extract_optional_directives(
427                    node.directives(),
428                    &mut acc.directives,
429                    repeatable_directives,
430                )?;
431                if let Some(fields_def) = node.fields_definition() {
432                    self.collect_fields(fields_def, &mut acc.fields, repeatable_directives)?;
433                }
434            }
435            TypeDefinitionNode::Union(node) => {
436                Self::extract_optional_directives(
437                    node.directives(),
438                    &mut acc.directives,
439                    repeatable_directives,
440                )?;
441                if let Some(member_types) = node.union_member_types() {
442                    collect_union_members(member_types, &mut acc.union_members)?;
443                }
444            }
445            TypeDefinitionNode::Enum(node) => {
446                Self::extract_optional_directives(
447                    node.directives(),
448                    &mut acc.directives,
449                    repeatable_directives,
450                )?;
451                if let Some(values_def) = node.enum_values_definition() {
452                    collect_enum_values(values_def, &mut acc.enum_values)?;
453                }
454            }
455            TypeDefinitionNode::InputObject(node) => {
456                Self::extract_optional_directives(
457                    node.directives(),
458                    &mut acc.directives,
459                    repeatable_directives,
460                )?;
461                if let Some(fields_def) = node.input_fields_definition() {
462                    self.collect_input_fields(fields_def, &mut acc.fields, repeatable_directives)?;
463                }
464            }
465        }
466
467        Ok(())
468    }
469
470    fn merge_extension(
471        &self,
472        ext: &TypeExtensionNode,
473        acc: &mut TypeBuildAccumulator,
474        repeatable_directives: &BTreeSet<String>,
475    ) -> Result<(), WesleyError> {
476        match ext {
477            TypeExtensionNode::Scalar(node) => {
478                Self::extract_optional_directives(
479                    node.directives(),
480                    &mut acc.directives,
481                    repeatable_directives,
482                )?;
483            }
484            TypeExtensionNode::Object(node) => {
485                if let Some(interfaces) = node.implements_interfaces() {
486                    collect_implements(interfaces, &mut acc.implements)?;
487                }
488                Self::extract_optional_directives(
489                    node.directives(),
490                    &mut acc.directives,
491                    repeatable_directives,
492                )?;
493                if let Some(fields_def) = node.fields_definition() {
494                    self.collect_fields(fields_def, &mut acc.fields, repeatable_directives)?;
495                }
496            }
497            TypeExtensionNode::Interface(node) => {
498                if let Some(interfaces) = node.implements_interfaces() {
499                    collect_implements(interfaces, &mut acc.implements)?;
500                }
501                Self::extract_optional_directives(
502                    node.directives(),
503                    &mut acc.directives,
504                    repeatable_directives,
505                )?;
506                if let Some(fields_def) = node.fields_definition() {
507                    self.collect_fields(fields_def, &mut acc.fields, repeatable_directives)?;
508                }
509            }
510            TypeExtensionNode::Union(node) => {
511                Self::extract_optional_directives(
512                    node.directives(),
513                    &mut acc.directives,
514                    repeatable_directives,
515                )?;
516                if let Some(member_types) = node.union_member_types() {
517                    collect_union_members(member_types, &mut acc.union_members)?;
518                }
519            }
520            TypeExtensionNode::Enum(node) => {
521                Self::extract_optional_directives(
522                    node.directives(),
523                    &mut acc.directives,
524                    repeatable_directives,
525                )?;
526                if let Some(values_def) = node.enum_values_definition() {
527                    collect_enum_values(values_def, &mut acc.enum_values)?;
528                }
529            }
530            TypeExtensionNode::InputObject(node) => {
531                Self::extract_optional_directives(
532                    node.directives(),
533                    &mut acc.directives,
534                    repeatable_directives,
535                )?;
536                if let Some(fields_def) = node.input_fields_definition() {
537                    self.collect_input_fields(fields_def, &mut acc.fields, repeatable_directives)?;
538                }
539            }
540        }
541
542        Ok(())
543    }
544
545    fn extract_optional_directives(
546        dirs: Option<cst::Directives>,
547        map: &mut IndexMap<String, serde_json::Value>,
548        repeatable_directives: &BTreeSet<String>,
549    ) -> Result<(), WesleyError> {
550        if let Some(dirs) = dirs {
551            Self::extract_directives_with_repeatability(dirs, map, repeatable_directives)?;
552        }
553
554        Ok(())
555    }
556
557    fn extract_directives_with_repeatability(
558        dirs: cst::Directives,
559        map: &mut IndexMap<String, serde_json::Value>,
560        repeatable_directives: &BTreeSet<String>,
561    ) -> Result<(), WesleyError> {
562        for dir in dirs.directives() {
563            let dir_name = dir
564                .name()
565                .ok_or(WesleyError::LoweringError {
566                    message: "Directive missing name".to_string(),
567                    area: "directive".to_string(),
568                })?
569                .text()
570                .to_string();
571            let core_name = canonical_core_directive_name(&dir_name);
572            let canonical_name = core_name.unwrap_or(dir_name.as_str()).to_string();
573
574            let mut args_map = serde_json::Map::new();
575            if let Some(args) = dir.arguments() {
576                for arg in args.arguments() {
577                    let arg_name = arg.name().map(|n| n.text().to_string()).unwrap_or_default();
578                    if let Some(val) = arg.value() {
579                        args_map.insert(arg_name, directive_value_to_json(val)?);
580                    }
581                }
582            }
583
584            let val = if args_map.is_empty() {
585                serde_json::Value::Bool(true)
586            } else {
587                serde_json::Value::Object(args_map)
588            };
589
590            let repeatable = core_name.is_none() && repeatable_directives.contains(&canonical_name);
591            insert_directive_value(map, canonical_name, val, repeatable)?;
592        }
593        Ok(())
594    }
595
596    fn collect_fields(
597        &self,
598        fields_def: cst::FieldsDefinition,
599        fields: &mut Vec<Field>,
600        repeatable_directives: &BTreeSet<String>,
601    ) -> Result<(), WesleyError> {
602        for field_def in fields_def.field_definitions() {
603            fields.push(self.build_field(field_def, repeatable_directives)?);
604        }
605
606        Ok(())
607    }
608
609    fn collect_input_fields(
610        &self,
611        fields_def: cst::InputFieldsDefinition,
612        fields: &mut Vec<Field>,
613        repeatable_directives: &BTreeSet<String>,
614    ) -> Result<(), WesleyError> {
615        for field_def in fields_def.input_value_definitions() {
616            fields.push(self.build_input_field(field_def, repeatable_directives)?);
617        }
618
619        Ok(())
620    }
621
622    fn build_field(
623        &self,
624        field_def: cst::FieldDefinition,
625        repeatable_directives: &BTreeSet<String>,
626    ) -> Result<Field, WesleyError> {
627        let name = field_def
628            .name()
629            .ok_or(WesleyError::LoweringError {
630                message: "Field missing name".to_string(),
631                area: "field".to_string(),
632            })?
633            .text()
634            .to_string();
635
636        let type_node = field_def.ty().ok_or(WesleyError::LoweringError {
637            message: "Field missing type".to_string(),
638            area: "field".to_string(),
639        })?;
640
641        let mut field_directives = IndexMap::new();
642        if let Some(dirs) = field_def.directives() {
643            Self::extract_directives_with_repeatability(
644                dirs,
645                &mut field_directives,
646                repeatable_directives,
647            )?;
648        }
649
650        Ok(Field {
651            name,
652            r#type: self.build_type_reference(type_node)?,
653            arguments: field_arguments_from_definition(
654                field_def.arguments_definition(),
655                repeatable_directives,
656            )?,
657            default_value: None,
658            directives: field_directives,
659            description: description_from(field_def.description()),
660        })
661    }
662
663    fn build_input_field(
664        &self,
665        field_def: cst::InputValueDefinition,
666        repeatable_directives: &BTreeSet<String>,
667    ) -> Result<Field, WesleyError> {
668        let name = field_def
669            .name()
670            .ok_or(WesleyError::LoweringError {
671                message: "Input field missing name".to_string(),
672                area: "field".to_string(),
673            })?
674            .text()
675            .to_string();
676
677        let type_node = field_def.ty().ok_or(WesleyError::LoweringError {
678            message: "Input field missing type".to_string(),
679            area: "field".to_string(),
680        })?;
681
682        let mut field_directives = IndexMap::new();
683        if let Some(dirs) = field_def.directives() {
684            Self::extract_directives_with_repeatability(
685                dirs,
686                &mut field_directives,
687                repeatable_directives,
688            )?;
689        }
690        let default_value = field_def
691            .default_value()
692            .and_then(|default_value| default_value.value())
693            .map(directive_value_to_json)
694            .transpose()?;
695
696        Ok(Field {
697            name,
698            r#type: self.build_type_reference(type_node)?,
699            arguments: Vec::new(),
700            default_value,
701            directives: field_directives,
702            description: description_from(field_def.description()),
703        })
704    }
705
706    fn build_type_reference(&self, type_node: cst::Type) -> Result<TypeReference, WesleyError> {
707        type_reference_from_type(type_node, true)
708    }
709}
710
711fn aggregate_for(
712    aggregates: &mut BTreeMap<String, TypeAggregate>,
713    name: String,
714    kind: TypeKind,
715) -> Result<&mut TypeAggregate, WesleyError> {
716    use std::collections::btree_map::Entry;
717
718    match aggregates.entry(name.clone()) {
719        Entry::Vacant(entry) => Ok(entry.insert(TypeAggregate {
720            name,
721            kind,
722            definitions: Vec::new(),
723            extensions: Vec::new(),
724        })),
725        Entry::Occupied(entry) => {
726            let aggregate = entry.into_mut();
727            if aggregate.kind != kind {
728                return Err(lowering_error_value(
729                    "type",
730                    format!(
731                        "Type '{}' is declared as both {:?} and {:?}",
732                        aggregate.name, aggregate.kind, kind
733                    ),
734                ));
735            }
736            Ok(aggregate)
737        }
738    }
739}
740
741fn type_node_name(name: Option<cst::Name>, message: &str) -> Result<String, WesleyError> {
742    name.map(|name| name.text().to_string())
743        .ok_or_else(|| lowering_error_value("type", message.to_string()))
744}
745
746fn description_from(description: Option<cst::Description>) -> Option<String> {
747    description
748        .and_then(|description| description.string_value())
749        .map(String::from)
750}
751
752fn collect_implements(
753    interfaces: cst::ImplementsInterfaces,
754    implements: &mut Vec<String>,
755) -> Result<(), WesleyError> {
756    for named_type in interfaces.named_types() {
757        let name = named_type_name_for_lowering(named_type, "Implemented interface missing name")?;
758        push_unique(implements, name);
759    }
760
761    Ok(())
762}
763
764fn collect_union_members(
765    member_types: cst::UnionMemberTypes,
766    union_members: &mut Vec<String>,
767) -> Result<(), WesleyError> {
768    for named_type in member_types.named_types() {
769        let name = named_type_name_for_lowering(named_type, "Union member missing name")?;
770        push_unique(union_members, name);
771    }
772
773    Ok(())
774}
775
776fn collect_enum_values(
777    values_def: cst::EnumValuesDefinition,
778    enum_values: &mut Vec<String>,
779) -> Result<(), WesleyError> {
780    for value_def in values_def.enum_value_definitions() {
781        let name = value_def
782            .enum_value()
783            .and_then(|enum_value| enum_value.name())
784            .map(|name| name.text().to_string())
785            .ok_or_else(|| lowering_error_value("enum", "Enum value missing name".to_string()))?;
786        push_unique(enum_values, name);
787    }
788
789    Ok(())
790}
791
792fn named_type_name_for_lowering(
793    named_type: cst::NamedType,
794    message: &str,
795) -> Result<String, WesleyError> {
796    named_type
797        .name()
798        .map(|name| name.text().to_string())
799        .ok_or_else(|| lowering_error_value("type", message.to_string()))
800}
801
802#[derive(Debug)]
803struct TypeReferenceShape {
804    base: String,
805    nullable: bool,
806    list_wrappers: Vec<TypeListWrapper>,
807    leaf_nullable: bool,
808}
809
810fn type_reference_from_type(
811    type_node: cst::Type,
812    nullable: bool,
813) -> Result<TypeReference, WesleyError> {
814    let shape = type_reference_shape_from_type(type_node, nullable)?;
815    let is_list = !shape.list_wrappers.is_empty();
816    let list_item_nullable = if is_list {
817        Some(
818            shape
819                .list_wrappers
820                .get(1)
821                .map(|wrapper| wrapper.nullable)
822                .unwrap_or(shape.leaf_nullable),
823        )
824    } else {
825        None
826    };
827    let has_nested_lists = shape.list_wrappers.len() > 1;
828
829    Ok(TypeReference {
830        base: shape.base,
831        nullable: shape.nullable,
832        is_list,
833        list_item_nullable,
834        list_wrappers: if has_nested_lists {
835            shape.list_wrappers
836        } else {
837            Vec::new()
838        },
839        leaf_nullable: if has_nested_lists {
840            Some(shape.leaf_nullable)
841        } else {
842            None
843        },
844    })
845}
846
847fn type_reference_shape_from_type(
848    type_node: cst::Type,
849    nullable: bool,
850) -> Result<TypeReferenceShape, WesleyError> {
851    match type_node {
852        cst::Type::NamedType(named_type) => Ok(TypeReferenceShape {
853            base: named_type_name_for_lowering(named_type, "Type reference missing name")?,
854            nullable,
855            list_wrappers: Vec::new(),
856            leaf_nullable: nullable,
857        }),
858        cst::Type::ListType(list_type) => {
859            let item_type = list_type.ty().ok_or_else(|| {
860                lowering_error_value("type", "List type missing item type".to_string())
861            })?;
862            let item_ref = type_reference_shape_from_type(item_type, true)?;
863            let mut list_wrappers = vec![TypeListWrapper { nullable }];
864            list_wrappers.extend(item_ref.list_wrappers);
865
866            Ok(TypeReferenceShape {
867                base: item_ref.base,
868                nullable,
869                list_wrappers,
870                leaf_nullable: item_ref.leaf_nullable,
871            })
872        }
873        cst::Type::NonNullType(non_null_type) => {
874            if let Some(named_type) = non_null_type.named_type() {
875                Ok(TypeReferenceShape {
876                    base: named_type_name_for_lowering(
877                        named_type,
878                        "Non-null type reference missing name",
879                    )?,
880                    nullable: false,
881                    list_wrappers: Vec::new(),
882                    leaf_nullable: false,
883                })
884            } else if let Some(list_type) = non_null_type.list_type() {
885                let item_type = list_type.ty().ok_or_else(|| {
886                    lowering_error_value("type", "Non-null list type missing item type".to_string())
887                })?;
888                let item_ref = type_reference_shape_from_type(item_type, true)?;
889                let mut list_wrappers = vec![TypeListWrapper { nullable: false }];
890                list_wrappers.extend(item_ref.list_wrappers);
891
892                Ok(TypeReferenceShape {
893                    base: item_ref.base,
894                    nullable: false,
895                    list_wrappers,
896                    leaf_nullable: item_ref.leaf_nullable,
897                })
898            } else {
899                Err(lowering_error_value(
900                    "type",
901                    "Non-null type missing inner type".to_string(),
902                ))
903            }
904        }
905    }
906}
907
908fn field_arguments_from_definition(
909    arguments_definition: Option<cst::ArgumentsDefinition>,
910    repeatable_directives: &BTreeSet<String>,
911) -> Result<Vec<FieldArgument>, WesleyError> {
912    let Some(arguments_definition) = arguments_definition else {
913        return Ok(Vec::new());
914    };
915
916    arguments_definition
917        .input_value_definitions()
918        .map(|input_value| field_argument_from_input_value(input_value, repeatable_directives))
919        .collect()
920}
921
922fn field_argument_from_input_value(
923    input_value: cst::InputValueDefinition,
924    repeatable_directives: &BTreeSet<String>,
925) -> Result<FieldArgument, WesleyError> {
926    let parts = argument_parts_from_input_value(
927        input_value,
928        "field argument",
929        "Field argument missing name",
930        |name| format!("Field argument '{name}' missing type"),
931        repeatable_directives,
932    )?;
933
934    Ok(FieldArgument {
935        name: parts.name,
936        description: parts.description,
937        r#type: parts.r#type,
938        default_value: parts.default_value,
939        directives: parts.directives,
940    })
941}
942
943struct ArgumentParts {
944    name: String,
945    description: Option<String>,
946    r#type: TypeReference,
947    default_value: Option<serde_json::Value>,
948    directives: IndexMap<String, serde_json::Value>,
949}
950
951fn argument_parts_from_input_value<F>(
952    input_value: cst::InputValueDefinition,
953    error_area: &'static str,
954    missing_name_message: &'static str,
955    missing_type_message: F,
956    repeatable_directives: &BTreeSet<String>,
957) -> Result<ArgumentParts, WesleyError>
958where
959    F: FnOnce(&str) -> String,
960{
961    let name = input_value
962        .name()
963        .map(|name| name.text().to_string())
964        .ok_or_else(|| lowering_error_value(error_area, missing_name_message.into()))?;
965    let type_node = input_value
966        .ty()
967        .ok_or_else(|| lowering_error_value(error_area, missing_type_message(&name)))?;
968    let default_value = input_value
969        .default_value()
970        .and_then(|default_value| default_value.value())
971        .map(directive_value_to_json)
972        .transpose()?;
973
974    let mut directives = IndexMap::new();
975    if let Some(dirs) = input_value.directives() {
976        ApolloLoweringAdapter::extract_directives_with_repeatability(
977            dirs,
978            &mut directives,
979            repeatable_directives,
980        )?;
981    }
982
983    Ok(ArgumentParts {
984        name,
985        description: description_from(input_value.description()),
986        r#type: type_reference_from_type(type_node, true)?,
987        default_value,
988        directives,
989    })
990}
991
992fn directive_value_to_json(value: cst::Value) -> Result<serde_json::Value, WesleyError> {
993    match value {
994        cst::Value::StringValue(value) => Ok(serde_json::Value::String(String::from(value))),
995        cst::Value::FloatValue(value) => {
996            let raw = value
997                .float_token()
998                .map(|token| token.text().to_string())
999                .unwrap_or_default();
1000            let parsed = raw.parse::<f64>().map_err(|err| {
1001                lowering_error_value(
1002                    "directive",
1003                    format!("Invalid float directive argument '{raw}': {err}"),
1004                )
1005            })?;
1006            serde_json::Number::from_f64(parsed)
1007                .map(serde_json::Value::Number)
1008                .ok_or_else(|| {
1009                    lowering_error_value(
1010                        "directive",
1011                        format!("Invalid finite float directive argument '{raw}'"),
1012                    )
1013                })
1014        }
1015        cst::Value::IntValue(value) => {
1016            let raw = value
1017                .int_token()
1018                .map(|token| token.text().to_string())
1019                .unwrap_or_default();
1020            raw.parse::<i64>()
1021                .map(|parsed| serde_json::Value::Number(parsed.into()))
1022                .map_err(|err| {
1023                    lowering_error_value(
1024                        "directive",
1025                        format!("Invalid integer directive argument '{raw}': {err}"),
1026                    )
1027                })
1028        }
1029        cst::Value::BooleanValue(value) => Ok(serde_json::Value::Bool(
1030            value.true_token().is_some() && value.false_token().is_none(),
1031        )),
1032        cst::Value::NullValue(_) => Ok(serde_json::Value::Null),
1033        cst::Value::EnumValue(value) => {
1034            let name = value
1035                .name()
1036                .map(|name| name.text().to_string())
1037                .ok_or_else(|| {
1038                    lowering_error_value(
1039                        "directive",
1040                        "Enum directive value missing name".to_string(),
1041                    )
1042                })?;
1043            Ok(serde_json::Value::String(name))
1044        }
1045        cst::Value::ListValue(list) => {
1046            let mut values = Vec::new();
1047            for value in list.values() {
1048                values.push(directive_value_to_json(value)?);
1049            }
1050            Ok(serde_json::Value::Array(values))
1051        }
1052        cst::Value::ObjectValue(object) => {
1053            let mut map = serde_json::Map::new();
1054            for field in object.object_fields() {
1055                let name = field
1056                    .name()
1057                    .map(|name| name.text().to_string())
1058                    .ok_or_else(|| {
1059                        lowering_error_value(
1060                            "directive",
1061                            "Object directive value field missing name".to_string(),
1062                        )
1063                    })?;
1064                let value = field.value().ok_or_else(|| {
1065                    lowering_error_value(
1066                        "directive",
1067                        format!("Object directive value field '{name}' missing value"),
1068                    )
1069                })?;
1070                map.insert(name, directive_value_to_json(value)?);
1071            }
1072            Ok(serde_json::Value::Object(map))
1073        }
1074        cst::Value::Variable(variable) => Err(lowering_error_value(
1075            "directive",
1076            format!(
1077                "Directive argument values cannot be variables: {}",
1078                variable.text()
1079            ),
1080        )),
1081    }
1082}
1083
1084fn lowering_error_value(area: &str, message: String) -> WesleyError {
1085    WesleyError::LoweringError {
1086        message,
1087        area: area.to_string(),
1088    }
1089}
1090
1091fn parse_error_from_apollo(sdl: &str, error: &ApolloParserError) -> WesleyError {
1092    let (line, column) = source_location_for_byte_index(sdl, error.index());
1093    WesleyError::ParseError {
1094        message: error.message().to_string(),
1095        line: Some(line),
1096        column: Some(column),
1097    }
1098}
1099
1100fn source_location_for_byte_index(source: &str, index: usize) -> (u32, u32) {
1101    let mut line = 1;
1102    let mut column = 1;
1103
1104    for (byte_index, character) in source.char_indices() {
1105        if byte_index >= index {
1106            break;
1107        }
1108        if character == '\n' {
1109            line += 1;
1110            column = 1;
1111        } else {
1112            column += 1;
1113        }
1114    }
1115
1116    (line, column)
1117}
1118
1119fn canonical_core_directive_name(name: &str) -> Option<&str> {
1120    match name {
1121        "wes_table" | "wesley_table" | "table" => Some("wes_table"),
1122        "wes_pk" | "wesley_pk" | "pk" | "primaryKey" => Some("wes_pk"),
1123        "wes_fk" | "wesley_fk" | "fk" | "foreignKey" => Some("wes_fk"),
1124        "wes_unique" | "wesley_unique" | "unique" => Some("wes_unique"),
1125        "wes_index" | "wesley_index" | "index" => Some("wes_index"),
1126        "wes_tenant" | "wesley_tenant" | "tenant" => Some("wes_tenant"),
1127        "wes_default" | "wesley_default" | "default" => Some("wes_default"),
1128        "wes_rls" | "wesley_rls" | "rls" => Some("wes_rls"),
1129        _ => None,
1130    }
1131}
1132
1133fn repeatable_directive_names_from_document(
1134    doc: &cst::Document,
1135) -> Result<BTreeSet<String>, WesleyError> {
1136    let mut repeatable = BTreeSet::new();
1137    for definition in doc.definitions() {
1138        let cst::Definition::DirectiveDefinition(directive) = definition else {
1139            continue;
1140        };
1141        if directive.repeatable_token().is_none() {
1142            continue;
1143        }
1144
1145        let name = directive
1146            .name()
1147            .map(|name| name.text().to_string())
1148            .ok_or_else(|| {
1149                lowering_error_value("directive", "Directive definition missing name".to_string())
1150            })?;
1151        let canonical_name = canonical_core_directive_name(&name)
1152            .unwrap_or(name.as_str())
1153            .to_string();
1154        repeatable.insert(canonical_name);
1155    }
1156
1157    Ok(repeatable)
1158}
1159
1160fn insert_directive_value(
1161    map: &mut IndexMap<String, serde_json::Value>,
1162    name: String,
1163    value: serde_json::Value,
1164    repeatable: bool,
1165) -> Result<(), WesleyError> {
1166    match map.get_mut(&name) {
1167        Some(_) if !repeatable => {
1168            return Err(lowering_error_value(
1169                "directive",
1170                format!("Duplicate non-repeatable directive '@{name}'"),
1171            ));
1172        }
1173        Some(serde_json::Value::Array(values)) => values.push(value),
1174        Some(existing) => {
1175            let first = std::mem::take(existing);
1176            *existing = serde_json::Value::Array(vec![first, value]);
1177        }
1178        None => {
1179            map.insert(name, value);
1180        }
1181    }
1182
1183    Ok(())
1184}
1185
1186/// Resolves response-path field selections from a single GraphQL operation.
1187pub fn resolve_operation_selections(operation_sdl: &str) -> Result<Vec<String>, WesleyError> {
1188    let parsed = parse_operation_document(operation_sdl)?;
1189    let op = parsed.only_operation()?;
1190    let mut selections = Vec::new();
1191
1192    if let Some(selection_set) = op.selection_set() {
1193        collect_selection_paths(
1194            &selection_set,
1195            "",
1196            &parsed.fragments,
1197            &mut Vec::new(),
1198            &mut selections,
1199        )?;
1200    }
1201
1202    Ok(selections)
1203}
1204
1205/// Resolves schema-coordinate field selections from a single GraphQL operation.
1206pub fn resolve_operation_selections_with_schema(
1207    schema_sdl: &str,
1208    operation_sdl: &str,
1209) -> Result<Vec<String>, WesleyError> {
1210    let adapter = ApolloLoweringAdapter::new(0);
1211    let ir = adapter.parse_and_lower(schema_sdl)?;
1212    let root_types = extract_root_types(schema_sdl)?;
1213
1214    let parsed = parse_operation_document(operation_sdl)?;
1215    let op = parsed.only_operation()?;
1216    let mut selections = Vec::new();
1217
1218    if let Some(selection_set) = op.selection_set() {
1219        let root_type = root_types.root_for_operation(op)?;
1220        let schema = SchemaIndex::new(&ir);
1221        collect_schema_coordinates(
1222            &selection_set,
1223            root_type,
1224            &schema,
1225            &parsed.fragments,
1226            &mut Vec::new(),
1227            &mut selections,
1228        )?;
1229    }
1230
1231    Ok(selections)
1232}
1233
1234/// Compiles runtime-provided SDL plus one GraphQL operation into an operation artifact.
1235///
1236/// This is a compiler-only entry point. It validates and inspects the declared
1237/// operation shape, but it does not execute the operation, grant authority, or
1238/// verify runtime law satisfaction.
1239pub fn compile_operation_artifact(
1240    sdl: &str,
1241    operation_source: &str,
1242    selected_operation: Option<&str>,
1243) -> Result<OperationArtifact, WesleyError> {
1244    let adapter = ApolloLoweringAdapter::new(0);
1245    let ir = adapter.parse_and_lower(sdl)?;
1246    let schema_id = compute_registry_hash(&ir).map_err(|err| {
1247        lowering_error_value(
1248            "operation artifact",
1249            format!("Failed to compute schema identity: {err}"),
1250        )
1251    })?;
1252    let schema = SchemaIndex::new(&ir);
1253    reject_operation_artifact_unsupported_schema_features(&ir)?;
1254    let root_types = extract_root_types(sdl)?;
1255    let schema_operations = list_schema_operations_sdl(sdl)?;
1256
1257    let parsed = parse_operation_document(operation_source)?;
1258    let op = parsed.selected_operation(selected_operation)?;
1259    let kind = operation_kind(op)?;
1260    let root_type = root_types.root_for_operation(op)?;
1261    let root_field = selected_root_field(op)?;
1262    let root_field_name = required_name(root_field.name(), "Root field selection missing name")?;
1263    let schema_operation =
1264        schema_operation_for_selected_field(&schema_operations, kind, &root_field_name)?;
1265    reject_operation_artifact_variable_defaults(op)?;
1266    let variable_types = variable_definition_types(op)?;
1267    validate_operation_artifact_executable_selection(
1268        &root_field,
1269        root_type,
1270        &schema,
1271        &parsed.fragments,
1272    )?;
1273    let root_arguments =
1274        root_argument_bindings(&root_field, schema_operation, &variable_types, &schema)?;
1275    let selection_arguments = selection_argument_bindings(
1276        &root_field,
1277        &schema_operation.result_type,
1278        &schema,
1279        &parsed.fragments,
1280        &variable_types,
1281    )?;
1282
1283    let operation_name = op.name().map(|name| name.text().to_string());
1284    let directives =
1285        directive_records_for_operation(op, root_type, &root_field, &schema, &parsed.fragments)?;
1286    let root_coordinate = format!("{root_type}.{root_field_name}");
1287    let declared_footprint = footprint_from_directives(&directives, &root_coordinate)?;
1288    let variable_shape = variable_codec_shape(op, schema_operation)?;
1289    let payload_shape = payload_codec_shape(
1290        &root_field,
1291        &schema_operation.result_type,
1292        &schema,
1293        &parsed.fragments,
1294    )?;
1295
1296    let identity_seed = serde_json::json!({
1297        "kind": kind,
1298        "name": operation_name,
1299        "rootArguments": root_arguments,
1300        "rootField": root_field_name,
1301        "schemaId": schema_id,
1302        "selectionArguments": selection_arguments,
1303        "variableShape": variable_shape,
1304        "payloadShape": payload_shape,
1305        "directives": directives,
1306    });
1307    let operation_id = stable_json_hash(&identity_seed, "operation identity")?;
1308    let law_claims =
1309        law_claims_for_operation(&operation_id, &directives, declared_footprint.as_ref())?;
1310    let requirements = requirements_from_footprint(declared_footprint.as_ref());
1311    let requirements_artifact = canonical_requirements_artifact(&serde_json::json!({
1312        "declaredFootprint": &declared_footprint,
1313        "lawClaims": &law_claims,
1314        "requirements": &requirements,
1315    }))?;
1316    let requirements_digest = requirements_artifact.digest.clone();
1317    let artifact_hash = stable_json_hash(
1318        &serde_json::json!({
1319            "directives": &directives,
1320            "operationId": &operation_id,
1321            "payloadShape": &payload_shape,
1322            "requirementsArtifact": {
1323                "codec": &requirements_artifact.codec,
1324                "digest": &requirements_artifact.digest,
1325            },
1326            "schemaId": &schema_id,
1327            "variableShape": &variable_shape,
1328        }),
1329        "operation artifact hash",
1330    )?;
1331    let artifact_id = artifact_hash.clone();
1332    let registration = OperationRegistrationDescriptor {
1333        artifact_id: artifact_id.clone(),
1334        artifact_hash: artifact_hash.clone(),
1335        schema_id: schema_id.clone(),
1336        operation_id: operation_id.clone(),
1337        requirements_digest: requirements_digest.clone(),
1338    };
1339
1340    Ok(OperationArtifact {
1341        artifact_id,
1342        artifact_hash,
1343        schema_id,
1344        requirements_digest,
1345        requirements_artifact,
1346        operation: CompiledOperation {
1347            operation_id,
1348            name: operation_name,
1349            kind,
1350            root_field: root_field_name,
1351            root_arguments,
1352            selection_arguments,
1353            variable_shape,
1354            payload_shape,
1355            directives,
1356            declared_footprint,
1357            law_claims,
1358        },
1359        requirements,
1360        registration,
1361    })
1362}
1363
1364/// Compiles runtime-provided SDL plus one GraphQL operation into a registration descriptor.
1365///
1366/// This returns a descriptor an external target can compare against the full
1367/// artifact before storing or importing compiler-produced evidence.
1368pub fn compile_operation_artifact_registration(
1369    sdl: &str,
1370    operation_source: &str,
1371    selected_operation: Option<&str>,
1372) -> Result<OperationRegistrationDescriptor, WesleyError> {
1373    Ok(compile_operation_artifact(sdl, operation_source, selected_operation)?.registration)
1374}
1375
1376/// Extracts arguments from operation directives with the requested directive name.
1377pub fn extract_operation_directive_args(
1378    operation_sdl: &str,
1379    directive_name: &str,
1380) -> Result<Vec<OperationDirectiveArgs>, WesleyError> {
1381    let parsed = parse_operation_document(operation_sdl)?;
1382    let op = parsed.only_operation()?;
1383    let mut directives = Vec::new();
1384
1385    let Some(operation_directives) = op.directives() else {
1386        return Ok(directives);
1387    };
1388
1389    for directive in operation_directives.directives() {
1390        let name = required_name(directive.name(), "Directive missing name")?;
1391        if name != directive_name {
1392            continue;
1393        }
1394
1395        directives.push(OperationDirectiveArgs {
1396            directive_name: name,
1397            arguments: extract_directive_arguments(directive.arguments())?,
1398        });
1399    }
1400
1401    Ok(directives)
1402}
1403
1404fn extract_directive_arguments(
1405    arguments: Option<cst::Arguments>,
1406) -> Result<IndexMap<String, serde_json::Value>, WesleyError> {
1407    let mut values = IndexMap::new();
1408
1409    let Some(arguments) = arguments else {
1410        return Ok(values);
1411    };
1412
1413    for argument in arguments.arguments() {
1414        let name = required_name(argument.name(), "Directive argument missing name")?;
1415        let value = argument.value().ok_or_else(|| {
1416            operation_error_value(format!("Directive argument '{name}' missing value"))
1417        })?;
1418        values.insert(name, directive_value_to_json(value)?);
1419    }
1420
1421    Ok(values)
1422}
1423
1424struct ParsedOperationDocument {
1425    operations: Vec<cst::OperationDefinition>,
1426    fragments: BTreeMap<String, cst::FragmentDefinition>,
1427}
1428
1429impl ParsedOperationDocument {
1430    fn only_operation(&self) -> Result<&cst::OperationDefinition, WesleyError> {
1431        match self.operations.len() {
1432            0 => operation_error("No GraphQL operation found".to_string()),
1433            1 => Ok(&self.operations[0]),
1434            count => operation_error(format!(
1435                "Expected exactly one GraphQL operation, found {count}"
1436            )),
1437        }
1438    }
1439
1440    fn selected_operation(
1441        &self,
1442        selected_operation: Option<&str>,
1443    ) -> Result<&cst::OperationDefinition, WesleyError> {
1444        let Some(selected_operation) = selected_operation else {
1445            return self.only_operation();
1446        };
1447
1448        self.operations
1449            .iter()
1450            .find(|operation| {
1451                operation
1452                    .name()
1453                    .map(|name| name.text() == selected_operation)
1454                    .unwrap_or(false)
1455            })
1456            .ok_or_else(|| {
1457                operation_error_value(format!(
1458                    "Selected GraphQL operation '{selected_operation}' not found"
1459                ))
1460            })
1461    }
1462}
1463
1464fn parse_operation_document(operation_sdl: &str) -> Result<ParsedOperationDocument, WesleyError> {
1465    let parser = Parser::new(operation_sdl);
1466    let cst = parser.parse();
1467
1468    let errors = cst.errors().collect::<Vec<_>>();
1469    if !errors.is_empty() {
1470        let err = &errors[0];
1471        return Err(parse_error_from_apollo(operation_sdl, err));
1472    }
1473
1474    let doc = cst.document();
1475    let mut operations = Vec::new();
1476    let mut fragments = BTreeMap::new();
1477
1478    for def in doc.definitions() {
1479        match def {
1480            cst::Definition::OperationDefinition(op) => {
1481                operations.push(op);
1482            }
1483            cst::Definition::FragmentDefinition(fragment) => {
1484                let name = fragment_name(&fragment)?;
1485                if fragments.insert(name.clone(), fragment).is_some() {
1486                    return operation_error(format!("Duplicate fragment definition '{name}'"));
1487                }
1488            }
1489            _ => {}
1490        }
1491    }
1492
1493    Ok(ParsedOperationDocument {
1494        operations,
1495        fragments,
1496    })
1497}
1498
1499fn collect_selection_paths(
1500    selection_set: &cst::SelectionSet,
1501    prefix: &str,
1502    fragments: &BTreeMap<String, cst::FragmentDefinition>,
1503    active_fragments: &mut Vec<String>,
1504    actual_selections: &mut Vec<String>,
1505) -> Result<(), WesleyError> {
1506    for selection in selection_set.selections() {
1507        match selection {
1508            cst::Selection::Field(field) => {
1509                let field_name = required_name(field.name(), "Field selection missing name")?;
1510                let path = if prefix.is_empty() {
1511                    field_name
1512                } else {
1513                    format!("{prefix}.{field_name}")
1514                };
1515
1516                push_unique(actual_selections, path.clone());
1517
1518                if let Some(nested_selection_set) = field.selection_set() {
1519                    collect_selection_paths(
1520                        &nested_selection_set,
1521                        &path,
1522                        fragments,
1523                        active_fragments,
1524                        actual_selections,
1525                    )?;
1526                }
1527            }
1528            cst::Selection::FragmentSpread(spread) => {
1529                let name = spread
1530                    .fragment_name()
1531                    .and_then(|fragment_name| fragment_name.name())
1532                    .map(|name| name.text().to_string())
1533                    .ok_or_else(|| {
1534                        operation_error_value("Fragment spread missing name".to_string())
1535                    })?;
1536
1537                if active_fragments.contains(&name) {
1538                    return operation_error(format!(
1539                        "Cyclic fragment spread detected for fragment '{name}'"
1540                    ));
1541                }
1542
1543                let fragment = fragments.get(&name).ok_or_else(|| {
1544                    operation_error_value(format!("Unknown fragment spread '{name}'"))
1545                })?;
1546
1547                active_fragments.push(name);
1548                if let Some(fragment_selection_set) = fragment.selection_set() {
1549                    collect_selection_paths(
1550                        &fragment_selection_set,
1551                        prefix,
1552                        fragments,
1553                        active_fragments,
1554                        actual_selections,
1555                    )?;
1556                }
1557                active_fragments.pop();
1558            }
1559            cst::Selection::InlineFragment(fragment) => {
1560                if let Some(inline_selection_set) = fragment.selection_set() {
1561                    collect_selection_paths(
1562                        &inline_selection_set,
1563                        prefix,
1564                        fragments,
1565                        active_fragments,
1566                        actual_selections,
1567                    )?;
1568                }
1569            }
1570        }
1571    }
1572
1573    Ok(())
1574}
1575
1576fn collect_schema_coordinates(
1577    selection_set: &cst::SelectionSet,
1578    parent_type: &str,
1579    schema: &SchemaIndex<'_>,
1580    fragments: &BTreeMap<String, cst::FragmentDefinition>,
1581    active_fragments: &mut Vec<String>,
1582    actual_selections: &mut Vec<String>,
1583) -> Result<(), WesleyError> {
1584    for selection in selection_set.selections() {
1585        match selection {
1586            cst::Selection::Field(field) => {
1587                let field_name = required_name(field.name(), "Field selection missing name")?;
1588                let schema_field = schema.field(parent_type, &field_name)?;
1589                let coordinate = format!("{parent_type}.{field_name}");
1590                push_unique(actual_selections, coordinate);
1591
1592                if let Some(nested_selection_set) = field.selection_set() {
1593                    let nested_parent = schema_field.r#type.base.as_str();
1594                    schema.require_type(nested_parent)?;
1595                    collect_schema_coordinates(
1596                        &nested_selection_set,
1597                        nested_parent,
1598                        schema,
1599                        fragments,
1600                        active_fragments,
1601                        actual_selections,
1602                    )?;
1603                }
1604            }
1605            cst::Selection::FragmentSpread(spread) => {
1606                let name = spread
1607                    .fragment_name()
1608                    .and_then(|fragment_name| fragment_name.name())
1609                    .map(|name| name.text().to_string())
1610                    .ok_or_else(|| {
1611                        operation_error_value("Fragment spread missing name".to_string())
1612                    })?;
1613
1614                if active_fragments.contains(&name) {
1615                    return operation_error(format!(
1616                        "Cyclic fragment spread detected for fragment '{name}'"
1617                    ));
1618                }
1619
1620                let fragment = fragments.get(&name).ok_or_else(|| {
1621                    operation_error_value(format!("Unknown fragment spread '{name}'"))
1622                })?;
1623                let fragment_parent = fragment_type_condition(fragment)?;
1624                validate_fragment_type_condition(parent_type, &fragment_parent, schema, &name)?;
1625
1626                active_fragments.push(name);
1627                if let Some(fragment_selection_set) = fragment.selection_set() {
1628                    collect_schema_coordinates(
1629                        &fragment_selection_set,
1630                        &fragment_parent,
1631                        schema,
1632                        fragments,
1633                        active_fragments,
1634                        actual_selections,
1635                    )?;
1636                }
1637                active_fragments.pop();
1638            }
1639            cst::Selection::InlineFragment(fragment) => {
1640                let inline_parent = if let Some(type_condition) = fragment.type_condition() {
1641                    named_type_name(type_condition.named_type(), "Inline fragment missing type")?
1642                } else {
1643                    parent_type.to_string()
1644                };
1645                validate_fragment_type_condition(parent_type, &inline_parent, schema, "inline")?;
1646
1647                if let Some(inline_selection_set) = fragment.selection_set() {
1648                    collect_schema_coordinates(
1649                        &inline_selection_set,
1650                        &inline_parent,
1651                        schema,
1652                        fragments,
1653                        active_fragments,
1654                        actual_selections,
1655                    )?;
1656                }
1657            }
1658        }
1659    }
1660
1661    Ok(())
1662}
1663
1664fn operation_kind(op: &cst::OperationDefinition) -> Result<OperationKind, WesleyError> {
1665    let Some(operation_type) = op.operation_type() else {
1666        return Ok(OperationKind::Query);
1667    };
1668
1669    if operation_type.query_token().is_some() {
1670        Ok(OperationKind::Query)
1671    } else if operation_type.mutation_token().is_some() {
1672        Ok(OperationKind::Mutation)
1673    } else if operation_type.subscription_token().is_some() {
1674        Ok(OperationKind::Subscription)
1675    } else {
1676        operation_error("Unknown GraphQL operation type".to_string())
1677    }
1678}
1679
1680fn selected_root_field(op: &cst::OperationDefinition) -> Result<cst::Field, WesleyError> {
1681    let selection_set = op.selection_set().ok_or_else(|| {
1682        operation_error_value("Operation artifact operation missing selection set".to_string())
1683    })?;
1684    let mut fields = Vec::new();
1685
1686    for selection in selection_set.selections() {
1687        match selection {
1688            cst::Selection::Field(field) => fields.push(field),
1689            cst::Selection::FragmentSpread(_) | cst::Selection::InlineFragment(_) => {
1690                return operation_error(
1691                    "Operation artifact v0 requires a concrete top-level field selection"
1692                        .to_string(),
1693                );
1694            }
1695        }
1696    }
1697
1698    match fields.len() {
1699        0 => operation_error("Operation artifact operation selects no root field".to_string()),
1700        1 => Ok(fields.remove(0)),
1701        count => operation_error(format!(
1702            "Operation artifact v0 expects exactly one root field selection, found {count}"
1703        )),
1704    }
1705}
1706
1707fn schema_operation_for_selected_field<'a>(
1708    schema_operations: &'a [SchemaOperation],
1709    kind: OperationKind,
1710    root_field_name: &str,
1711) -> Result<&'a SchemaOperation, WesleyError> {
1712    let operation_type = OperationType::from(kind);
1713    schema_operations
1714        .iter()
1715        .find(|operation| {
1716            operation.operation_type == operation_type && operation.field_name == root_field_name
1717        })
1718        .ok_or_else(|| {
1719            operation_error_value(format!(
1720                "Schema root operation '{root_field_name}' not found for {kind:?}"
1721            ))
1722        })
1723}
1724
1725fn reject_operation_artifact_unsupported_schema_features(ir: &WesleyIR) -> Result<(), WesleyError> {
1726    for type_def in &ir.types {
1727        if type_def.kind == TypeKind::Interface && !type_def.implements.is_empty() {
1728            return operation_error(format!(
1729                "Operation artifact v0 does not support interface inheritance on '{}'",
1730                type_def.name
1731            ));
1732        }
1733    }
1734
1735    Ok(())
1736}
1737
1738fn reject_operation_artifact_variable_defaults(
1739    op: &cst::OperationDefinition,
1740) -> Result<(), WesleyError> {
1741    let Some(variable_definitions) = op.variable_definitions() else {
1742        return Ok(());
1743    };
1744
1745    for variable in variable_definitions.variable_definitions() {
1746        if variable.default_value().is_some() {
1747            let name = variable
1748                .variable()
1749                .and_then(|variable| variable.name())
1750                .map(|name| name.text().to_string())
1751                .unwrap_or_else(|| "<unknown>".to_string());
1752            return operation_error(format!(
1753                "Operation artifact v0 does not support default value for variable '${name}'"
1754            ));
1755        }
1756    }
1757
1758    Ok(())
1759}
1760
1761fn validate_operation_artifact_executable_selection(
1762    root_field: &cst::Field,
1763    root_type: &str,
1764    schema: &SchemaIndex<'_>,
1765    fragments: &BTreeMap<String, cst::FragmentDefinition>,
1766) -> Result<(), WesleyError> {
1767    validate_operation_artifact_field_selection(
1768        root_field,
1769        root_type,
1770        schema,
1771        fragments,
1772        &mut Vec::new(),
1773    )
1774}
1775
1776fn validate_operation_artifact_selection_set(
1777    selection_set: &cst::SelectionSet,
1778    parent_type: &str,
1779    schema: &SchemaIndex<'_>,
1780    fragments: &BTreeMap<String, cst::FragmentDefinition>,
1781    active_fragments: &mut Vec<String>,
1782) -> Result<(), WesleyError> {
1783    let mut response_signatures = BTreeMap::new();
1784    validate_operation_artifact_selection_set_into(
1785        selection_set,
1786        parent_type,
1787        schema,
1788        fragments,
1789        active_fragments,
1790        &mut response_signatures,
1791    )
1792}
1793
1794fn validate_operation_artifact_selection_set_into(
1795    selection_set: &cst::SelectionSet,
1796    parent_type: &str,
1797    schema: &SchemaIndex<'_>,
1798    fragments: &BTreeMap<String, cst::FragmentDefinition>,
1799    active_fragments: &mut Vec<String>,
1800    response_signatures: &mut BTreeMap<String, OperationArtifactFieldSignature>,
1801) -> Result<(), WesleyError> {
1802    for selection in selection_set.selections() {
1803        match selection {
1804            cst::Selection::Field(field) => {
1805                let (response_name, signature) =
1806                    operation_artifact_field_signature(&field, parent_type, schema)?;
1807                if let Some(existing) = response_signatures.get(&response_name) {
1808                    if existing != &signature {
1809                        return operation_error(format!(
1810                            "Operation artifact response name '{response_name}' has conflicting field selections"
1811                        ));
1812                    }
1813                } else {
1814                    response_signatures.insert(response_name, signature);
1815                }
1816
1817                validate_operation_artifact_field_selection(
1818                    &field,
1819                    parent_type,
1820                    schema,
1821                    fragments,
1822                    active_fragments,
1823                )?;
1824            }
1825            cst::Selection::FragmentSpread(spread) => {
1826                let name = spread
1827                    .fragment_name()
1828                    .and_then(|fragment_name| fragment_name.name())
1829                    .map(|name| name.text().to_string())
1830                    .ok_or_else(|| {
1831                        operation_error_value("Fragment spread missing name".to_string())
1832                    })?;
1833
1834                if active_fragments.contains(&name) {
1835                    return operation_error(format!(
1836                        "Cyclic fragment spread detected for fragment '{name}'"
1837                    ));
1838                }
1839
1840                let fragment = fragments.get(&name).ok_or_else(|| {
1841                    operation_error_value(format!("Unknown fragment spread '{name}'"))
1842                })?;
1843                let fragment_parent = fragment_type_condition(fragment)?;
1844                validate_fragment_type_condition(parent_type, &fragment_parent, schema, &name)?;
1845
1846                active_fragments.push(name);
1847                if let Some(fragment_selection_set) = fragment.selection_set() {
1848                    validate_operation_artifact_selection_set_into(
1849                        &fragment_selection_set,
1850                        &fragment_parent,
1851                        schema,
1852                        fragments,
1853                        active_fragments,
1854                        response_signatures,
1855                    )?;
1856                }
1857                active_fragments.pop();
1858            }
1859            cst::Selection::InlineFragment(fragment) => {
1860                let inline_parent = if let Some(type_condition) = fragment.type_condition() {
1861                    named_type_name(type_condition.named_type(), "Inline fragment missing type")?
1862                } else {
1863                    parent_type.to_string()
1864                };
1865                validate_fragment_type_condition(parent_type, &inline_parent, schema, "inline")?;
1866
1867                if let Some(inline_selection_set) = fragment.selection_set() {
1868                    validate_operation_artifact_selection_set_into(
1869                        &inline_selection_set,
1870                        &inline_parent,
1871                        schema,
1872                        fragments,
1873                        active_fragments,
1874                        response_signatures,
1875                    )?;
1876                }
1877            }
1878        }
1879    }
1880
1881    Ok(())
1882}
1883
1884fn validate_operation_artifact_field_selection(
1885    field: &cst::Field,
1886    parent_type: &str,
1887    schema: &SchemaIndex<'_>,
1888    fragments: &BTreeMap<String, cst::FragmentDefinition>,
1889    active_fragments: &mut Vec<String>,
1890) -> Result<(), WesleyError> {
1891    let field_name = required_name(field.name(), "Field selection missing name")?;
1892    reject_operation_artifact_unsupported_field_name(&field_name)?;
1893    let schema_field = schema.field(parent_type, &field_name)?;
1894    let has_selection_set = field.selection_set().is_some();
1895    let is_composite = is_composite_output_type(&schema_field.r#type, schema)?;
1896
1897    match (is_composite, has_selection_set) {
1898        (true, false) => operation_error(format!(
1899            "Operation artifact field '{parent_type}.{field_name}' returns composite type '{}' and requires a subselection",
1900            schema_field.r#type.base
1901        )),
1902        (false, true) => operation_error(format!(
1903            "Operation artifact field '{parent_type}.{field_name}' returns leaf type '{}' and must not have a subselection",
1904            schema_field.r#type.base
1905        )),
1906        _ => {
1907            if let Some(selection_set) = field.selection_set() {
1908                validate_operation_artifact_selection_set(
1909                    &selection_set,
1910                    &schema_field.r#type.base,
1911                    schema,
1912                    fragments,
1913                    active_fragments,
1914                )?;
1915            }
1916            Ok(())
1917        }
1918    }
1919}
1920
1921#[derive(Clone, PartialEq)]
1922struct OperationArtifactFieldSignature {
1923    parent_type: String,
1924    field_name: String,
1925    arguments_canonical_json: String,
1926    type_ref: TypeReference,
1927}
1928
1929fn operation_artifact_field_signature(
1930    field: &cst::Field,
1931    parent_type: &str,
1932    schema: &SchemaIndex<'_>,
1933) -> Result<(String, OperationArtifactFieldSignature), WesleyError> {
1934    let field_name = required_name(field.name(), "Field selection missing name")?;
1935    reject_operation_artifact_unsupported_field_name(&field_name)?;
1936    let response_name = response_field_name(field)?;
1937    let schema_field = schema.field(parent_type, &field_name)?;
1938
1939    Ok((
1940        response_name,
1941        OperationArtifactFieldSignature {
1942            parent_type: parent_type.to_string(),
1943            field_name,
1944            arguments_canonical_json: field_arguments_canonical_json(field.arguments())?,
1945            type_ref: schema_field.r#type.clone(),
1946        },
1947    ))
1948}
1949
1950fn field_arguments_canonical_json(
1951    arguments: Option<cst::Arguments>,
1952) -> Result<String, WesleyError> {
1953    let mut values = IndexMap::new();
1954
1955    if let Some(arguments) = arguments {
1956        for argument in arguments.arguments() {
1957            let name = required_name(argument.name(), "Field argument missing name")?;
1958            if values.contains_key(&name) {
1959                return operation_error(format!(
1960                    "Operation artifact field argument '{name}' is declared more than once"
1961                ));
1962            }
1963            let value = argument.value().ok_or_else(|| {
1964                operation_error_value(format!("Field argument '{name}' missing value"))
1965            })?;
1966            values.insert(name, executable_value_to_json(value)?);
1967        }
1968    }
1969
1970    stable_json_string(&values, "operation artifact field arguments")
1971}
1972
1973fn reject_operation_artifact_unsupported_field_name(field_name: &str) -> Result<(), WesleyError> {
1974    if field_name == "__typename" {
1975        return operation_error(
1976            "Operation artifact v0 does not support __typename selections".to_string(),
1977        );
1978    }
1979
1980    Ok(())
1981}
1982
1983fn is_composite_output_type(
1984    type_ref: &TypeReference,
1985    schema: &SchemaIndex<'_>,
1986) -> Result<bool, WesleyError> {
1987    match schema.type_kind(&type_ref.base) {
1988        Some(TypeKind::Object | TypeKind::Interface | TypeKind::Union) => Ok(true),
1989        Some(TypeKind::Enum | TypeKind::Scalar) => Ok(false),
1990        Some(TypeKind::InputObject) => operation_error(format!(
1991            "Operation artifact field references input object '{}' as an output type",
1992            type_ref.base
1993        )),
1994        None if is_builtin_scalar(&type_ref.base) => Ok(false),
1995        None => operation_error(format!(
1996            "Operation artifact field references unknown output type '{}'",
1997            type_ref.base
1998        )),
1999    }
2000}
2001
2002fn directive_records_for_operation(
2003    op: &cst::OperationDefinition,
2004    root_type: &str,
2005    root_field: &cst::Field,
2006    schema: &SchemaIndex<'_>,
2007    fragments: &BTreeMap<String, cst::FragmentDefinition>,
2008) -> Result<Vec<DirectiveRecord>, WesleyError> {
2009    let operation_coordinate = op
2010        .name()
2011        .map(|name| format!("Operation.{}", name.text()))
2012        .unwrap_or_else(|| "Operation.<anonymous>".to_string());
2013    let mut records = Vec::new();
2014
2015    push_directive_records(&operation_coordinate, op.directives(), &mut records)?;
2016    collect_field_directive_records(
2017        root_field,
2018        root_type,
2019        schema,
2020        fragments,
2021        &mut Vec::new(),
2022        "",
2023        &mut records,
2024    )?;
2025
2026    Ok(records)
2027}
2028
2029fn collect_field_directive_records(
2030    field: &cst::Field,
2031    parent_type: &str,
2032    schema: &SchemaIndex<'_>,
2033    fragments: &BTreeMap<String, cst::FragmentDefinition>,
2034    active_fragments: &mut Vec<String>,
2035    context_coordinate: &str,
2036    records: &mut Vec<DirectiveRecord>,
2037) -> Result<(), WesleyError> {
2038    let field_name = required_name(field.name(), "Field selection missing name")?;
2039    let schema_field = schema.field(parent_type, &field_name)?;
2040    let coordinate = format!("{parent_type}.{field_name}");
2041    push_directive_records(&coordinate, field.directives(), records)?;
2042
2043    if let Some(selection_set) = field.selection_set() {
2044        let nested_parent = schema_field.r#type.base.as_str();
2045        schema.require_type(nested_parent)?;
2046        collect_selection_directive_records(
2047            &selection_set,
2048            nested_parent,
2049            schema,
2050            fragments,
2051            active_fragments,
2052            &coordinate,
2053            records,
2054        )?;
2055    } else if !context_coordinate.is_empty() {
2056        let _ = context_coordinate;
2057    }
2058
2059    Ok(())
2060}
2061
2062fn collect_selection_directive_records(
2063    selection_set: &cst::SelectionSet,
2064    parent_type: &str,
2065    schema: &SchemaIndex<'_>,
2066    fragments: &BTreeMap<String, cst::FragmentDefinition>,
2067    active_fragments: &mut Vec<String>,
2068    context_coordinate: &str,
2069    records: &mut Vec<DirectiveRecord>,
2070) -> Result<(), WesleyError> {
2071    for selection in selection_set.selections() {
2072        match selection {
2073            cst::Selection::Field(field) => {
2074                collect_field_directive_records(
2075                    &field,
2076                    parent_type,
2077                    schema,
2078                    fragments,
2079                    active_fragments,
2080                    context_coordinate,
2081                    records,
2082                )?;
2083            }
2084            cst::Selection::FragmentSpread(spread) => {
2085                let name = spread
2086                    .fragment_name()
2087                    .and_then(|fragment_name| fragment_name.name())
2088                    .map(|name| name.text().to_string())
2089                    .ok_or_else(|| {
2090                        operation_error_value("Fragment spread missing name".to_string())
2091                    })?;
2092
2093                if active_fragments.contains(&name) {
2094                    return operation_error(format!(
2095                        "Cyclic fragment spread detected for fragment '{name}'"
2096                    ));
2097                }
2098
2099                let fragment = fragments.get(&name).ok_or_else(|| {
2100                    operation_error_value(format!("Unknown fragment spread '{name}'"))
2101                })?;
2102                let fragment_parent = fragment_type_condition(fragment)?;
2103                validate_fragment_type_condition(parent_type, &fragment_parent, schema, &name)?;
2104
2105                let spread_coordinate = if context_coordinate.is_empty() {
2106                    format!("{parent_type}...{name}")
2107                } else {
2108                    format!("{context_coordinate}...{name}")
2109                };
2110                push_directive_records(&spread_coordinate, spread.directives(), records)?;
2111                push_directive_records(
2112                    &format!("Fragment.{name}"),
2113                    fragment.directives(),
2114                    records,
2115                )?;
2116
2117                active_fragments.push(name);
2118                if let Some(fragment_selection_set) = fragment.selection_set() {
2119                    collect_selection_directive_records(
2120                        &fragment_selection_set,
2121                        &fragment_parent,
2122                        schema,
2123                        fragments,
2124                        active_fragments,
2125                        context_coordinate,
2126                        records,
2127                    )?;
2128                }
2129                active_fragments.pop();
2130            }
2131            cst::Selection::InlineFragment(fragment) => {
2132                let inline_parent = if let Some(type_condition) = fragment.type_condition() {
2133                    named_type_name(type_condition.named_type(), "Inline fragment missing type")?
2134                } else {
2135                    parent_type.to_string()
2136                };
2137                validate_fragment_type_condition(parent_type, &inline_parent, schema, "inline")?;
2138
2139                let inline_coordinate = if context_coordinate.is_empty() {
2140                    format!("{parent_type}...on {inline_parent}")
2141                } else {
2142                    format!("{context_coordinate}...on {inline_parent}")
2143                };
2144                push_directive_records(&inline_coordinate, fragment.directives(), records)?;
2145
2146                if let Some(inline_selection_set) = fragment.selection_set() {
2147                    collect_selection_directive_records(
2148                        &inline_selection_set,
2149                        &inline_parent,
2150                        schema,
2151                        fragments,
2152                        active_fragments,
2153                        &inline_coordinate,
2154                        records,
2155                    )?;
2156                }
2157            }
2158        }
2159    }
2160
2161    Ok(())
2162}
2163
2164fn push_directive_records(
2165    coordinate: &str,
2166    directives: Option<cst::Directives>,
2167    records: &mut Vec<DirectiveRecord>,
2168) -> Result<(), WesleyError> {
2169    let Some(directives) = directives else {
2170        return Ok(());
2171    };
2172
2173    for directive in directives.directives() {
2174        let name = required_name(directive.name(), "Directive missing name")?;
2175        let arguments = extract_executable_directive_arguments(directive.arguments())?;
2176        let arguments_canonical_json =
2177            stable_json_string(&arguments, "operation artifact directive")?;
2178        records.push(DirectiveRecord {
2179            coordinate: coordinate.to_string(),
2180            name,
2181            arguments_canonical_json,
2182        });
2183    }
2184
2185    Ok(())
2186}
2187
2188fn extract_executable_directive_arguments(
2189    arguments: Option<cst::Arguments>,
2190) -> Result<IndexMap<String, serde_json::Value>, WesleyError> {
2191    let mut values = IndexMap::new();
2192
2193    let Some(arguments) = arguments else {
2194        return Ok(values);
2195    };
2196
2197    for argument in arguments.arguments() {
2198        let name = required_name(argument.name(), "Directive argument missing name")?;
2199        let value = argument.value().ok_or_else(|| {
2200            operation_error_value(format!("Directive argument '{name}' missing value"))
2201        })?;
2202        if values
2203            .insert(name.clone(), executable_value_to_json(value)?)
2204            .is_some()
2205        {
2206            return operation_error(format!(
2207                "Directive argument '{name}' is declared more than once"
2208            ));
2209        }
2210    }
2211
2212    Ok(values)
2213}
2214
2215fn footprint_from_directives(
2216    directives: &[DirectiveRecord],
2217    root_coordinate: &str,
2218) -> Result<Option<Footprint>, WesleyError> {
2219    let mut footprint = None;
2220
2221    for directive in directives
2222        .iter()
2223        .filter(|directive| directive.name == "wes_footprint")
2224    {
2225        if directive.coordinate != root_coordinate {
2226            return operation_error(format!(
2227                "Operation artifact @wes_footprint is only supported on selected root field '{root_coordinate}', found on '{}'",
2228                directive.coordinate
2229            ));
2230        }
2231
2232        if footprint.is_some() {
2233            return operation_error("Operation artifact declares multiple footprints".to_string());
2234        }
2235
2236        let arguments: serde_json::Value =
2237            serde_json::from_str(&directive.arguments_canonical_json).map_err(|err| {
2238                operation_error_value(format!("Invalid canonical footprint arguments: {err}"))
2239            })?;
2240        footprint = Some(Footprint {
2241            reads: required_string_array(&arguments, "reads")?,
2242            writes: required_string_array(&arguments, "writes")?,
2243            forbids: optional_string_array(&arguments, "forbids")?,
2244        });
2245    }
2246
2247    Ok(footprint)
2248}
2249
2250fn required_string_array(
2251    arguments: &serde_json::Value,
2252    name: &str,
2253) -> Result<Vec<String>, WesleyError> {
2254    let value = arguments
2255        .get(name)
2256        .ok_or_else(|| operation_error_value(format!("Footprint argument '{name}' is required")))?;
2257    string_array(value, name)
2258}
2259
2260fn optional_string_array(
2261    arguments: &serde_json::Value,
2262    name: &str,
2263) -> Result<Vec<String>, WesleyError> {
2264    let Some(value) = arguments.get(name) else {
2265        return Ok(Vec::new());
2266    };
2267
2268    string_array(value, name)
2269}
2270
2271fn string_array(value: &serde_json::Value, name: &str) -> Result<Vec<String>, WesleyError> {
2272    let serde_json::Value::Array(items) = value else {
2273        return operation_error(format!(
2274            "Footprint argument '{name}' must be a string array"
2275        ));
2276    };
2277
2278    let mut labels = Vec::new();
2279    let mut seen = BTreeSet::new();
2280    for item in items {
2281        let label = item
2282            .as_str()
2283            .map(|value| value.to_string())
2284            .ok_or_else(|| {
2285                operation_error_value(format!(
2286                    "Footprint argument '{name}' contains a non-string value"
2287                ))
2288            })?;
2289        if !seen.insert(label.clone()) {
2290            return operation_error(format!(
2291                "Footprint argument '{name}' contains duplicate label '{label}'"
2292            ));
2293        }
2294        labels.push(label);
2295    }
2296
2297    Ok(labels)
2298}
2299
2300fn variable_definition_types(
2301    op: &cst::OperationDefinition,
2302) -> Result<BTreeMap<String, TypeReference>, WesleyError> {
2303    let mut variables = BTreeMap::new();
2304
2305    let Some(variable_definitions) = op.variable_definitions() else {
2306        return Ok(variables);
2307    };
2308
2309    for variable in variable_definitions.variable_definitions() {
2310        let name = variable
2311            .variable()
2312            .and_then(|variable| variable.name())
2313            .map(|name| name.text().to_string())
2314            .ok_or_else(|| operation_error_value("Variable definition missing name".to_string()))?;
2315        let type_node = variable.ty().ok_or_else(|| {
2316            operation_error_value(format!("Variable definition '{name}' missing type"))
2317        })?;
2318        let type_ref = type_reference_from_type(type_node, true)?;
2319
2320        if variables.insert(name.clone(), type_ref).is_some() {
2321            return operation_error(format!("Duplicate variable definition '${name}'"));
2322        }
2323    }
2324
2325    Ok(variables)
2326}
2327
2328fn root_argument_bindings(
2329    root_field: &cst::Field,
2330    schema_operation: &SchemaOperation,
2331    variable_types: &BTreeMap<String, TypeReference>,
2332    schema: &SchemaIndex<'_>,
2333) -> Result<Vec<RootArgumentBinding>, WesleyError> {
2334    let expected_arguments = schema_operation
2335        .arguments
2336        .iter()
2337        .map(|argument| (argument.name.as_str(), argument))
2338        .collect::<BTreeMap<_, _>>();
2339    let mut supplied = BTreeSet::new();
2340    let mut bindings = Vec::new();
2341
2342    if let Some(arguments) = root_field.arguments() {
2343        for argument in arguments.arguments() {
2344            let name = required_name(argument.name(), "Root field argument missing name")?;
2345            if !supplied.insert(name.clone()) {
2346                return operation_error(format!(
2347                    "Operation artifact root field '{}' declares duplicate argument '{name}'",
2348                    schema_operation.field_name
2349                ));
2350            }
2351
2352            let expected = expected_arguments.get(name.as_str()).ok_or_else(|| {
2353                operation_error_value(format!(
2354                    "Operation artifact root field '{}' declares unknown argument '{name}'",
2355                    schema_operation.field_name
2356                ))
2357            })?;
2358            let value = argument.value().ok_or_else(|| {
2359                operation_error_value(format!("Root field argument '{name}' missing value"))
2360            })?;
2361
2362            validate_root_argument_value(value.clone(), expected, variable_types, schema)?;
2363            let value_canonical_json = stable_json_string(
2364                &executable_value_to_json(value)?,
2365                "operation artifact argument",
2366            )?;
2367            bindings.push(RootArgumentBinding {
2368                name,
2369                type_ref: expected.r#type.clone(),
2370                value_canonical_json,
2371            });
2372        }
2373    }
2374
2375    for expected in &schema_operation.arguments {
2376        if !expected.r#type.nullable
2377            && expected.default_value.is_none()
2378            && !supplied.contains(&expected.name)
2379        {
2380            return operation_error(format!(
2381                "Operation artifact root field '{}' missing required argument '{}'",
2382                schema_operation.field_name, expected.name
2383            ));
2384        }
2385    }
2386
2387    bindings.sort_by(|left, right| left.name.cmp(&right.name));
2388    Ok(bindings)
2389}
2390
2391fn selection_argument_bindings(
2392    root_field: &cst::Field,
2393    result_type: &TypeReference,
2394    schema: &SchemaIndex<'_>,
2395    fragments: &BTreeMap<String, cst::FragmentDefinition>,
2396    variable_types: &BTreeMap<String, TypeReference>,
2397) -> Result<Vec<SelectionArgumentBinding>, WesleyError> {
2398    let mut bindings = Vec::new();
2399    let context = SelectionArgumentBindingContext {
2400        schema,
2401        fragments,
2402        variable_types,
2403    };
2404
2405    if let Some(selection_set) = root_field.selection_set() {
2406        collect_selection_argument_bindings(
2407            &selection_set,
2408            &result_type.base,
2409            &context,
2410            &mut Vec::new(),
2411            "",
2412            &mut bindings,
2413        )?;
2414    }
2415
2416    Ok(bindings)
2417}
2418
2419struct SelectionArgumentBindingContext<'a> {
2420    schema: &'a SchemaIndex<'a>,
2421    fragments: &'a BTreeMap<String, cst::FragmentDefinition>,
2422    variable_types: &'a BTreeMap<String, TypeReference>,
2423}
2424
2425fn collect_selection_argument_bindings(
2426    selection_set: &cst::SelectionSet,
2427    parent_type: &str,
2428    context: &SelectionArgumentBindingContext<'_>,
2429    active_fragments: &mut Vec<String>,
2430    prefix: &str,
2431    bindings: &mut Vec<SelectionArgumentBinding>,
2432) -> Result<(), WesleyError> {
2433    for selection in selection_set.selections() {
2434        match selection {
2435            cst::Selection::Field(field) => {
2436                let field_name = required_name(field.name(), "Field selection missing name")?;
2437                let response_name = response_field_name(&field)?;
2438                let schema_field = context.schema.field(parent_type, &field_name)?;
2439                let path = if prefix.is_empty() {
2440                    response_name
2441                } else {
2442                    format!("{prefix}.{response_name}")
2443                };
2444
2445                append_field_argument_bindings(
2446                    &field,
2447                    &path,
2448                    schema_field,
2449                    context.variable_types,
2450                    context.schema,
2451                    bindings,
2452                )?;
2453
2454                if let Some(nested_selection_set) = field.selection_set() {
2455                    let nested_parent = schema_field.r#type.base.as_str();
2456                    context.schema.require_type(nested_parent)?;
2457                    collect_selection_argument_bindings(
2458                        &nested_selection_set,
2459                        nested_parent,
2460                        context,
2461                        active_fragments,
2462                        &path,
2463                        bindings,
2464                    )?;
2465                }
2466            }
2467            cst::Selection::FragmentSpread(spread) => {
2468                let name = spread
2469                    .fragment_name()
2470                    .and_then(|fragment_name| fragment_name.name())
2471                    .map(|name| name.text().to_string())
2472                    .ok_or_else(|| {
2473                        operation_error_value("Fragment spread missing name".to_string())
2474                    })?;
2475
2476                if active_fragments.contains(&name) {
2477                    return operation_error(format!(
2478                        "Cyclic fragment spread detected for fragment '{name}'"
2479                    ));
2480                }
2481
2482                let fragment = context.fragments.get(&name).ok_or_else(|| {
2483                    operation_error_value(format!("Unknown fragment spread '{name}'"))
2484                })?;
2485                let fragment_parent = fragment_type_condition(fragment)?;
2486                validate_fragment_type_condition(
2487                    parent_type,
2488                    &fragment_parent,
2489                    context.schema,
2490                    &name,
2491                )?;
2492
2493                active_fragments.push(name);
2494                if let Some(fragment_selection_set) = fragment.selection_set() {
2495                    collect_selection_argument_bindings(
2496                        &fragment_selection_set,
2497                        &fragment_parent,
2498                        context,
2499                        active_fragments,
2500                        prefix,
2501                        bindings,
2502                    )?;
2503                }
2504                active_fragments.pop();
2505            }
2506            cst::Selection::InlineFragment(fragment) => {
2507                let inline_parent = if let Some(type_condition) = fragment.type_condition() {
2508                    named_type_name(type_condition.named_type(), "Inline fragment missing type")?
2509                } else {
2510                    parent_type.to_string()
2511                };
2512                validate_fragment_type_condition(
2513                    parent_type,
2514                    &inline_parent,
2515                    context.schema,
2516                    "inline",
2517                )?;
2518
2519                if let Some(inline_selection_set) = fragment.selection_set() {
2520                    collect_selection_argument_bindings(
2521                        &inline_selection_set,
2522                        &inline_parent,
2523                        context,
2524                        active_fragments,
2525                        prefix,
2526                        bindings,
2527                    )?;
2528                }
2529            }
2530        }
2531    }
2532
2533    Ok(())
2534}
2535
2536fn append_field_argument_bindings(
2537    field: &cst::Field,
2538    path: &str,
2539    schema_field: &Field,
2540    variable_types: &BTreeMap<String, TypeReference>,
2541    schema: &SchemaIndex<'_>,
2542    bindings: &mut Vec<SelectionArgumentBinding>,
2543) -> Result<(), WesleyError> {
2544    let expected_arguments = schema_field
2545        .arguments
2546        .iter()
2547        .map(|argument| (argument.name.as_str(), argument))
2548        .collect::<BTreeMap<_, _>>();
2549    let mut supplied = BTreeSet::new();
2550    let mut field_bindings = Vec::new();
2551
2552    if let Some(arguments) = field.arguments() {
2553        for argument in arguments.arguments() {
2554            let name = required_name(argument.name(), "Field argument missing name")?;
2555            if !supplied.insert(name.clone()) {
2556                return operation_error(format!(
2557                    "Operation artifact field '{path}' declares duplicate argument '{name}'"
2558                ));
2559            }
2560
2561            let expected = expected_arguments.get(name.as_str()).ok_or_else(|| {
2562                operation_error_value(format!(
2563                    "Operation artifact field '{path}' declares unknown argument '{name}'"
2564                ))
2565            })?;
2566            let value = argument.value().ok_or_else(|| {
2567                operation_error_value(format!("Field argument '{name}' missing value"))
2568            })?;
2569
2570            validate_field_argument_value(value.clone(), expected, variable_types, schema)?;
2571            let value_canonical_json = stable_json_string(
2572                &executable_value_to_json(value)?,
2573                "operation artifact selection argument",
2574            )?;
2575            field_bindings.push(SelectionArgumentBinding {
2576                path: path.to_string(),
2577                name,
2578                type_ref: expected.r#type.clone(),
2579                value_canonical_json,
2580            });
2581        }
2582    }
2583
2584    for expected in &schema_field.arguments {
2585        if !expected.r#type.nullable
2586            && expected.default_value.is_none()
2587            && !supplied.contains(&expected.name)
2588        {
2589            return operation_error(format!(
2590                "Operation artifact field '{path}' missing required argument '{}'",
2591                expected.name
2592            ));
2593        }
2594    }
2595
2596    field_bindings.sort_by(|left, right| left.name.cmp(&right.name));
2597    bindings.extend(field_bindings);
2598    Ok(())
2599}
2600
2601fn validate_root_argument_value(
2602    value: cst::Value,
2603    expected: &OperationArgument,
2604    variable_types: &BTreeMap<String, TypeReference>,
2605    schema: &SchemaIndex<'_>,
2606) -> Result<(), WesleyError> {
2607    validate_argument_value(
2608        value,
2609        "Root argument",
2610        &expected.name,
2611        &expected.r#type,
2612        variable_types,
2613        schema,
2614    )
2615}
2616
2617fn validate_field_argument_value(
2618    value: cst::Value,
2619    expected: &FieldArgument,
2620    variable_types: &BTreeMap<String, TypeReference>,
2621    schema: &SchemaIndex<'_>,
2622) -> Result<(), WesleyError> {
2623    validate_argument_value(
2624        value,
2625        "Field argument",
2626        &expected.name,
2627        &expected.r#type,
2628        variable_types,
2629        schema,
2630    )
2631}
2632
2633fn validate_argument_value(
2634    value: cst::Value,
2635    argument_context: &str,
2636    argument_name: &str,
2637    expected_type: &TypeReference,
2638    variable_types: &BTreeMap<String, TypeReference>,
2639    schema: &SchemaIndex<'_>,
2640) -> Result<(), WesleyError> {
2641    match value {
2642        cst::Value::Variable(variable) => {
2643            let variable_name = variable
2644                .name()
2645                .map(|name| name.text().to_string())
2646                .ok_or_else(|| operation_error_value("Variable reference missing name".into()))?;
2647            let variable_type = variable_types.get(&variable_name).ok_or_else(|| {
2648                operation_error_value(format!(
2649                    "{argument_context} '{argument_name}' references undefined variable '${variable_name}'"
2650                ))
2651            })?;
2652
2653            if !variable_type_is_compatible(variable_type, expected_type) {
2654                return operation_error(format!(
2655                    "Variable '${variable_name}' has type '{}' but argument '{}' expects '{}'",
2656                    display_type_ref(variable_type),
2657                    argument_name,
2658                    display_type_ref(expected_type)
2659                ));
2660            }
2661
2662            Ok(())
2663        }
2664        literal => validate_literal_value(
2665            literal,
2666            argument_context,
2667            argument_name,
2668            expected_type,
2669            schema,
2670        ),
2671    }
2672}
2673
2674fn validate_literal_value(
2675    value: cst::Value,
2676    argument_context: &str,
2677    argument_name: &str,
2678    expected_type: &TypeReference,
2679    schema: &SchemaIndex<'_>,
2680) -> Result<(), WesleyError> {
2681    match value {
2682        cst::Value::NullValue(_) => {
2683            if expected_type.nullable {
2684                Ok(())
2685            } else {
2686                operation_error(format!(
2687                    "{argument_context} '{argument_name}' is non-null but received null",
2688                ))
2689            }
2690        }
2691        cst::Value::StringValue(_) => validate_named_scalar_literal(
2692            argument_context,
2693            argument_name,
2694            "String",
2695            expected_type,
2696            schema,
2697        ),
2698        cst::Value::IntValue(_) => {
2699            if expected_type.base == "Float" && !is_list_type(expected_type) {
2700                Ok(())
2701            } else {
2702                validate_named_scalar_literal(
2703                    argument_context,
2704                    argument_name,
2705                    "Int",
2706                    expected_type,
2707                    schema,
2708                )
2709            }
2710        }
2711        cst::Value::FloatValue(_) => validate_named_scalar_literal(
2712            argument_context,
2713            argument_name,
2714            "Float",
2715            expected_type,
2716            schema,
2717        ),
2718        cst::Value::BooleanValue(_) => validate_named_scalar_literal(
2719            argument_context,
2720            argument_name,
2721            "Boolean",
2722            expected_type,
2723            schema,
2724        ),
2725        cst::Value::EnumValue(value) => validate_enum_literal(
2726            value,
2727            argument_context,
2728            argument_name,
2729            expected_type,
2730            schema,
2731        ),
2732        cst::Value::ListValue(list) => {
2733            if !is_list_type(expected_type) {
2734                return literal_type_error(argument_context, argument_name, "List", expected_type);
2735            }
2736            let item_type = list_item_type_ref(expected_type);
2737            for item in list.values() {
2738                validate_literal_value(item, argument_context, argument_name, &item_type, schema)?;
2739            }
2740            Ok(())
2741        }
2742        cst::Value::ObjectValue(object) => validate_object_literal(
2743            object,
2744            argument_context,
2745            argument_name,
2746            expected_type,
2747            schema,
2748        ),
2749        cst::Value::Variable(_) => operation_error(format!(
2750            "{argument_context} '{argument_name}' received nested variable value"
2751        )),
2752    }
2753}
2754
2755fn validate_named_scalar_literal(
2756    argument_context: &str,
2757    argument_name: &str,
2758    actual: &str,
2759    expected_type: &TypeReference,
2760    schema: &SchemaIndex<'_>,
2761) -> Result<(), WesleyError> {
2762    let builtin_matches = match actual {
2763        "String" => matches!(expected_type.base.as_str(), "String" | "ID"),
2764        "Int" => matches!(expected_type.base.as_str(), "Int" | "Float"),
2765        "Float" => expected_type.base == "Float",
2766        "Boolean" => expected_type.base == "Boolean",
2767        _ => false,
2768    };
2769
2770    if builtin_matches && !is_list_type(expected_type) {
2771        return Ok(());
2772    }
2773
2774    if schema.type_kind(&expected_type.base) == Some(TypeKind::Scalar)
2775        && !is_builtin_scalar(&expected_type.base)
2776        && !is_list_type(expected_type)
2777    {
2778        return Ok(());
2779    }
2780
2781    literal_type_error(argument_context, argument_name, actual, expected_type)
2782}
2783
2784fn validate_enum_literal(
2785    value: cst::EnumValue,
2786    argument_context: &str,
2787    argument_name: &str,
2788    expected_type: &TypeReference,
2789    schema: &SchemaIndex<'_>,
2790) -> Result<(), WesleyError> {
2791    let name = value
2792        .name()
2793        .map(|name| name.text().to_string())
2794        .ok_or_else(|| operation_error_value("Enum argument value missing name".into()))?;
2795
2796    if is_list_type(expected_type) {
2797        return literal_type_error(argument_context, argument_name, "Enum", expected_type);
2798    }
2799
2800    match schema.type_kind(&expected_type.base) {
2801        Some(TypeKind::Enum) => {
2802            let type_def = schema.require_type(&expected_type.base)?;
2803            if type_def.enum_values.contains(&name) {
2804                Ok(())
2805            } else {
2806                operation_error(format!(
2807                    "{argument_context} '{argument_name}' received unknown enum value '{name}' for '{}'",
2808                    expected_type.base
2809                ))
2810            }
2811        }
2812        Some(TypeKind::Scalar) if !is_builtin_scalar(&expected_type.base) => Ok(()),
2813        _ => literal_type_error(argument_context, argument_name, "Enum", expected_type),
2814    }
2815}
2816
2817fn validate_object_literal(
2818    object: cst::ObjectValue,
2819    argument_context: &str,
2820    argument_name: &str,
2821    expected_type: &TypeReference,
2822    schema: &SchemaIndex<'_>,
2823) -> Result<(), WesleyError> {
2824    if is_list_type(expected_type) {
2825        return literal_type_error(argument_context, argument_name, "Object", expected_type);
2826    }
2827
2828    let type_def = schema.require_type(&expected_type.base)?;
2829    if type_def.kind == TypeKind::Scalar && !is_builtin_scalar(&expected_type.base) {
2830        return Ok(());
2831    }
2832    if type_def.kind != TypeKind::InputObject {
2833        return literal_type_error(argument_context, argument_name, "Object", expected_type);
2834    }
2835
2836    let expected_fields = type_def
2837        .fields
2838        .iter()
2839        .map(|field| (field.name.as_str(), field))
2840        .collect::<BTreeMap<_, _>>();
2841    let mut supplied = BTreeSet::new();
2842
2843    for field in object.object_fields() {
2844        let name = field
2845            .name()
2846            .map(|name| name.text().to_string())
2847            .ok_or_else(|| operation_error_value("Object argument field missing name".into()))?;
2848        if !supplied.insert(name.clone()) {
2849            return operation_error(format!(
2850                "{argument_context} '{argument_name}' declares duplicate input field '{name}'"
2851            ));
2852        }
2853
2854        let expected_field = expected_fields.get(name.as_str()).ok_or_else(|| {
2855            operation_error_value(format!(
2856                "{argument_context} '{argument_name}' declares unknown input field '{name}'"
2857            ))
2858        })?;
2859        let value = field.value().ok_or_else(|| {
2860            operation_error_value(format!("Object argument field '{name}' missing value"))
2861        })?;
2862        validate_literal_value(value, "Input field", &name, &expected_field.r#type, schema)?;
2863    }
2864
2865    for expected_field in &type_def.fields {
2866        if !expected_field.r#type.nullable
2867            && expected_field.default_value.is_none()
2868            && !supplied.contains(&expected_field.name)
2869        {
2870            return operation_error(format!(
2871                "{argument_context} '{argument_name}' missing required input field '{}'",
2872                expected_field.name
2873            ));
2874        }
2875    }
2876
2877    Ok(())
2878}
2879
2880fn executable_value_to_json(value: cst::Value) -> Result<serde_json::Value, WesleyError> {
2881    match value {
2882        cst::Value::Variable(variable) => {
2883            let name = variable
2884                .name()
2885                .map(|name| name.text().to_string())
2886                .ok_or_else(|| operation_error_value("Variable reference missing name".into()))?;
2887            Ok(serde_json::json!({ "$variable": name }))
2888        }
2889        cst::Value::StringValue(value) => Ok(serde_json::Value::String(String::from(value))),
2890        cst::Value::FloatValue(value) => {
2891            let raw = value
2892                .float_token()
2893                .map(|token| token.text().to_string())
2894                .unwrap_or_default();
2895            let parsed = raw.parse::<f64>().map_err(|err| {
2896                operation_error_value(format!("Invalid float argument value '{raw}': {err}"))
2897            })?;
2898            serde_json::Number::from_f64(parsed)
2899                .map(serde_json::Value::Number)
2900                .ok_or_else(|| {
2901                    operation_error_value(format!("Invalid finite float argument value '{raw}'"))
2902                })
2903        }
2904        cst::Value::IntValue(value) => {
2905            let raw = value
2906                .int_token()
2907                .map(|token| token.text().to_string())
2908                .unwrap_or_default();
2909            raw.parse::<i64>()
2910                .map(|parsed| serde_json::Value::Number(parsed.into()))
2911                .map_err(|err| {
2912                    operation_error_value(format!("Invalid integer argument value '{raw}': {err}"))
2913                })
2914        }
2915        cst::Value::BooleanValue(value) => Ok(serde_json::Value::Bool(
2916            value.true_token().is_some() && value.false_token().is_none(),
2917        )),
2918        cst::Value::NullValue(_) => Ok(serde_json::Value::Null),
2919        cst::Value::EnumValue(value) => {
2920            let name = value
2921                .name()
2922                .map(|name| name.text().to_string())
2923                .ok_or_else(|| operation_error_value("Enum argument value missing name".into()))?;
2924            Ok(serde_json::Value::String(name))
2925        }
2926        cst::Value::ListValue(list) => {
2927            let mut values = Vec::new();
2928            for value in list.values() {
2929                values.push(executable_value_to_json(value)?);
2930            }
2931            Ok(serde_json::Value::Array(values))
2932        }
2933        cst::Value::ObjectValue(object) => {
2934            let mut map = serde_json::Map::new();
2935            for field in object.object_fields() {
2936                let name = field
2937                    .name()
2938                    .map(|name| name.text().to_string())
2939                    .ok_or_else(|| {
2940                        operation_error_value("Object argument field missing name".into())
2941                    })?;
2942                let value = field.value().ok_or_else(|| {
2943                    operation_error_value(format!("Object argument field '{name}' missing value"))
2944                })?;
2945                map.insert(name, executable_value_to_json(value)?);
2946            }
2947            Ok(serde_json::Value::Object(map))
2948        }
2949    }
2950}
2951
2952fn literal_type_error(
2953    argument_context: &str,
2954    argument_name: &str,
2955    actual: &str,
2956    expected: &TypeReference,
2957) -> Result<(), WesleyError> {
2958    operation_error(format!(
2959        "{argument_context} '{argument_name}' received {actual} value but expects '{}'",
2960        display_type_ref(expected)
2961    ))
2962}
2963
2964fn variable_type_is_compatible(actual: &TypeReference, expected: &TypeReference) -> bool {
2965    actual.base == expected.base
2966        && actual.is_list == expected.is_list
2967        && actual.list_wrappers == expected.list_wrappers
2968        && (!actual.nullable || expected.nullable)
2969        && list_items_are_compatible(actual, expected)
2970}
2971
2972fn list_items_are_compatible(actual: &TypeReference, expected: &TypeReference) -> bool {
2973    if matches!(
2974        (actual.list_item_nullable, expected.list_item_nullable),
2975        (Some(true), Some(false))
2976    ) {
2977        return false;
2978    }
2979
2980    !matches!(
2981        (
2982            actual.leaf_nullable.or(actual.list_item_nullable),
2983            expected.leaf_nullable.or(expected.list_item_nullable),
2984        ),
2985        (Some(true), Some(false))
2986    )
2987}
2988
2989fn list_item_type_ref(type_ref: &TypeReference) -> TypeReference {
2990    if !type_ref.list_wrappers.is_empty() {
2991        let leaf_nullable = type_ref
2992            .leaf_nullable
2993            .unwrap_or_else(|| type_ref.list_item_nullable.unwrap_or(true));
2994        return type_ref_from_list_shape(
2995            type_ref.base.clone(),
2996            type_ref.list_wrappers[1..].to_vec(),
2997            leaf_nullable,
2998        );
2999    }
3000
3001    type_ref_from_list_shape(
3002        type_ref.base.clone(),
3003        Vec::new(),
3004        type_ref.list_item_nullable.unwrap_or(true),
3005    )
3006}
3007
3008fn type_ref_from_list_shape(
3009    base: String,
3010    list_wrappers: Vec<TypeListWrapper>,
3011    leaf_nullable: bool,
3012) -> TypeReference {
3013    if list_wrappers.is_empty() {
3014        return TypeReference {
3015            base,
3016            nullable: leaf_nullable,
3017            is_list: false,
3018            list_item_nullable: None,
3019            list_wrappers: Vec::new(),
3020            leaf_nullable: None,
3021        };
3022    }
3023
3024    let list_item_nullable = Some(
3025        list_wrappers
3026            .get(1)
3027            .map(|wrapper| wrapper.nullable)
3028            .unwrap_or(leaf_nullable),
3029    );
3030    let has_nested_lists = list_wrappers.len() > 1;
3031
3032    TypeReference {
3033        base,
3034        nullable: list_wrappers[0].nullable,
3035        is_list: true,
3036        list_item_nullable,
3037        list_wrappers: if has_nested_lists {
3038            list_wrappers
3039        } else {
3040            Vec::new()
3041        },
3042        leaf_nullable: if has_nested_lists {
3043            Some(leaf_nullable)
3044        } else {
3045            None
3046        },
3047    }
3048}
3049
3050fn display_type_ref(type_ref: &TypeReference) -> String {
3051    let mut rendered = if type_ref.is_list {
3052        format!(
3053            "[{}{}]",
3054            type_ref.base,
3055            if type_ref.list_item_nullable == Some(false) {
3056                "!"
3057            } else {
3058                ""
3059            }
3060        )
3061    } else {
3062        type_ref.base.clone()
3063    };
3064
3065    if !type_ref.nullable {
3066        rendered.push('!');
3067    }
3068
3069    rendered
3070}
3071
3072fn is_builtin_scalar(name: &str) -> bool {
3073    matches!(name, "ID" | "String" | "Int" | "Float" | "Boolean")
3074}
3075
3076fn variable_codec_shape(
3077    op: &cst::OperationDefinition,
3078    schema_operation: &SchemaOperation,
3079) -> Result<CodecShape, WesleyError> {
3080    let type_name = op
3081        .name()
3082        .map(|name| format!("{}Variables", name.text()))
3083        .unwrap_or_else(|| format!("{}Variables", schema_operation.field_name));
3084
3085    let fields = if let Some(variable_definitions) = op.variable_definitions() {
3086        variable_definitions
3087            .variable_definitions()
3088            .map(variable_codec_field)
3089            .collect::<Result<Vec<_>, _>>()?
3090    } else {
3091        schema_operation
3092            .arguments
3093            .iter()
3094            .map(|argument| CodecField {
3095                name: argument.name.clone(),
3096                type_ref: argument.r#type.clone(),
3097                required: argument.default_value.is_none() && !argument.r#type.nullable,
3098                list: is_list_type(&argument.r#type),
3099            })
3100            .collect()
3101    };
3102
3103    Ok(CodecShape { type_name, fields })
3104}
3105
3106fn variable_codec_field(variable: cst::VariableDefinition) -> Result<CodecField, WesleyError> {
3107    let name = variable
3108        .variable()
3109        .and_then(|variable| variable.name())
3110        .map(|name| name.text().to_string())
3111        .ok_or_else(|| operation_error_value("Variable definition missing name".to_string()))?;
3112    let type_node = variable.ty().ok_or_else(|| {
3113        operation_error_value(format!("Variable definition '{name}' missing type"))
3114    })?;
3115    let type_ref = type_reference_from_type(type_node, true)?;
3116
3117    Ok(CodecField {
3118        name,
3119        required: variable.default_value().is_none() && !type_ref.nullable,
3120        list: is_list_type(&type_ref),
3121        type_ref,
3122    })
3123}
3124
3125fn payload_codec_shape(
3126    root_field: &cst::Field,
3127    result_type: &TypeReference,
3128    schema: &SchemaIndex<'_>,
3129    fragments: &BTreeMap<String, cst::FragmentDefinition>,
3130) -> Result<CodecShape, WesleyError> {
3131    let mut fields = Vec::new();
3132    let context = PayloadCodecContext { schema, fragments };
3133
3134    if let Some(selection_set) = root_field.selection_set() {
3135        collect_payload_codec_fields(
3136            &selection_set,
3137            &result_type.base,
3138            &context,
3139            &mut Vec::new(),
3140            "",
3141            !result_type.nullable,
3142            &mut fields,
3143        )?;
3144    }
3145
3146    Ok(CodecShape {
3147        type_name: result_type.base.to_string(),
3148        fields,
3149    })
3150}
3151
3152struct PayloadCodecContext<'a> {
3153    schema: &'a SchemaIndex<'a>,
3154    fragments: &'a BTreeMap<String, cst::FragmentDefinition>,
3155}
3156
3157fn collect_payload_codec_fields(
3158    selection_set: &cst::SelectionSet,
3159    parent_type: &str,
3160    context: &PayloadCodecContext<'_>,
3161    active_fragments: &mut Vec<String>,
3162    prefix: &str,
3163    parent_path_required: bool,
3164    fields: &mut Vec<CodecField>,
3165) -> Result<(), WesleyError> {
3166    for selection in selection_set.selections() {
3167        match selection {
3168            cst::Selection::Field(field) => {
3169                let field_name = required_name(field.name(), "Field selection missing name")?;
3170                let response_name = response_field_name(&field)?;
3171                let schema_field = context.schema.field(parent_type, &field_name)?;
3172                let path = if prefix.is_empty() {
3173                    response_name
3174                } else {
3175                    format!("{prefix}.{response_name}")
3176                };
3177                let field_required = parent_path_required && !schema_field.r#type.nullable;
3178
3179                push_unique_codec_field(
3180                    fields,
3181                    CodecField {
3182                        name: path.clone(),
3183                        type_ref: schema_field.r#type.clone(),
3184                        required: field_required,
3185                        list: is_list_type(&schema_field.r#type),
3186                    },
3187                );
3188
3189                if let Some(nested_selection_set) = field.selection_set() {
3190                    let nested_parent = schema_field.r#type.base.as_str();
3191                    context.schema.require_type(nested_parent)?;
3192                    collect_payload_codec_fields(
3193                        &nested_selection_set,
3194                        nested_parent,
3195                        context,
3196                        active_fragments,
3197                        &path,
3198                        field_required,
3199                        fields,
3200                    )?;
3201                }
3202            }
3203            cst::Selection::FragmentSpread(spread) => {
3204                let name = spread
3205                    .fragment_name()
3206                    .and_then(|fragment_name| fragment_name.name())
3207                    .map(|name| name.text().to_string())
3208                    .ok_or_else(|| {
3209                        operation_error_value("Fragment spread missing name".to_string())
3210                    })?;
3211
3212                if active_fragments.contains(&name) {
3213                    return operation_error(format!(
3214                        "Cyclic fragment spread detected for fragment '{name}'"
3215                    ));
3216                }
3217
3218                let fragment = context.fragments.get(&name).ok_or_else(|| {
3219                    operation_error_value(format!("Unknown fragment spread '{name}'"))
3220                })?;
3221                let fragment_parent = fragment_type_condition(fragment)?;
3222                validate_fragment_type_condition(
3223                    parent_type,
3224                    &fragment_parent,
3225                    context.schema,
3226                    &name,
3227                )?;
3228
3229                active_fragments.push(name);
3230                if let Some(fragment_selection_set) = fragment.selection_set() {
3231                    collect_payload_codec_fields(
3232                        &fragment_selection_set,
3233                        &fragment_parent,
3234                        context,
3235                        active_fragments,
3236                        prefix,
3237                        parent_path_required,
3238                        fields,
3239                    )?;
3240                }
3241                active_fragments.pop();
3242            }
3243            cst::Selection::InlineFragment(fragment) => {
3244                let inline_parent = if let Some(type_condition) = fragment.type_condition() {
3245                    named_type_name(type_condition.named_type(), "Inline fragment missing type")?
3246                } else {
3247                    parent_type.to_string()
3248                };
3249                validate_fragment_type_condition(
3250                    parent_type,
3251                    &inline_parent,
3252                    context.schema,
3253                    "inline",
3254                )?;
3255
3256                if let Some(inline_selection_set) = fragment.selection_set() {
3257                    collect_payload_codec_fields(
3258                        &inline_selection_set,
3259                        &inline_parent,
3260                        context,
3261                        active_fragments,
3262                        prefix,
3263                        parent_path_required,
3264                        fields,
3265                    )?;
3266                }
3267            }
3268        }
3269    }
3270
3271    Ok(())
3272}
3273
3274fn response_field_name(field: &cst::Field) -> Result<String, WesleyError> {
3275    field
3276        .alias()
3277        .and_then(|alias| alias.name())
3278        .or_else(|| field.name())
3279        .map(|name| name.text().to_string())
3280        .ok_or_else(|| operation_error_value("Field selection missing response name".into()))
3281}
3282
3283fn push_unique_codec_field(fields: &mut Vec<CodecField>, field: CodecField) {
3284    if !fields.iter().any(|existing| existing.name == field.name) {
3285        fields.push(field);
3286    }
3287}
3288
3289fn law_claims_for_operation(
3290    operation_id: &str,
3291    directives: &[DirectiveRecord],
3292    footprint: Option<&Footprint>,
3293) -> Result<Vec<LawClaimTemplate>, WesleyError> {
3294    let mut claims = Vec::new();
3295    let mut seen = BTreeSet::new();
3296
3297    push_law_claim(
3298        &mut claims,
3299        &mut seen,
3300        operation_id,
3301        "operation.shape.valid.v1",
3302        vec![EvidenceKind::Compiler],
3303    );
3304    push_law_claim(
3305        &mut claims,
3306        &mut seen,
3307        operation_id,
3308        "operation.codec.canonical.v1",
3309        vec![EvidenceKind::Compiler, EvidenceKind::Codec],
3310    );
3311
3312    for law_id in law_ids_from_directives(directives)? {
3313        push_law_claim(
3314            &mut claims,
3315            &mut seen,
3316            operation_id,
3317            &law_id,
3318            vec![EvidenceKind::HostPolicy, EvidenceKind::DomainVerifier],
3319        );
3320    }
3321
3322    if footprint.is_some() {
3323        push_law_claim(
3324            &mut claims,
3325            &mut seen,
3326            operation_id,
3327            "operation.footprint.closed.v1",
3328            vec![EvidenceKind::RuntimeTrace],
3329        );
3330    }
3331
3332    Ok(claims)
3333}
3334
3335fn requirements_from_footprint(footprint: Option<&Footprint>) -> OperationRequirements {
3336    let mut required_permissions = Vec::new();
3337    let mut forbidden_resources = Vec::new();
3338
3339    if let Some(footprint) = footprint {
3340        for resource in &footprint.reads {
3341            required_permissions.push(PermissionRequirement {
3342                action: PermissionAction::Read,
3343                resource: resource.clone(),
3344                source: "wes_footprint.reads".to_string(),
3345            });
3346        }
3347
3348        for resource in &footprint.writes {
3349            required_permissions.push(PermissionRequirement {
3350                action: PermissionAction::Write,
3351                resource: resource.clone(),
3352                source: "wes_footprint.writes".to_string(),
3353            });
3354        }
3355
3356        forbidden_resources = footprint.forbids.clone();
3357    }
3358
3359    OperationRequirements {
3360        identity: IdentityRequirement {
3361            required: true,
3362            accepted_principal_kinds: Vec::new(),
3363        },
3364        required_permissions,
3365        forbidden_resources,
3366    }
3367}
3368
3369fn push_law_claim(
3370    claims: &mut Vec<LawClaimTemplate>,
3371    seen: &mut BTreeSet<String>,
3372    operation_id: &str,
3373    law_id: &str,
3374    required_evidence: Vec<EvidenceKind>,
3375) {
3376    if !seen.insert(law_id.to_string()) {
3377        return;
3378    }
3379
3380    let claim_id = compute_content_hash(&format!("law-claim:{operation_id}:{law_id}"));
3381    claims.push(LawClaimTemplate {
3382        law_id: law_id.to_string(),
3383        claim_id,
3384        operation_id: operation_id.to_string(),
3385        required_evidence,
3386    });
3387}
3388
3389fn law_ids_from_directives(directives: &[DirectiveRecord]) -> Result<Vec<String>, WesleyError> {
3390    let mut law_ids = Vec::new();
3391
3392    for directive in directives
3393        .iter()
3394        .filter(|directive| directive.name == "wes_law")
3395    {
3396        let arguments: serde_json::Value =
3397            serde_json::from_str(&directive.arguments_canonical_json).map_err(|err| {
3398                operation_error_value(format!("Invalid canonical law arguments: {err}"))
3399            })?;
3400        let law_id = arguments
3401            .get("id")
3402            .and_then(serde_json::Value::as_str)
3403            .ok_or_else(|| {
3404                operation_error_value("Directive 'wes_law' requires string argument 'id'".into())
3405            })?;
3406        law_ids.push(law_id.to_string());
3407    }
3408
3409    Ok(law_ids)
3410}
3411
3412fn is_list_type(type_ref: &TypeReference) -> bool {
3413    type_ref.is_list || !type_ref.list_wrappers.is_empty()
3414}
3415
3416fn stable_json_hash<T: serde::Serialize>(value: &T, area: &str) -> Result<String, WesleyError> {
3417    let canonical = stable_json_string(value, area)?;
3418    Ok(compute_content_hash(&canonical))
3419}
3420
3421fn canonical_requirements_artifact<T: serde::Serialize>(
3422    value: &T,
3423) -> Result<OperationRequirementsArtifact, WesleyError> {
3424    let canonical = stable_json_string(value, "operation artifact requirements artifact")?;
3425    let bytes = canonical.into_bytes();
3426    let digest = compute_content_hash_bytes(&bytes);
3427
3428    Ok(OperationRequirementsArtifact {
3429        digest,
3430        codec: OPERATION_REQUIREMENTS_ARTIFACT_CODEC.to_string(),
3431        bytes,
3432    })
3433}
3434
3435fn stable_json_string<T: serde::Serialize>(value: &T, area: &str) -> Result<String, WesleyError> {
3436    to_canonical_json(value)
3437        .map_err(|err| lowering_error_value(area, format!("Failed to serialize JSON: {err}")))
3438}
3439
3440struct SchemaIndex<'a> {
3441    types: HashMap<&'a str, &'a TypeDefinition>,
3442}
3443
3444impl<'a> SchemaIndex<'a> {
3445    fn new(ir: &'a WesleyIR) -> Self {
3446        let types = ir
3447            .types
3448            .iter()
3449            .map(|type_def| (type_def.name.as_str(), type_def))
3450            .collect::<HashMap<_, _>>();
3451        Self { types }
3452    }
3453
3454    fn require_type(&self, name: &str) -> Result<&'a TypeDefinition, WesleyError> {
3455        self.types
3456            .get(name)
3457            .copied()
3458            .ok_or_else(|| operation_error_value(format!("Unknown selection parent type '{name}'")))
3459    }
3460
3461    fn type_kind(&self, name: &str) -> Option<TypeKind> {
3462        self.types.get(name).map(|type_def| type_def.kind)
3463    }
3464
3465    fn possible_runtime_types(&self, name: &str) -> Result<BTreeSet<String>, WesleyError> {
3466        let type_def = self.require_type(name)?;
3467        match type_def.kind {
3468            TypeKind::Object => Ok(BTreeSet::from([name.to_string()])),
3469            TypeKind::Interface => Ok(self
3470                .types
3471                .values()
3472                .filter(|candidate| {
3473                    candidate.kind == TypeKind::Object
3474                        && candidate
3475                            .implements
3476                            .iter()
3477                            .any(|interface| interface == name)
3478                })
3479                .map(|candidate| candidate.name.clone())
3480                .collect()),
3481            TypeKind::Union => Ok(type_def.union_members.iter().cloned().collect()),
3482            _ => operation_error(format!("Type '{name}' is not a composite fragment parent")),
3483        }
3484    }
3485
3486    fn field(&self, parent_type: &str, field_name: &str) -> Result<&'a Field, WesleyError> {
3487        let type_def = self.require_type(parent_type)?;
3488        type_def
3489            .fields
3490            .iter()
3491            .find(|field| field.name == field_name)
3492            .ok_or_else(|| {
3493                operation_error_value(format!(
3494                    "Type '{parent_type}' does not define selected field '{field_name}'"
3495                ))
3496            })
3497    }
3498}
3499
3500struct RootTypes {
3501    query: String,
3502    mutation: String,
3503    subscription: String,
3504}
3505
3506impl Default for RootTypes {
3507    fn default() -> Self {
3508        Self {
3509            query: "Query".to_string(),
3510            mutation: "Mutation".to_string(),
3511            subscription: "Subscription".to_string(),
3512        }
3513    }
3514}
3515
3516impl RootTypes {
3517    fn operation_types_for_type(&self, type_name: &str) -> Vec<OperationType> {
3518        let mut operation_types = Vec::new();
3519
3520        if self.query == type_name {
3521            operation_types.push(OperationType::Query);
3522        }
3523        if self.mutation == type_name {
3524            operation_types.push(OperationType::Mutation);
3525        }
3526        if self.subscription == type_name {
3527            operation_types.push(OperationType::Subscription);
3528        }
3529
3530        operation_types
3531    }
3532
3533    fn root_for_operation(&self, op: &cst::OperationDefinition) -> Result<&str, WesleyError> {
3534        let Some(operation_type) = op.operation_type() else {
3535            return Ok(self.query.as_str());
3536        };
3537
3538        if operation_type.query_token().is_some() {
3539            Ok(self.query.as_str())
3540        } else if operation_type.mutation_token().is_some() {
3541            Ok(self.mutation.as_str())
3542        } else if operation_type.subscription_token().is_some() {
3543            Ok(self.subscription.as_str())
3544        } else {
3545            operation_error("Unknown GraphQL operation type".to_string())
3546        }
3547    }
3548}
3549
3550fn extract_root_types(schema_sdl: &str) -> Result<RootTypes, WesleyError> {
3551    let parser = Parser::new(schema_sdl);
3552    let cst = parser.parse();
3553
3554    let errors = cst.errors().collect::<Vec<_>>();
3555    if !errors.is_empty() {
3556        let err = &errors[0];
3557        return Err(parse_error_from_apollo(schema_sdl, err));
3558    }
3559
3560    let mut root_types = RootTypes::default();
3561
3562    for def in cst.document().definitions() {
3563        match def {
3564            cst::Definition::SchemaDefinition(schema) => {
3565                update_root_types(schema.root_operation_type_definitions(), &mut root_types)?;
3566            }
3567            cst::Definition::SchemaExtension(schema) => {
3568                update_root_types(schema.root_operation_type_definitions(), &mut root_types)?;
3569            }
3570            _ => {}
3571        }
3572    }
3573
3574    Ok(root_types)
3575}
3576
3577fn collect_schema_operations_from_object(
3578    name: Option<cst::Name>,
3579    fields_definition: Option<cst::FieldsDefinition>,
3580    root_types: &RootTypes,
3581    repeatable_directives: &BTreeSet<String>,
3582    operations: &mut Vec<SchemaOperation>,
3583) -> Result<(), WesleyError> {
3584    let type_name = type_node_name(name, "Object type missing name")?;
3585    let operation_types = root_types.operation_types_for_type(&type_name);
3586    if operation_types.is_empty() {
3587        return Ok(());
3588    }
3589
3590    let Some(fields_definition) = fields_definition else {
3591        return Ok(());
3592    };
3593
3594    for field_def in fields_definition.field_definitions() {
3595        for operation_type in &operation_types {
3596            operations.push(schema_operation_from_field(
3597                *operation_type,
3598                &type_name,
3599                field_def.clone(),
3600                repeatable_directives,
3601            )?);
3602        }
3603    }
3604
3605    Ok(())
3606}
3607
3608fn schema_operation_from_field(
3609    operation_type: OperationType,
3610    root_type_name: &str,
3611    field_def: cst::FieldDefinition,
3612    repeatable_directives: &BTreeSet<String>,
3613) -> Result<SchemaOperation, WesleyError> {
3614    let field_name = field_def
3615        .name()
3616        .map(|name| name.text().to_string())
3617        .ok_or_else(|| {
3618            lowering_error_value("schema operation", "Root field missing name".into())
3619        })?;
3620    let result_type = field_def.ty().ok_or_else(|| {
3621        lowering_error_value(
3622            "schema operation",
3623            format!("Root field '{field_name}' missing result type"),
3624        )
3625    })?;
3626
3627    let mut directives = IndexMap::new();
3628    if let Some(dirs) = field_def.directives() {
3629        ApolloLoweringAdapter::extract_directives_with_repeatability(
3630            dirs,
3631            &mut directives,
3632            repeatable_directives,
3633        )?;
3634    }
3635
3636    Ok(SchemaOperation {
3637        operation_type,
3638        root_type_name: root_type_name.to_string(),
3639        field_name,
3640        arguments: operation_arguments_from_definition(
3641            field_def.arguments_definition(),
3642            repeatable_directives,
3643        )?,
3644        result_type: type_reference_from_type(result_type, true)?,
3645        directives,
3646    })
3647}
3648
3649fn operation_arguments_from_definition(
3650    arguments_definition: Option<cst::ArgumentsDefinition>,
3651    repeatable_directives: &BTreeSet<String>,
3652) -> Result<Vec<OperationArgument>, WesleyError> {
3653    let Some(arguments_definition) = arguments_definition else {
3654        return Ok(Vec::new());
3655    };
3656
3657    arguments_definition
3658        .input_value_definitions()
3659        .map(|input_value| operation_argument_from_input_value(input_value, repeatable_directives))
3660        .collect()
3661}
3662
3663fn operation_argument_from_input_value(
3664    input_value: cst::InputValueDefinition,
3665    repeatable_directives: &BTreeSet<String>,
3666) -> Result<OperationArgument, WesleyError> {
3667    let parts = argument_parts_from_input_value(
3668        input_value,
3669        "schema operation",
3670        "Operation argument missing name",
3671        |name| format!("Operation argument '{name}' missing type"),
3672        repeatable_directives,
3673    )?;
3674
3675    Ok(OperationArgument {
3676        name: parts.name,
3677        r#type: parts.r#type,
3678        default_value: parts.default_value,
3679        directives: parts.directives,
3680    })
3681}
3682
3683fn update_root_types(
3684    root_defs: cst::CstChildren<cst::RootOperationTypeDefinition>,
3685    root_types: &mut RootTypes,
3686) -> Result<(), WesleyError> {
3687    for root_def in root_defs {
3688        let operation_type = root_def.operation_type().ok_or_else(|| {
3689            operation_error_value("Schema root operation missing operation type".to_string())
3690        })?;
3691        let named_type = named_type_name(
3692            root_def.named_type(),
3693            "Schema root operation missing named type",
3694        )?;
3695
3696        if operation_type.query_token().is_some() {
3697            root_types.query = named_type;
3698        } else if operation_type.mutation_token().is_some() {
3699            root_types.mutation = named_type;
3700        } else if operation_type.subscription_token().is_some() {
3701            root_types.subscription = named_type;
3702        }
3703    }
3704
3705    Ok(())
3706}
3707
3708fn push_unique(values: &mut Vec<String>, value: String) {
3709    if !values.contains(&value) {
3710        values.push(value);
3711    }
3712}
3713
3714fn fragment_name(fragment: &cst::FragmentDefinition) -> Result<String, WesleyError> {
3715    fragment
3716        .fragment_name()
3717        .and_then(|fragment_name| fragment_name.name())
3718        .map(|name| name.text().to_string())
3719        .ok_or_else(|| operation_error_value("Fragment definition missing name".to_string()))
3720}
3721
3722fn fragment_type_condition(fragment: &cst::FragmentDefinition) -> Result<String, WesleyError> {
3723    let type_condition = fragment.type_condition().ok_or_else(|| {
3724        operation_error_value("Fragment definition missing type condition".to_string())
3725    })?;
3726    named_type_name(
3727        type_condition.named_type(),
3728        "Fragment definition missing type condition",
3729    )
3730}
3731
3732fn validate_fragment_type_condition(
3733    parent_type: &str,
3734    condition_type: &str,
3735    schema: &SchemaIndex<'_>,
3736    context: &str,
3737) -> Result<(), WesleyError> {
3738    let parent_possible = schema.possible_runtime_types(parent_type)?;
3739    let condition_possible = schema.possible_runtime_types(condition_type)?;
3740
3741    if parent_possible.is_disjoint(&condition_possible) {
3742        return operation_error(format!(
3743            "Fragment '{context}' type condition '{condition_type}' cannot apply to parent type '{parent_type}'"
3744        ));
3745    }
3746
3747    Ok(())
3748}
3749
3750fn named_type_name(name: Option<cst::NamedType>, message: &str) -> Result<String, WesleyError> {
3751    name.and_then(|named_type| named_type.name())
3752        .map(|name| name.text().to_string())
3753        .ok_or_else(|| operation_error_value(message.to_string()))
3754}
3755
3756fn required_name(name: Option<cst::Name>, message: &str) -> Result<String, WesleyError> {
3757    name.map(|name| name.text().to_string())
3758        .ok_or_else(|| operation_error_value(message.to_string()))
3759}
3760
3761fn operation_error<T>(message: String) -> Result<T, WesleyError> {
3762    Err(operation_error_value(message))
3763}
3764
3765fn operation_error_value(message: String) -> WesleyError {
3766    WesleyError::LoweringError {
3767        message,
3768        area: "operation".to_string(),
3769    }
3770}