1use 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
23pub struct ApolloLoweringAdapter {
25 _max_retries: usize,
26}
27
28impl ApolloLoweringAdapter {
29 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
44pub fn lower_schema_sdl(sdl: &str) -> Result<WesleyIR, WesleyError> {
46 ApolloLoweringAdapter::new(0).parse_and_lower(sdl)
47}
48
49pub 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
112pub 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
121pub 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
175struct 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
1186pub 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
1205pub 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
1234pub 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
1364pub 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
1376pub 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}