Skip to content

Commit

Permalink
Improve behavior of Disabled plugins
Browse files Browse the repository at this point in the history
- Make it an error to install a built-in plugin and its "Disabled"
  counterpart in the same server. "Disabled" really means "don't install
  by default", but it's reasonable to assume it would counteract a
  manual installation of the same plugin too.  For simplicity, this
  makes installing both flavors into an error at start time. This is
  technically backwards-incompatible but we are still early in the ASv4
  lifecycle and this combination wasn't expected to have well-defined
  behavior.
- Add `ApolloServerPluginSchemaReportingDisabled` which allows you to
  override the effect of the `APOLLO_SCHEMA_REPORTING` environment
  variable.
- Doc improvements.

Fixes #4778. Fixes #7099.
  • Loading branch information
glasser committed Oct 31, 2022
1 parent 4881198 commit 0d3a5ea
Show file tree
Hide file tree
Showing 13 changed files with 139 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/eight-bats-shop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@apollo/server': minor
---

New `ApolloServerPluginSchemaReportingDisabled` plugin which can override the `APOLLO_SCHEMA_REPORTING` environment variable.
5 changes: 5 additions & 0 deletions .changeset/strong-laws-bow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@apollo/server': minor
---

It is now an error to combine a "disabled" plugin such as `ApolloServerPluginUsageReportingDisabled` with its enabled counterpart such as `ApolloServerPluginUsageReporting`.
4 changes: 2 additions & 2 deletions docs/source/api/apollo-server.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -372,10 +372,10 @@ To learn more about configuring Apollo Server's cache, see [Configuring cache ba

An array of [plugins](../integrations/plugins) to install in your server instance. Each array element can be either a valid plugin object or a zero-argument function that _returns_ a valid plugin object.

In certain cases, Apollo Server installs some of its built-in plugins automatically (for example, when you provide an Apollo Studio API key with the `APOLLO_KEY` environment variable). For details, see the API references for these plugins: [usage reporting](./plugin/usage-reporting/), [schema reporting](./plugin/schema-reporting/), [landing page](./plugin/landing-pages/), and [inline trace](./plugin/inline-trace/).

