2022-01-28 01:46:14 +00:00
use std ::collections ::{ HashMap , HashSet } ;
2020-10-15 06:38:10 +00:00
2022-04-19 04:25:11 +00:00
use crate ::{
parser ::types ::{ Field , Selection , SelectionSet } ,
validation ::visitor ::{ Visitor , VisitorContext } ,
Positioned ,
} ;
2020-03-12 09:11:02 +00:00
#[ derive(Default) ]
pub struct OverlappingFieldsCanBeMerged ;
impl < ' a > Visitor < ' a > for OverlappingFieldsCanBeMerged {
fn enter_selection_set (
& mut self ,
2020-03-22 08:45:59 +00:00
ctx : & mut VisitorContext < ' a > ,
2020-05-10 02:59:51 +00:00
selection_set : & ' a Positioned < SelectionSet > ,
2020-03-12 09:11:02 +00:00
) {
let mut find_conflicts = FindConflicts {
outputs : Default ::default ( ) ,
2022-01-28 01:46:14 +00:00
visited : Default ::default ( ) ,
2020-03-12 09:11:02 +00:00
ctx ,
} ;
2022-06-25 02:51:59 +00:00
find_conflicts . find ( None , selection_set ) ;
2020-03-12 09:11:02 +00:00
}
}
struct FindConflicts < ' a , ' ctx > {
2022-06-25 02:51:59 +00:00
outputs : HashMap < ( Option < & ' a str > , & ' a str ) , & ' a Positioned < Field > > ,
2022-01-28 01:46:14 +00:00
visited : HashSet < & ' a str > ,
2020-03-22 08:45:59 +00:00
ctx : & ' a mut VisitorContext < ' ctx > ,
2020-03-12 09:11:02 +00:00
}
impl < ' a , ' ctx > FindConflicts < ' a , ' ctx > {
2022-06-25 02:51:59 +00:00
pub fn find ( & mut self , on_type : Option < & ' a str > , selection_set : & ' a Positioned < SelectionSet > ) {
2020-09-06 05:38:31 +00:00
for selection in & selection_set . node . items {
2020-05-09 09:55:04 +00:00
match & selection . node {
2020-03-12 09:11:02 +00:00
Selection ::Field ( field ) = > {
let output_name = field
2020-09-06 05:38:31 +00:00
. node
2020-03-12 09:11:02 +00:00
. alias
2020-05-09 09:55:04 +00:00
. as_ref ( )
2020-09-06 05:38:31 +00:00
. map ( | name | & name . node )
. unwrap_or_else ( | | & field . node . name . node ) ;
2022-06-25 02:51:59 +00:00
self . add_output ( on_type , & output_name , field ) ;
2020-03-12 09:11:02 +00:00
}
Selection ::InlineFragment ( inline_fragment ) = > {
2022-06-25 02:51:59 +00:00
let on_type = inline_fragment
. node
. type_condition
. as_ref ( )
. map ( | cond | cond . node . on . node . as_str ( ) ) ;
self . find ( on_type , & inline_fragment . node . selection_set ) ;
2020-03-12 09:11:02 +00:00
}
Selection ::FragmentSpread ( fragment_spread ) = > {
2020-09-06 06:16:36 +00:00
if let Some ( fragment ) =
self . ctx . fragment ( & fragment_spread . node . fragment_name . node )
{
2022-06-25 02:51:59 +00:00
let on_type = Some ( fragment . node . type_condition . node . on . node . as_str ( ) ) ;
2022-01-28 01:46:14 +00:00
if ! self
. visited
. insert ( fragment_spread . node . fragment_name . node . as_str ( ) )
{
2022-04-19 04:25:11 +00:00
// To avoid recursing itself, this error is detected by the
// `NoFragmentCycles` validator.
2022-01-28 01:46:14 +00:00
continue ;
}
2022-06-25 02:51:59 +00:00
self . find ( on_type , & fragment . node . selection_set ) ;
2020-03-12 09:11:02 +00:00
}
}
}
}
}
2022-06-25 02:51:59 +00:00
fn add_output (
& mut self ,
on_type : Option < & ' a str > ,
name : & ' a str ,
field : & ' a Positioned < Field > ,
) {
if let Some ( prev_field ) = self . outputs . get ( & ( on_type , name ) ) {
2020-09-06 05:38:31 +00:00
if prev_field . node . name . node ! = field . node . name . node {
2020-03-12 09:11:02 +00:00
self . ctx . report_error (
2020-09-06 05:38:31 +00:00
vec! [ prev_field . pos , field . pos ] ,
2020-03-12 09:11:02 +00:00
format! ( " Fields \" {} \" conflict because \" {} \" and \" {} \" are different fields. Use different aliases on the fields to fetch both if this was intentional. " ,
2020-09-06 05:38:31 +00:00
name , prev_field . node . name . node , field . node . name . node ) ) ;
2020-03-12 09:11:02 +00:00
}
// check arguments
2020-09-06 05:38:31 +00:00
if prev_field . node . arguments . len ( ) ! = field . node . arguments . len ( ) {
2020-03-12 09:11:02 +00:00
self . ctx . report_error (
2020-09-06 05:38:31 +00:00
vec! [ prev_field . pos , field . pos ] ,
2020-03-12 09:11:02 +00:00
format! ( " Fields \" {} \" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional. " , name ) ) ;
}
2020-09-06 05:38:31 +00:00
for ( name , value ) in & prev_field . node . arguments {
match field . node . get_argument ( & name . node ) {
2020-03-12 09:11:02 +00:00
Some ( other_value ) if value = = other_value = > { }
_ = > self . ctx . report_error (
2020-09-06 05:38:31 +00:00
vec! [ prev_field . pos , field . pos ] ,
2020-03-12 09:11:02 +00:00
format! ( " Fields \" {} \" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional. " , name ) ) ,
}
}
} else {
2022-06-25 02:51:59 +00:00
self . outputs . insert ( ( on_type , name ) , field ) ;
2020-03-12 09:11:02 +00:00
}
}
}
2022-06-25 02:51:59 +00:00
#[ cfg(test) ]
mod tests {
use super ::* ;
pub fn factory ( ) -> OverlappingFieldsCanBeMerged {
OverlappingFieldsCanBeMerged
}
#[ test ]
fn same_field_on_different_type ( ) {
expect_passes_rule! (
factory ,
r #"
{
pet {
.. . on Dog {
doesKnowCommand ( dogCommand : SIT )
}
.. . on Cat {
doesKnowCommand ( catCommand : JUMP )
}
}
}
" #,
) ;
}
#[ test ]
fn same_field_on_same_type ( ) {
expect_fails_rule! (
factory ,
r #"
{
pet {
.. . on Dog {
doesKnowCommand ( dogCommand : SIT )
}
.. . on Dog {
doesKnowCommand ( dogCommand : Heel )
}
}
}
" #,
) ;
}
#[ test ]
fn same_alias_on_different_type ( ) {
expect_passes_rule! (
factory ,
r #"
{
pet {
.. . on Dog {
volume : barkVolume
}
.. . on Cat {
volume : meowVolume
}
}
}
" #,
) ;
}
}