@@ -6,7 +6,10 @@ use std::{
66 hash:: Hash ,
77} ;
88
9- use crate :: utils:: pretty_display:: { get_indent, PrettyDisplay } ;
9+ use crate :: {
10+ ast:: merge_path:: { Condition , MergePath , Segment } ,
11+ utils:: pretty_display:: { get_indent, PrettyDisplay } ,
12+ } ;
1013
1114use super :: { arguments:: ArgumentsMap , selection_item:: SelectionItem } ;
1215
@@ -55,10 +58,24 @@ impl Display for SelectionSet {
5558}
5659
5760impl SelectionSet {
61+ pub fn cost ( & self ) -> u64 {
62+ let mut cost = 1 ;
63+
64+ for node in & self . items {
65+ cost += node. cost ( ) ;
66+ }
67+
68+ cost
69+ }
70+
5871 pub fn is_empty ( & self ) -> bool {
5972 self . items . is_empty ( )
6073 }
6174
75+ pub fn contains ( & self , other : & Self ) -> bool {
76+ selection_items_are_subset_of ( & self . items , & other. items )
77+ }
78+
6279 pub fn variable_usages ( & self ) -> BTreeSet < String > {
6380 self . items
6481 . iter ( )
@@ -343,6 +360,234 @@ impl PrettyDisplay for InlineFragmentSelection {
343360 }
344361}
345362
363+ pub fn selection_items_are_subset_of ( source : & [ SelectionItem ] , target : & [ SelectionItem ] ) -> bool {
364+ target. iter ( ) . all ( |target_node| {
365+ source
366+ . iter ( )
367+ . any ( |source_node| selection_item_is_subset_of ( source_node, target_node) )
368+ } )
369+ }
370+
371+ fn selection_item_is_subset_of ( source : & SelectionItem , target : & SelectionItem ) -> bool {
372+ match ( source, target) {
373+ ( SelectionItem :: Field ( source_field) , SelectionItem :: Field ( target_field) ) => {
374+ if source_field. name != target_field. name {
375+ return false ;
376+ }
377+
378+ if source_field. is_leaf ( ) != target_field. is_leaf ( ) {
379+ return false ;
380+ }
381+
382+ selection_items_are_subset_of (
383+ & source_field. selections . items ,
384+ & target_field. selections . items ,
385+ )
386+ }
387+ // TODO: support fragments
388+ _ => false ,
389+ }
390+ }
391+
392+ pub fn merge_selection_set ( target : & mut SelectionSet , source : & SelectionSet , as_first : bool ) {
393+ if source. items . is_empty ( ) {
394+ return ;
395+ }
396+
397+ let mut pending_items = Vec :: with_capacity ( source. items . len ( ) ) ;
398+ for source_item in source. items . iter ( ) {
399+ let mut found = false ;
400+ for target_item in target. items . iter_mut ( ) {
401+ match ( source_item, target_item) {
402+ ( SelectionItem :: Field ( source_field) , SelectionItem :: Field ( target_field) ) => {
403+ if source_field == target_field {
404+ found = true ;
405+ merge_selection_set (
406+ & mut target_field. selections ,
407+ & source_field. selections ,
408+ as_first,
409+ ) ;
410+ break ;
411+ }
412+ }
413+ (
414+ SelectionItem :: InlineFragment ( source_fragment) ,
415+ SelectionItem :: InlineFragment ( target_fragment) ,
416+ ) => {
417+ if source_fragment. type_condition == target_fragment. type_condition {
418+ found = true ;
419+ merge_selection_set (
420+ & mut target_fragment. selections ,
421+ & source_fragment. selections ,
422+ as_first,
423+ ) ;
424+ break ;
425+ }
426+ }
427+ _ => { }
428+ }
429+ }
430+
431+ if !found {
432+ pending_items. push ( source_item. clone ( ) )
433+ }
434+ }
435+
436+ if !pending_items. is_empty ( ) {
437+ if as_first {
438+ let mut new_items = pending_items;
439+ new_items. append ( & mut target. items ) ;
440+ target. items = new_items;
441+ } else {
442+ target. items . extend ( pending_items) ;
443+ }
444+ }
445+ }
446+
447+ pub fn find_selection_set_by_path_mut < ' a > (
448+ root_selection_set : & ' a mut SelectionSet ,
449+ path : & MergePath ,
450+ ) -> Option < & ' a mut SelectionSet > {
451+ let mut current_selection_set = root_selection_set;
452+
453+ for path_element in path. inner . iter ( ) {
454+ match path_element {
455+ Segment :: List => {
456+ continue ;
457+ }
458+ Segment :: Cast ( type_name, condition) => {
459+ let next_selection_set_option =
460+ current_selection_set
461+ . items
462+ . iter_mut ( )
463+ . find_map ( |item| match item {
464+ SelectionItem :: Field ( _) => None ,
465+ SelectionItem :: InlineFragment ( f) => {
466+ if f. type_condition . eq ( type_name)
467+ && fragment_condition_equal ( condition, f)
468+ {
469+ Some ( & mut f. selections )
470+ } else {
471+ None
472+ }
473+ }
474+ SelectionItem :: FragmentSpread ( _) => None ,
475+ } ) ;
476+
477+ match next_selection_set_option {
478+ Some ( next_set) => {
479+ current_selection_set = next_set;
480+ }
481+ None => {
482+ return None ;
483+ }
484+ }
485+ }
486+ Segment :: Field ( field_name, args_hash, condition) => {
487+ let next_selection_set_option =
488+ current_selection_set
489+ . items
490+ . iter_mut ( )
491+ . find_map ( |item| match item {
492+ SelectionItem :: Field ( field) => {
493+ if field. selection_identifier ( ) == field_name
494+ && field. arguments_hash ( ) == * args_hash
495+ && field_condition_equal ( condition, field)
496+ {
497+ Some ( & mut field. selections )
498+ } else {
499+ None
500+ }
501+ }
502+ SelectionItem :: InlineFragment ( ..) => None ,
503+ SelectionItem :: FragmentSpread ( _) => None ,
504+ } ) ;
505+
506+ match next_selection_set_option {
507+ Some ( next_set) => {
508+ current_selection_set = next_set;
509+ }
510+ None => {
511+ return None ;
512+ }
513+ }
514+ }
515+ }
516+ }
517+ Some ( current_selection_set)
518+ }
519+
520+ pub fn field_condition_equal ( cond : & Option < Condition > , field : & FieldSelection ) -> bool {
521+ match cond {
522+ Some ( cond) => match cond {
523+ Condition :: Include ( var_name) => {
524+ field. include_if . as_ref ( ) . is_some_and ( |v| v == var_name)
525+ }
526+ Condition :: Skip ( var_name) => field. skip_if . as_ref ( ) . is_some_and ( |v| v == var_name) ,
527+ } ,
528+ None => field. include_if . is_none ( ) && field. skip_if . is_none ( ) ,
529+ }
530+ }
531+
532+ fn fragment_condition_equal ( cond : & Option < Condition > , fragment : & InlineFragmentSelection ) -> bool {
533+ match cond {
534+ Some ( cond) => match cond {
535+ Condition :: Include ( var_name) => {
536+ fragment. include_if . as_ref ( ) . is_some_and ( |v| v == var_name)
537+ }
538+ Condition :: Skip ( var_name) => fragment. skip_if . as_ref ( ) . is_some_and ( |v| v == var_name) ,
539+ } ,
540+ None => fragment. include_if . is_none ( ) && fragment. skip_if . is_none ( ) ,
541+ }
542+ }
543+
544+ /// Find the arguments conflicts between two selections.
545+ /// Returns a vector of tuples containing the indices of conflicting fields in both "source" and "other"
546+ /// Both indices are returned in order to allow for easy resolution of conflicts later, in either side.
547+ pub fn find_arguments_conflicts (
548+ source : & SelectionSet ,
549+ other : & SelectionSet ,
550+ ) -> Vec < ( usize , usize ) > {
551+ other
552+ . items
553+ . iter ( )
554+ . enumerate ( )
555+ . filter_map ( |( index, other_selection) | {
556+ if let SelectionItem :: Field ( other_field) = other_selection {
557+ let other_identifier = other_field. selection_identifier ( ) ;
558+ let other_args_hash = other_field. arguments_hash ( ) ;
559+
560+ let existing_in_self =
561+ source
562+ . items
563+ . iter ( )
564+ . enumerate ( )
565+ . find_map ( |( self_index, self_selection) | {
566+ if let SelectionItem :: Field ( self_field) = self_selection {
567+ // If the field selection identifier matches and the arguments hash is different,
568+ // then it means that we can't merge the two input siblings
569+ if self_field. selection_identifier ( ) == other_identifier
570+ && self_field. arguments_hash ( ) != other_args_hash
571+ {
572+ return Some ( self_index) ;
573+ }
574+ }
575+
576+ None
577+ } ) ;
578+
579+ if let Some ( existing_index) = existing_in_self {
580+ return Some ( ( existing_index, index) ) ;
581+ }
582+
583+ return None ;
584+ }
585+
586+ None
587+ } )
588+ . collect ( )
589+ }
590+
346591#[ cfg( test) ]
347592mod tests {
348593 use crate :: ast:: value:: Value ;
0 commit comments