You can _also_ add plugins to your server before you start it using the [`addPlugin`](#addplugin) method.

Apollo Server comes with several plugins that it installs automatically in their default configuration if certain conditions are met. For example, the usage reporting plugin is installed if you provide a graph API key and a graph ref. Apollo Server skips this automatic installation if manually provide the plugin (in the `plugins` array or with the `addPlugin` method), or if you provide the plugin's corresponding "disabled" plugin (such as `ApolloServerPluginUsageReportingDisabled()` for `ApolloServerPluginUsageReporting`). For details, see the API references for these plugins: [usage reporting](./plugin/usage-reporting/), [schema reporting](./plugin/schema-reporting/), [landing page](./plugin/landing-pages/), [cache control](./plugin/cache-control/), and [inline trace](./plugin/inline-trace/).

</td>
</tr>

Expand Down
21 changes: 21 additions & 0 deletions docs/source/api/plugin/schema-reporting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,24 @@ Specifies which [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fet

</tbody>
</table>

## Disabling the plugin

The schema reporting plugin is only automatically installed if the `APOLLO_SCHEMA_REPORTING` is set to `true`, so the easiest way to disable the plugin is to avoid setting that environment variable (and to not explicitly install `ApolloServerPluginSchemaReporting`). However, if you'd like to ensure that the schema reporting plugin is not installed in your server (perhaps for a test that might run with arbitrary environment variables set), you can disable schema reporting by installing the `ApolloServerPluginSchemaReportingDisabled` plugin, like so:

<MultiCodeBlock>

```ts
import { ApolloServer } from '@apollo/server';
import { ApolloServerPluginSchemaReportingDisabled } from '@apollo/server/plugin/disabled';

const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
ApolloServerPluginSchemaReportingDisabled(),
],
});
```

</MultiCodeBlock>
23 changes: 23 additions & 0 deletions docs/source/migration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1429,6 +1429,29 @@ new ApolloServer({
});
```

## "Disabled" plugins cannot be combined with their enabled counterpart

Apollo Server has several plugins that are installed by default in certain conditions. To override this behavior, each of these plugins has a "disabled" counterpart that prevents this default installation.

But what happens if you combine the manual installation of a plugin with its disabled counterpart? Consider the following code:

```ts
const server = new ApolloServer({
schema,
plugins: [
ApolloServerPluginUsageReporting(),
ApolloServerPluginUsageReportingDisabled(),
]
});
await server.start();
```

In Apollo Server 3, the "disabled" plugin is simply ignored if combined with its enabled counterpart. This could lead to confusion, as it can appear that an attempt to disable a feature is completely ignored.

In Apollo Server 4, `await server.start()` will throw if you combine a "disabled" plugin with its enabled counterpart, with an error like `You have tried to install both ApolloServerPluginUsageReporting and ApolloServerPluginUsageReportingDisabled`. If your server throws this error, choose whether you want the feature enabled or disabled, and install only the appropriate plugin.

(This change affects the usage reporting, inline trace, and cache control features. It also affects the schema reporting feature, although `ApolloServerPluginSchemaReportingDisabled` did not exist in Apollo Server 3. For technical reasons, it does not affect the landing page feature: combining `ApolloServerPluginLandingPageDisabled` with a landing page plugin should be considered as unspecified behavior which may change in a future release of Apollo Server 4.)

## Plugin API changes

### Fields on `GraphQLRequestContext`
Expand Down
43 changes: 43 additions & 0 deletions packages/server/src/ApolloServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,49 @@ export class ApolloServer<in out TContext extends BaseContext = BaseContext> {
(p) => pluginIsInternal(p) && p.__internal_plugin_id__() === id,
);

// Make sure we're not trying to explicitly enable and disable the same
// feature. (Be careful: we are not trying to stop people from installing
// the same plugin twice if they have a use case for it, like two usage
// reporting plugins for different variants.)
//
// Note that this check doesn't work for the landing page plugin, because
// users can write their own landing page plugins and we don't know if a
// given plugin is a landing page plugin until the server has started.
const pluginsByInternalID = new Map<
InternalPluginId,
{ sawDisabled: boolean; sawNonDisabled: boolean }
>();
for (const p of plugins) {
if (pluginIsInternal(p)) {
if (!pluginsByInternalID.has(p.__internal_plugin_id__())) {
pluginsByInternalID.set(p.__internal_plugin_id__(), {
sawDisabled: false,
sawNonDisabled: false,
});
}
if (p.__is_disabled_plugin__()) {
pluginsByInternalID.get(p.__internal_plugin_id__())!.sawDisabled =
true;
} else {
pluginsByInternalID.get(p.__internal_plugin_id__())!.sawNonDisabled =
true;
}
}
}
for (const [
id,
{ sawDisabled, sawNonDisabled },
] of pluginsByInternalID.entries()) {
if (sawDisabled && sawNonDisabled) {
throw new Error(
`You have tried to install both ApolloServerPlugin${id} and ` +
`ApolloServerPlugin${id}Disabled in your server. Please choose ` +
`whether or not you want to disable the feature and install the ` +
`appropriate plugin for your use case.`,
);
}
}

// Special case: cache control is on unless you explicitly disable it.
{
if (!alreadyHavePluginWithInternalId('CacheControl')) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ import {
ApolloServerPluginUsageReportingOptions,
ApolloServerPluginUsageReporting,
} from '../../../plugin/usageReporting';
import { ApolloServerPluginCacheControlDisabled } from '../../../plugin/disabled';
import {
ApolloServerPluginCacheControlDisabled,
ApolloServerPluginUsageReportingDisabled,
} from '../../../plugin/disabled';
import { describe, it, expect, afterEach } from '@jest/globals';

const quietLogger = loglevel.getLogger('quiet');
Expand Down Expand Up @@ -525,3 +528,19 @@ describe('sendHeaders makeHTTPRequestHeaders helper', () => {
expect(http.requestHeaders['set-cookie']).toBe(undefined);
});
});

it('cannot combine enabling with disabling', async () => {
const server = new ApolloServer({
typeDefs: 'type Query { x: ID }',
plugins: [
ApolloServerPluginUsageReporting(),
ApolloServerPluginUsageReportingDisabled(),
],
});
await expect(server.start()).rejects.toThrow(
'You have tried to install both ApolloServerPluginUsageReporting ' +
'and ApolloServerPluginUsageReportingDisabled in your server. Please ' +
'choose whether or not you want to disable the feature and install the ' +
'appropriate plugin for your use case.',
);
});
1 change: 1 addition & 0 deletions packages/server/src/internalPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface InternalApolloServerPlugin<TContext extends BaseContext>
// Used to identify a few specific plugins that are instantiated
// by default if not explicitly used or disabled.
__internal_plugin_id__(): InternalPluginId;
__is_disabled_plugin__(): boolean;
}

// Helper function for writing internal plugins which lets you write an object
Expand Down
3 changes: 3 additions & 0 deletions packages/server/src/plugin/cacheControl/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ export function ApolloServerPluginCacheControl(
__internal_plugin_id__() {
return 'CacheControl';
},
__is_disabled_plugin__() {
return false;
},

async serverWillStart({ schema }) {
// Set the size of the caches to be equal to the number of composite types
Expand Down
7 changes: 7 additions & 0 deletions packages/server/src/plugin/disabled/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ function disabledPlugin(id: InternalPluginId): ApolloServerPlugin {
__internal_plugin_id__() {
return id;
},
__is_disabled_plugin__() {
return true;
},
};
return plugin;
}
Expand All @@ -33,6 +36,10 @@ export function ApolloServerPluginLandingPageDisabled(): ApolloServerPlugin<Base
return disabledPlugin('LandingPageDisabled');
}

export function ApolloServerPluginSchemaReportingDisabled(): ApolloServerPlugin<BaseContext> {
return disabledPlugin('SchemaReporting');
}

export function ApolloServerPluginUsageReportingDisabled(): ApolloServerPlugin<BaseContext> {
return disabledPlugin('UsageReporting');
}
3 changes: 3 additions & 0 deletions packages/server/src/plugin/inlineTrace/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ export function ApolloServerPluginInlineTrace(
__internal_plugin_id__() {
return 'InlineTrace';
},
__is_disabled_plugin__() {
return false;
},
async serverWillStart({ schema, logger }) {
// Handle the case that the plugin was implicitly installed. We only want it
// to actually be active if the schema appears to be federated. If you don't
Expand Down
3 changes: 3 additions & 0 deletions packages/server/src/plugin/schemaReporting/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ export function ApolloServerPluginSchemaReporting(
__internal_plugin_id__() {
return 'SchemaReporting';
},
__is_disabled_plugin__() {
return false;
},
async serverWillStart({ apollo, schema, logger }) {
const { key, graphRef } = apollo;
if (!key) {
Expand Down
3 changes: 3 additions & 0 deletions packages/server/src/plugin/usageReporting/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ export function ApolloServerPluginUsageReporting<TContext extends BaseContext>(
__internal_plugin_id__() {
return 'UsageReporting';
},
__is_disabled_plugin__() {
return false;
},

// We want to be able to access locals from `serverWillStart` in our `requestDidStart`, thus
// this little hack. (Perhaps we should also allow GraphQLServerListener to contain
Expand Down

0 comments on commit 0d3a5ea

Please sign in to comment.