@@ -8383,4 +8383,184 @@ mod tests {
83838383 }
83848384 "# ) ;
83858385 }
8386+
8387+ /// Flattens a referencer index into an order-independent set of facts, so an incrementally
8388+ /// maintained index can be compared with one rebuilt from scratch.
8389+ fn referencer_facts ( referencers : & Referencers ) -> std:: collections:: BTreeSet < String > {
8390+ fn add < T : Debug > (
8391+ facts : & mut std:: collections:: BTreeSet < String > ,
8392+ kind : & str ,
8393+ key : & Name ,
8394+ slot : & str ,
8395+ positions : impl IntoIterator < Item = T > ,
8396+ ) {
8397+ facts. insert ( format ! ( "{kind} {key}" ) ) ;
8398+ for position in positions {
8399+ facts. insert ( format ! ( "{kind} {key} {slot} {position:?}" ) ) ;
8400+ }
8401+ }
8402+ let mut facts = std:: collections:: BTreeSet :: new ( ) ;
8403+ let f = & mut facts;
8404+ for ( key, r) in & referencers. scalar_types {
8405+ add ( f, "scalar" , key, "object_fields" , & r. object_fields ) ;
8406+ add (
8407+ f,
8408+ "scalar" ,
8409+ key,
8410+ "object_field_arguments" ,
8411+ & r. object_field_arguments ,
8412+ ) ;
8413+ add ( f, "scalar" , key, "interface_fields" , & r. interface_fields ) ;
8414+ add (
8415+ f,
8416+ "scalar" ,
8417+ key,
8418+ "interface_field_arguments" ,
8419+ & r. interface_field_arguments ,
8420+ ) ;
8421+ add ( f, "scalar" , key, "union_fields" , & r. union_fields ) ;
8422+ add (
8423+ f,
8424+ "scalar" ,
8425+ key,
8426+ "input_object_fields" ,
8427+ & r. input_object_fields ,
8428+ ) ;
8429+ add (
8430+ f,
8431+ "scalar" ,
8432+ key,
8433+ "directive_arguments" ,
8434+ & r. directive_arguments ,
8435+ ) ;
8436+ }
8437+ for ( key, r) in & referencers. object_types {
8438+ add ( f, "object" , key, "schema_roots" , & r. schema_roots ) ;
8439+ add ( f, "object" , key, "object_fields" , & r. object_fields ) ;
8440+ add ( f, "object" , key, "interface_fields" , & r. interface_fields ) ;
8441+ add ( f, "object" , key, "union_types" , & r. union_types ) ;
8442+ }
8443+ for ( key, r) in & referencers. interface_types {
8444+ add ( f, "interface" , key, "object_types" , & r. object_types ) ;
8445+ add ( f, "interface" , key, "object_fields" , & r. object_fields ) ;
8446+ add ( f, "interface" , key, "interface_types" , & r. interface_types ) ;
8447+ add ( f, "interface" , key, "interface_fields" , & r. interface_fields ) ;
8448+ }
8449+ for ( key, r) in & referencers. union_types {
8450+ add ( f, "union" , key, "object_fields" , & r. object_fields ) ;
8451+ add ( f, "union" , key, "interface_fields" , & r. interface_fields ) ;
8452+ }
8453+ for ( key, r) in & referencers. enum_types {
8454+ add ( f, "enum" , key, "object_fields" , & r. object_fields ) ;
8455+ add (
8456+ f,
8457+ "enum" ,
8458+ key,
8459+ "object_field_arguments" ,
8460+ & r. object_field_arguments ,
8461+ ) ;
8462+ add ( f, "enum" , key, "interface_fields" , & r. interface_fields ) ;
8463+ add (
8464+ f,
8465+ "enum" ,
8466+ key,
8467+ "interface_field_arguments" ,
8468+ & r. interface_field_arguments ,
8469+ ) ;
8470+ add (
8471+ f,
8472+ "enum" ,
8473+ key,
8474+ "input_object_fields" ,
8475+ & r. input_object_fields ,
8476+ ) ;
8477+ add (
8478+ f,
8479+ "enum" ,
8480+ key,
8481+ "directive_arguments" ,
8482+ & r. directive_arguments ,
8483+ ) ;
8484+ }
8485+ for ( key, r) in & referencers. input_object_types {
8486+ add (
8487+ f,
8488+ "input" ,
8489+ key,
8490+ "object_field_arguments" ,
8491+ & r. object_field_arguments ,
8492+ ) ;
8493+ add (
8494+ f,
8495+ "input" ,
8496+ key,
8497+ "interface_field_arguments" ,
8498+ & r. interface_field_arguments ,
8499+ ) ;
8500+ add (
8501+ f,
8502+ "input" ,
8503+ key,
8504+ "input_object_fields" ,
8505+ & r. input_object_fields ,
8506+ ) ;
8507+ add (
8508+ f,
8509+ "input" ,
8510+ key,
8511+ "directive_arguments" ,
8512+ & r. directive_arguments ,
8513+ ) ;
8514+ }
8515+ for ( key, r) in & referencers. directives {
8516+ add ( f, "directive" , key, "targets" , r. iter ( ) ) ;
8517+ }
8518+ facts
8519+ }
8520+
8521+ /// Renames a type through the public entry point, then checks the edit contract: the compiler
8522+ /// schema is still valid, and the incrementally maintained referencers equal referencers
8523+ /// rebuilt from scratch from the edited schema.
8524+ fn rename_and_check ( sdl : & str , old_name : Name , new_name : Name ) -> FederationSchema {
8525+ let mut schema = FederationSchema :: new (
8526+ Schema :: parse_and_validate ( sdl, "rename.graphql" )
8527+ . unwrap ( )
8528+ . into_inner ( ) ,
8529+ )
8530+ . unwrap ( ) ;
8531+ schema
8532+ . get_type ( & old_name)
8533+ . unwrap ( )
8534+ . rename ( & mut schema, new_name)
8535+ . unwrap ( ) ;
8536+ let valid = schema. schema ( ) . clone ( ) . validate ( ) ;
8537+ assert ! ( valid. is_ok( ) , "renamed schema is invalid: {valid:?}" ) ;
8538+ let rebuilt = FederationSchema :: new ( schema. schema ( ) . clone ( ) ) . unwrap ( ) ;
8539+ let actual = referencer_facts ( schema. referencers ( ) ) ;
8540+ let expected = referencer_facts ( rebuilt. referencers ( ) ) ;
8541+ let stale: Vec < _ > = actual. difference ( & expected) . collect ( ) ;
8542+ let missing: Vec < _ > = expected. difference ( & actual) . collect ( ) ;
8543+ assert ! (
8544+ stale. is_empty( ) && missing. is_empty( ) ,
8545+ "referencers diverge from a rebuild after renaming {old_name}\n \
8546+ stale: {stale:#?}\n missing: {missing:#?}"
8547+ ) ;
8548+ schema
8549+ }
8550+
8551+ #[ test]
8552+ fn renamed_interface_keeps_its_directive_referencer_position ( ) {
8553+ let schema = rename_and_check (
8554+ "directive @mark on INTERFACE interface I @mark { x: String } type Query { i: I }" ,
8555+ name ! ( "I" ) ,
8556+ name ! ( "Renamed" ) ,
8557+ ) ;
8558+ let targets = & schema. referencers ( ) . directives [ "mark" ] . interface_types ;
8559+ assert ! ( targets. contains( & InterfaceTypeDefinitionPosition {
8560+ type_name: name!( "Renamed" )
8561+ } ) ) ;
8562+ assert ! ( !targets. contains( & InterfaceTypeDefinitionPosition {
8563+ type_name: name!( "I" )
8564+ } ) ) ;
8565+ }
83868566}
0 commit comments