1
- import { asFed2SubgraphDocument , assert , buildSchema , buildSubgraph , extractSubgraphsFromSupergraph , isObjectType , ObjectType , printSchema , Schema , ServiceDefinition , Subgraphs } from '@apollo/federation-internals' ;
1
+ import { asFed2SubgraphDocument , assert , buildSchema , buildSubgraph , extractSubgraphsFromSupergraph , FEDERATION2_LINK_WTH_FULL_IMPORTS , isObjectType , ObjectType , printSchema , Schema , ServiceDefinition , Subgraphs } from '@apollo/federation-internals' ;
2
2
import { CompositionResult , composeServices , CompositionSuccess } from '../compose' ;
3
3
import gql from 'graphql-tag' ;
4
4
import './matchers' ;
5
+ import { print } from 'graphql' ;
5
6
6
7
function assertCompositionSuccess ( r : CompositionResult ) : asserts r is CompositionSuccess {
7
8
if ( r . errors ) {
@@ -253,7 +254,7 @@ describe('composition', () => {
253
254
expect ( subgraphs . get ( 'subgraphA' ) ! . toString ( ) ) . toMatchString ( `
254
255
schema
255
256
@link(url: "https://specs.apollo.dev/link/v1.0")
256
- @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@requires", "@provides", "@external", "@shareable", "@tag", "@extends"])
257
+ ${ FEDERATION2_LINK_WTH_FULL_IMPORTS }
257
258
{
258
259
query: Query
259
260
}
@@ -271,7 +272,7 @@ describe('composition', () => {
271
272
expect ( subgraphs . get ( 'subgraphB' ) ! . toString ( ) ) . toMatchString ( `
272
273
schema
273
274
@link(url: "https://specs.apollo.dev/link/v1.0")
274
- @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@requires", "@provides", "@external", "@shareable", "@tag", "@extends"])
275
+ ${ FEDERATION2_LINK_WTH_FULL_IMPORTS }
275
276
{
276
277
query: Query
277
278
}
@@ -327,7 +328,7 @@ describe('composition', () => {
327
328
expect ( subgraphs . get ( 'subgraphA' ) ! . toString ( ) ) . toMatchString ( `
328
329
schema
329
330
@link(url: "https://specs.apollo.dev/link/v1.0")
330
- @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@requires", "@provides", "@external", "@shareable", "@tag", "@extends"])
331
+ ${ FEDERATION2_LINK_WTH_FULL_IMPORTS }
331
332
{
332
333
query: Query
333
334
}
@@ -347,7 +348,7 @@ describe('composition', () => {
347
348
expect ( subgraphs . get ( 'subgraphB' ) ! . toString ( ) ) . toMatchString ( `
348
349
schema
349
350
@link(url: "https://specs.apollo.dev/link/v1.0")
350
- @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@requires", "@provides", "@external", "@shareable", "@tag", "@extends"])
351
+ ${ FEDERATION2_LINK_WTH_FULL_IMPORTS }
351
352
{
352
353
query: Query
353
354
}
@@ -2327,4 +2328,256 @@ describe('composition', () => {
2327
2328
expect ( tagOnQ2 ?. arguments ( ) [ 'name' ] ) . toBe ( 't2' ) ;
2328
2329
} )
2329
2330
} ) ;
2331
+
2332
+ describe ( '@inaccessible' , ( ) => {
2333
+ it ( 'propagates @inaccessible to the supergraph' , ( ) => {
2334
+ const subgraphA = {
2335
+ typeDefs : gql `
2336
+ type Query {
2337
+ me: User @inaccessible
2338
+ users: [User]
2339
+ }
2340
+
2341
+ type User @key(fields: "id") {
2342
+ id: ID!
2343
+ name: String!
2344
+ }
2345
+ ` ,
2346
+ name : 'subgraphA' ,
2347
+ } ;
2348
+
2349
+ const subgraphB = {
2350
+ typeDefs : gql `
2351
+ type User @key(fields: "id") {
2352
+ id: ID!
2353
+ birthdate: String!
2354
+ age: Int! @inaccessible
2355
+ }
2356
+ ` ,
2357
+ name : 'subgraphB' ,
2358
+ } ;
2359
+
2360
+ const result = composeAsFed2Subgraphs ( [ subgraphA , subgraphB ] ) ;
2361
+ assertCompositionSuccess ( result ) ;
2362
+ const supergraph = result . schema ;
2363
+ expect ( supergraph . schemaDefinition . rootType ( 'query' ) ?. field ( 'me' ) ?. appliedDirectivesOf ( 'inaccessible' ) . pop ( ) ) . toBeDefined ( ) ;
2364
+
2365
+ const userType = supergraph . type ( 'User' ) ;
2366
+ assert ( userType && isObjectType ( userType ) , `Should be an object type` ) ;
2367
+ expect ( userType ?. field ( 'age' ) ?. appliedDirectivesOf ( 'inaccessible' ) . pop ( ) ) . toBeDefined ( ) ;
2368
+ } ) ;
2369
+
2370
+ it ( 'merges @inacessible on the same element' , ( ) => {
2371
+ const subgraphA = {
2372
+ typeDefs : gql `
2373
+ type Query {
2374
+ user: [User]
2375
+ }
2376
+
2377
+ type User @key(fields: "id") {
2378
+ id: ID!
2379
+ name: String @shareable @inaccessible
2380
+ }
2381
+ ` ,
2382
+ name : 'subgraphA' ,
2383
+ } ;
2384
+
2385
+ const subgraphB = {
2386
+ typeDefs : gql `
2387
+ type User @key(fields: "id") {
2388
+ id: ID!
2389
+ name: String @shareable @inaccessible
2390
+ }
2391
+ ` ,
2392
+ name : 'subgraphB' ,
2393
+ } ;
2394
+
2395
+ const result = composeAsFed2Subgraphs ( [ subgraphA , subgraphB ] ) ;
2396
+ assertCompositionSuccess ( result ) ;
2397
+ const supergraph = result . schema ;
2398
+
2399
+ const userType = supergraph . type ( 'User' ) ;
2400
+ assert ( userType && isObjectType ( userType ) , `Should be an object type` ) ;
2401
+ expect ( userType ?. field ( 'name' ) ?. appliedDirectivesOf ( 'inaccessible' ) . pop ( ) ) . toBeDefined ( ) ;
2402
+ } ) ;
2403
+
2404
+ describe ( 'rejects @inaccessible and @external together' , ( ) => {
2405
+ const subgraphA = {
2406
+ typeDefs : gql `
2407
+ type Query {
2408
+ user: [User]
2409
+ }
2410
+
2411
+ type User @key(fields: "id") {
2412
+ id: ID!
2413
+ name: String!
2414
+ birthdate: Int! @external @inaccessible
2415
+ age: Int! @requires(fields: "birthdate")
2416
+ }
2417
+ ` ,
2418
+ name : 'subgraphA' ,
2419
+ } ;
2420
+
2421
+ const subgraphB = {
2422
+ typeDefs : gql `
2423
+ type User @key(fields: "id") {
2424
+ id: ID!
2425
+ birthdate: Int!
2426
+ }
2427
+ ` ,
2428
+ name : 'subgraphB' ,
2429
+ } ;
2430
+
2431
+ const result = composeAsFed2Subgraphs ( [ subgraphA , subgraphB ] ) ;
2432
+ expect ( result . errors ) . toBeDefined ( ) ;
2433
+ expect ( errors ( result ) ) . toStrictEqual ( [
2434
+ [ 'MERGED_DIRECTIVE_APPLICATION_ON_EXTERNAL' , '[subgraphA] Cannot apply merged directive @inaccessible to external field "User.birthdate"' ]
2435
+ ] ) ;
2436
+ } ) ;
2437
+
2438
+ it ( 'errors out if @inaccessible is imported under mismatched names' , ( ) => {
2439
+ const subgraphA = {
2440
+ typeDefs : gql `
2441
+ extend schema
2442
+ @link(url: "https://specs.apollo.dev/federation/v2.0", import: [{name: "@inaccessible", as: "@private"}])
2443
+
2444
+ type Query {
2445
+ q: Int
2446
+ q1: Int @private
2447
+ }
2448
+ ` ,
2449
+ name : 'subgraphA' ,
2450
+ } ;
2451
+
2452
+ const subgraphB = {
2453
+ typeDefs : gql `
2454
+ extend schema
2455
+ @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@inaccessible"])
2456
+
2457
+ type Query {
2458
+ q2: Int @inaccessible
2459
+ }
2460
+ ` ,
2461
+ name : 'subgraphB' ,
2462
+ } ;
2463
+
2464
+ const result = composeServices ( [ subgraphA , subgraphB ] ) ;
2465
+ expect ( result . errors ) . toBeDefined ( ) ;
2466
+ expect ( errors ( result ) ) . toStrictEqual ( [
2467
+ [ 'LINK_IMPORT_NAME_MISMATCH' , 'The federation "@inaccessible" directive is imported with mismatched name between subgraphs: it is imported as "@inaccessible" in subgraph "subgraphB" but "@private" in subgraph "subgraphA"' ]
2468
+ ] ) ;
2469
+ } ) ;
2470
+
2471
+ it ( 'succeeds if @inaccessible is imported under the same non-default name' , ( ) => {
2472
+ const subgraphA = {
2473
+ typeDefs : gql `
2474
+ extend schema
2475
+ @link(url: "https://specs.apollo.dev/federation/v2.0", import: [{name: "@inaccessible", as: "@private"}])
2476
+
2477
+ type Query {
2478
+ q: Int
2479
+ q1: Int @private
2480
+ }
2481
+ ` ,
2482
+ name : 'subgraphA' ,
2483
+ } ;
2484
+
2485
+ const subgraphB = {
2486
+ typeDefs : gql `
2487
+ extend schema
2488
+ @link(url: "https://specs.apollo.dev/federation/v2.0", import: [{name: "@inaccessible", as: "@private"}])
2489
+
2490
+ type Query {
2491
+ q2: Int @private
2492
+ }
2493
+ ` ,
2494
+ name : 'subgraphB' ,
2495
+ } ;
2496
+
2497
+ const result = composeServices ( [ subgraphA , subgraphB ] ) ;
2498
+ assertCompositionSuccess ( result ) ;
2499
+ const supergraph = result . schema ;
2500
+ expect ( supergraph . schemaDefinition . rootType ( 'query' ) ?. field ( 'q1' ) ?. appliedDirectivesOf ( 'private' ) . pop ( ) ) . toBeDefined ( ) ;
2501
+ expect ( supergraph . schemaDefinition . rootType ( 'query' ) ?. field ( 'q2' ) ?. appliedDirectivesOf ( 'private' ) . pop ( ) ) . toBeDefined ( ) ;
2502
+ } ) ;
2503
+
2504
+ it ( 'ignores inaccessible element when validating composition' , ( ) => {
2505
+ // The following example would _not_ compose if the `z` was not marked inaccessible since it wouldn't be reachable
2506
+ // from the `origin` query. So all this test does is double-checking that validation does pass with it marked inaccessible.
2507
+ const subgraphA = {
2508
+ typeDefs : gql `
2509
+ type Query {
2510
+ origin: Point
2511
+ }
2512
+
2513
+ type Point @shareable {
2514
+ x: Int
2515
+ y: Int
2516
+ }
2517
+ ` ,
2518
+ name : 'subgraphA' ,
2519
+ } ;
2520
+
2521
+ const subgraphB = {
2522
+ typeDefs : gql `
2523
+ type Point @shareable {
2524
+ x: Int
2525
+ y: Int
2526
+ z: Int @inaccessible
2527
+ }
2528
+ ` ,
2529
+ name : 'subgraphB' ,
2530
+ } ;
2531
+
2532
+ const result = composeAsFed2Subgraphs ( [ subgraphA , subgraphB ] ) ;
2533
+ assertCompositionSuccess ( result ) ;
2534
+ } ) ;
2535
+
2536
+ it ( 'errors if a subgraph misuse @inaccessible' , ( ) => {
2537
+ const subgraphA = {
2538
+ typeDefs : gql `
2539
+ type Query {
2540
+ q1: Int
2541
+ q2: A
2542
+ }
2543
+
2544
+ type A @shareable {
2545
+ x: Int
2546
+ y: Int
2547
+ }
2548
+ ` ,
2549
+ name : 'subgraphA' ,
2550
+ } ;
2551
+
2552
+ const subgraphB = {
2553
+ typeDefs : gql `
2554
+ type A @shareable @inaccessible {
2555
+ x: Int
2556
+ y: Int
2557
+ }
2558
+ ` ,
2559
+ name : 'subgraphB' ,
2560
+ } ;
2561
+
2562
+ const result = composeAsFed2Subgraphs ( [ subgraphA , subgraphB ] ) ;
2563
+ expect ( result . errors ) . toBeDefined ( ) ;
2564
+ expect ( errors ( result ) ) . toStrictEqual ( [
2565
+ [ 'REFERENCED_INACCESSIBLE' , 'Field "Query.q2" returns @inaccessible type "A" without being marked @inaccessible itself.' ]
2566
+ ] ) ;
2567
+
2568
+ // Because @inaccessible are thrown by the toAPISchema code and not the merge code directly, let's make sure the include
2569
+ // link to the relevant nodes in the subgaphs. Also note that in those test we don't have proper "location" information
2570
+ // in the AST nodes (line numbers in particular) because `gql` doesn't populate those, but printing the AST nodes kind of
2571
+ // guarantees us that we do get subgraph nodes and not supergraph nodes because supergraph nodes would have @join__ *
2572
+ // directives and would _not_ have the `@shareable`/`@inacessible` directives.
2573
+ const nodes = result . errors ! [ 0 ] . nodes ! ;
2574
+ expect ( print ( nodes [ 0 ] ) ) . toMatchString ( 'q2: A' ) ;
2575
+ expect ( print ( nodes [ 1 ] ) ) . toMatchString ( `
2576
+ type A @shareable @inaccessible {
2577
+ x: Int
2578
+ y: Int
2579
+ }`
2580
+ ) ;
2581
+ } )
2582
+ } ) ;
2330
2583
} ) ;
0 commit comments