Repository: apollographql/apollo-server
Pull request #7274 (2022-12-23 10:52:13)

Update `schemaIsSubgraph` to also support non nullable `_Service.sdl`
Related to https://github.com/apollographql/apollo-federation-subgraph-compatibility/pull/302

We found a discrepancy in how libraries implement the federation specification (`_Service.sdl` is `String` in Federation 1 and is `String` in Federation 2). 
I'm doing this PR mostly to check how easy it would be to support both specs at once, if I understood correctly the code `schemaIsSubgraph` might also be used outside Apollo Server 😊


netlify[bot]: ### <span aria-hidden="true">👷</span> Deploy Preview for *apollo-server-docs* processing.


|  Name | Link |
|---------------------------------|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 6c2a1d1fa0906692a2e76b877041d71ae4e60862 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/apollo-server-docs/deploys/63a5886129e14f0008467c76 |
codesandbox-ci[bot]: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/apollographql/apollo-server/pr/7274/builds/329588) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 6c2a1d1fa0906692a2e76b877041d71ae4e60862:

|Sandbox| Source |
|--|--|
|[Apollo Server Typescript](https://codesandbox.io/s/apollo-server-typescript-01b648)| Configuration |
|[Apollo Server](https://codesandbox.io/s/apollo-server-si2eec)| Configuration |


Patch:
diff --git a/.changeset/quiet-pears-float.md b/.changeset/quiet-pears-float.md
new file mode 100644
index 000000000..b7feaa1aa
--- /dev/null
+++ b/.changeset/quiet-pears-float.md
@@ -0,0 +1,5 @@
+---
+'@apollo/server': patch
+---
+
+Update schemaIsSubgraph to also support non nullable \_Service.sdl
diff --git a/package-lock.json b/package-lock.json
index 367d2de09..5df5146a2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,7 +20,7 @@
         "@graphql-tools/mock": "8.7.14",
         "@graphql-tools/schema": "9.0.12",
         "@josephg/resolvable": "1.0.1",
-        "@rollup/plugin-commonjs": "23.0.7",
+        "@rollup/plugin-commonjs": "24.0.0",
         "@types/async-retry": "1.4.5",
         "@types/body-parser": "1.19.2",
         "@types/compression": "1.7.2",
@@ -3869,9 +3869,9 @@
       "license": "BSD-3-Clause"
     },
     "node_modules/@rollup/plugin-commonjs": {
-      "version": "23.0.7",
-      "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-23.0.7.tgz",
-      "integrity": "sha512-hsSD5Qzyuat/swzrExGG5l7EuIlPhwTsT7KwKbSCQzIcJWjRxiimi/0tyMYY2bByitNb3i1p+6JWEDGa0NvT0Q==",
+      "version": "24.0.0",
+      "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.0.tgz",
+      "integrity": "sha512-0w0wyykzdyRRPHOb0cQt14mIBLujfAv6GgP6g8nvg/iBxEm112t3YPPq+Buqe2+imvElTka+bjNlJ/gB56TD8g==",
       "dev": true,
       "dependencies": {
         "@rollup/pluginutils": "^5.0.1",
@@ -3922,9 +3922,9 @@
       }
     },
     "node_modules/@rollup/plugin-commonjs/node_modules/minimatch": {
-      "version": "5.1.1",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.1.tgz",
-      "integrity": "sha512-362NP+zlprccbEt/SkxKfRMHnNY85V74mVnpUpNyr3F35covl09Kec7/sEFLt3RA4oXmewtoaanoIf67SE5Y5g==",
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz",
+      "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==",
       "dev": true,
       "dependencies": {
         "brace-expansion": "^2.0.1"
@@ -16445,9 +16445,9 @@
       "version": "1.1.0"
     },
     "@rollup/plugin-commonjs": {
-      "version": "23.0.7",
-      "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-23.0.7.tgz",
-      "integrity": "sha512-hsSD5Qzyuat/swzrExGG5l7EuIlPhwTsT7KwKbSCQzIcJWjRxiimi/0tyMYY2bByitNb3i1p+6JWEDGa0NvT0Q==",
+      "version": "24.0.0",
+      "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.0.tgz",
+      "integrity": "sha512-0w0wyykzdyRRPHOb0cQt14mIBLujfAv6GgP6g8nvg/iBxEm112t3YPPq+Buqe2+imvElTka+bjNlJ/gB56TD8g==",
       "dev": true,
       "requires": {
         "@rollup/pluginutils": "^5.0.1",
@@ -16481,9 +16481,9 @@
           }
         },
         "minimatch": {
-          "version": "5.1.1",
-          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.1.tgz",
-          "integrity": "sha512-362NP+zlprccbEt/SkxKfRMHnNY85V74mVnpUpNyr3F35covl09Kec7/sEFLt3RA4oXmewtoaanoIf67SE5Y5g==",
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz",
+          "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==",
           "dev": true,
           "requires": {
             "brace-expansion": "^2.0.1"
diff --git a/package.json b/package.json
index d2034b078..ebb19b8ca 100644
--- a/package.json
+++ b/package.json
@@ -47,7 +47,7 @@
     "@graphql-tools/mock": "8.7.14",
     "@graphql-tools/schema": "9.0.12",
     "@josephg/resolvable": "1.0.1",
-    "@rollup/plugin-commonjs": "23.0.7",
+    "@rollup/plugin-commonjs": "24.0.0",
     "@types/async-retry": "1.4.5",
     "@types/body-parser": "1.19.2",
     "@types/compression": "1.7.2",
diff --git a/packages/server/src/plugin/schemaIsSubgraph.ts b/packages/server/src/plugin/schemaIsSubgraph.ts
index 88e75aecb..580926d57 100644
--- a/packages/server/src/plugin/schemaIsSubgraph.ts
+++ b/packages/server/src/plugin/schemaIsSubgraph.ts
@@ -1,4 +1,9 @@
-import { GraphQLSchema, isObjectType, isScalarType } from 'graphql';
+import {
+  GraphQLSchema,
+  isObjectType,
+  isScalarType,
+  isNonNullType,
+} from 'graphql';
 
 // Returns true if it appears that the schema was appears to be of a subgraph
 // (eg, returned from @apollo/subgraph's buildSubgraphSchema). This strategy
@@ -24,7 +29,11 @@ export function schemaIsSubgraph(schema: GraphQLSchema): boolean {
   if (!sdlField) {
     return false;
   }
-  const sdlFieldType = sdlField.type;
+
+  let sdlFieldType = sdlField.type;
+  if (isNonNullType(sdlFieldType)) {
+    sdlFieldType = sdlFieldType.ofType;
+  }
   if (!isScalarType(sdlFieldType)) {
     return false;
   }


Test patch:
diff --git a/packages/server/src/__tests__/plugin/schemaIsSubgraph.test.ts b/packages/server/src/__tests__/plugin/schemaIsSubgraph.test.ts
new file mode 100644
index 000000000..0dcefdc80
--- /dev/null
+++ b/packages/server/src/__tests__/plugin/schemaIsSubgraph.test.ts
@@ -0,0 +1,122 @@
+import { schemaIsSubgraph } from '../../plugin/schemaIsSubgraph';
+import { describe, it, expect } from '@jest/globals';
+
+import {
+  GraphQLSchema,
+  GraphQLObjectType,
+  GraphQLString,
+  GraphQLInt,
+  GraphQLNonNull,
+} from 'graphql';
+
+describe('schemaIsSubgraph', () => {
+  it('returns false when there is no service field', async () => {
+    const schema = new GraphQLSchema({
+      query: new GraphQLObjectType({
+        name: 'QueryType',
+        fields: {
+          hello: {
+            type: GraphQLString,
+          },
+        },
+      }),
+    });
+
+    expect(schemaIsSubgraph(schema)).toBe(false);
+  });
+
+  it('returns false when the sdl field is a not string', async () => {
+    const schema = new GraphQLSchema({
+      query: new GraphQLObjectType({
+        name: 'QueryType',
+        fields: {
+          _service: {
+            type: new GraphQLObjectType({
+              name: '_Service',
+              fields: {
+                sdl: {
+                  type: GraphQLInt,
+                },
+              },
+            }),
+          },
+        },
+      }),
+    });
+
+    expect(schemaIsSubgraph(schema)).toBe(false);
+  });
+
+  it('returns false when the sdl field is a scalar', async () => {
+    const schema = new GraphQLSchema({
+      query: new GraphQLObjectType({
+        name: 'QueryType',
+        fields: {
+          _service: {
+            type: new GraphQLObjectType({
+              name: '_Service',
+              fields: {
+                sdl: {
+                  type: new GraphQLObjectType({
+                    name: 'Scalar',
+                    fields: {
+                      value: {
+                        type: GraphQLString,
+                      },
+                    },
+                  }),
+                },
+              },
+            }),
+          },
+        },
+      }),
+    });
+
+    expect(schemaIsSubgraph(schema)).toBe(false);
+  });
+
+  it('returns true when the sdl field is a string', async () => {
+    const schema = new GraphQLSchema({
+      query: new GraphQLObjectType({
+        name: 'QueryType',
+        fields: {
+          _service: {
+            type: new GraphQLObjectType({
+              name: '_Service',
+              fields: {
+                sdl: {
+                  type: GraphQLString,
+                },
+              },
+            }),
+          },
+        },
+      }),
+    });
+
+    expect(schemaIsSubgraph(schema)).toBe(true);
+  });
+
+  it('returns true when the sdl field is a non nullable string', async () => {
+    const schema = new GraphQLSchema({
+      query: new GraphQLObjectType({
+        name: 'QueryType',
+        fields: {
+          _service: {
+            type: new GraphQLObjectType({
+              name: '_Service',
+              fields: {
+                sdl: {
+                  type: new GraphQLNonNull(GraphQLString),
+                },
+              },
+            }),
+          },
+        },
+      }),
+    });
+
+    expect(schemaIsSubgraph(schema)).toBe(true);
+  });
+});
diff --git a/smoke-test/package.json b/smoke-test/package.json
index d5080027e..e50e9e4c0 100644
--- a/smoke-test/package.json
+++ b/smoke-test/package.json
@@ -4,8 +4,8 @@
     "make-fetch-happen": "11.0.2"
   },
   "devDependencies": {
-    "@rollup/plugin-commonjs": "23.0.7",
-    "@rollup/plugin-json": "5.0.2",
+    "@rollup/plugin-commonjs": "24.0.0",
+    "@rollup/plugin-json": "6.0.0",
     "@rollup/plugin-node-resolve": "15.0.1",
     "@types/make-fetch-happen": "10.0.1",
     "rollup": "3.8.1"