Skip to content

Commit

Permalink
refactor: remove directiveToExtensionsTransform
Browse files Browse the repository at this point in the history
  • Loading branch information
n1ru4l committed Jul 28, 2021
1 parent a112aee commit 345fda6
Show file tree
Hide file tree
Showing 8 changed files with 252 additions and 158 deletions.
5 changes: 5 additions & 0 deletions .changeset/lovely-weeks-allow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@n1ru4l/graphql-public-schema-filter": patch
---

handle interface fields annotated with directives
7 changes: 7 additions & 0 deletions .changeset/polite-ads-trade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@n1ru4l/graphql-public-schema-filter": minor
---

BREAKING: remove `directiveToExtensionsTransform`, the `buildPublicSchema` now checks both extension fields and directive usages.

BREAKING: Change function parameter signature for `buildPublicSchema` to an object, allow overwriting the `isPublic` function which is used to determine whether a field should be public based on extensions and or schema directives.
31 changes: 25 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,20 @@ const GraphQLQueryType = new GraphQLObjectType({
const privateSchema = new GraphQLSchema({
query: GraphQLQueryType,
});
const publicSchema = buildPublicSchema(privateSchema);
const publicSchema = buildPublicSchema({ schema: privateSchema });
// serve privateSchema or publicSchema based on the request :)
```

You can also find this example within `examples/src/schema.ts`.

### SDL-First

This package exposes a `directiveToExtensionsTransform` function that can be passed to the `makeExecutableSchema` `schemaTransforms` options. It will map schema fragment usages to the `isPublic` extension fields.
Instead of using the extension fields, we use the `@public` directive.

```tsx
import { makeExecutableSchema } from "@graphql-tools/schema";
import {
publicDirectiveSDL,
directiveToExtensionsTransform,
buildPublicSchema,
} from "@n1ru4l/graphql-public-schema-filter";

Expand All @@ -84,9 +83,8 @@ const source = /* GraphQL */ `

const privateSchema = makeExecutableSchema({
typeDefs: [publicDirectiveSDL, source],
schemaTransforms: [directiveToExtensionsTransform],
});
const publicSchema = buildPublicSchema(privateSchema);
const publicSchema = buildPublicSchema({ schema: privateSchema });
// serve privateSchema or publicSchema based on the request :)
```

Expand All @@ -100,7 +98,28 @@ Deny-listing is more prone to errors than allow-listing. By adding a directive/e

I considered this at the beginning, but in practice we never had a use for this. Having multiple public schemas requires maintaining a lot of documentation. In our use-case we only have a public and a private schema. There is still role based access for the public schema. certain users are not allowed to select specific fields. Instead of hiding those fields for those users we instead deny operations that select fields the users are not allowed to select before even executing it with the [envelop `useOperationFieldPermissions` plugin](https://www.envelop.dev/plugins/use-operation-field-permissions).

If you need to build many unique schemas based on different parameters and think it is a good idea please open a issue or a pull request so we can discuss a implementation.
You can overwrite the `isPublic` function which is used to determine whether a field or type is public based on directives and extensions. This allows to fully customize the behavior based on your needs.

```ts
type SharedExtensionAndDirectiveInformation = {
extensions?: Maybe<{
[attributeName: string]: any;
}>;
astNode?: Maybe<
Readonly<{
directives?: ReadonlyArray<DirectiveNode>;
}>
>;
};

export const defaultIsPublic = (
input: SharedExtensionAndDirectiveInformation
): boolean =>
input.extensions?.["isPublic"] === true ||
!!input.astNode?.directives?.find(
(directive) => directive.name.value === "public"
);
```

## License

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"husky": "7.0.1",
"jest": "27.0.6",
"lint-staged": "11.0.0",
"patch-package": "6.4.7",
"prettier": "2.3.2",
"ts-jest": "27.0.3",
"typescript": "4.3.5"
Expand All @@ -54,6 +55,7 @@
"graphql": "15.x.x"
},
"scripts": {
"postinstall": "patch-package",
"test": "jest",
"build": "tsc && bob build --single",
"ci:eslint": "eslint --ext .ts,.js,.tsx --ignore-path .gitignore .",
Expand Down
26 changes: 26 additions & 0 deletions patches/@graphql-codegen+testing+1.17.7.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
diff --git a/node_modules/@graphql-codegen/testing/index.cjs.js b/node_modules/@graphql-codegen/testing/index.cjs.js
index 0df1c42..d6050a9 100644
--- a/node_modules/@graphql-codegen/testing/index.cjs.js
+++ b/node_modules/@graphql-codegen/testing/index.cjs.js
@@ -7,7 +7,7 @@ function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'defau
const commonTags = require('common-tags');
const path = require('path');
const fs = require('fs');
-const diff = _interopDefault(require('jest-diff'));
+const {diff} = _interopDefault(require('jest-diff'));
const typescript = require('typescript');
const open = _interopDefault(require('open'));

diff --git a/node_modules/@graphql-codegen/testing/index.esm.js b/node_modules/@graphql-codegen/testing/index.esm.js
index 38c193c..c299a15 100644
--- a/node_modules/@graphql-codegen/testing/index.esm.js
+++ b/node_modules/@graphql-codegen/testing/index.esm.js
@@ -1,7 +1,7 @@
import { oneLine, stripIndent } from 'common-tags';
import { resolve, join, dirname } from 'path';
import { existsSync } from 'fs';
-import diff from 'jest-diff';
+import {diff} from 'jest-diff';
import { ModuleResolutionKind, ScriptTarget, JsxEmit, ModuleKind, createSourceFile, ScriptKind, flattenDiagnosticMessageText, createCompilerHost, createProgram } from 'typescript';
import open from 'open';

76 changes: 60 additions & 16 deletions src/public-schema-filter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
const buildSchema = (source: string) =>
makeExecutableSchema({
typeDefs: [publicDirectiveSDL, source],
schemaTransforms: [lib.directiveToExtensionsTransform],
});

const printIntrospectionSdl = (filteredSchema: GraphQLSchema) => {
Expand Down Expand Up @@ -71,7 +70,7 @@ it("can be called", () => {

const schema = buildSchema(source);

const filteredSchema = lib.buildPublicSchema(schema);
const filteredSchema = lib.buildPublicSchema({ schema });

expectGraphQlSdlEqual(
filteredSchema,
Expand All @@ -98,7 +97,7 @@ it("does not expose the public directive", () => {

const schema = buildSchema(source);

const filteredSchema = lib.buildPublicSchema(schema);
const filteredSchema = lib.buildPublicSchema({ schema });
const sdl = printIntrospectionSdl(filteredSchema);

expect(sdl.includes("public")).toEqual(false);
Expand All @@ -119,7 +118,7 @@ it("makes type public when its field is public", () => {

const schema = buildSchema(source);

const filteredSchema = lib.buildPublicSchema(schema);
const filteredSchema = lib.buildPublicSchema({ schema });

expectGraphQlSdlEqual(
filteredSchema,
Expand Down Expand Up @@ -153,7 +152,7 @@ it("makes type public when its extend field is public", () => {

const schema = buildSchema(source);

const filteredSchema = lib.buildPublicSchema(schema);
const filteredSchema = lib.buildPublicSchema({ schema });

expectGraphQlSdlEqual(
filteredSchema,
Expand Down Expand Up @@ -184,7 +183,7 @@ it("makes fields public when type is public", () => {

const schema = buildSchema(source);

const filteredSchema = lib.buildPublicSchema(schema);
const filteredSchema = lib.buildPublicSchema({ schema });

expectGraphQlSdlEqual(
filteredSchema,
Expand Down Expand Up @@ -216,7 +215,7 @@ it("what if type is not public", () => {

const schema = buildSchema(source);

const filteredSchema = lib.buildPublicSchema(schema);
const filteredSchema = lib.buildPublicSchema({ schema });

expectGraphQlSdlEqual(
filteredSchema,
Expand Down Expand Up @@ -249,7 +248,7 @@ it("does not make unions public when type is not public", () => {

const schema = buildSchema(source);

const filteredSchema = lib.buildPublicSchema(schema);
const filteredSchema = lib.buildPublicSchema({ schema });

expectGraphQlSdlEqual(
filteredSchema,
Expand Down Expand Up @@ -282,7 +281,7 @@ it("makes unions public when type is public", () => {

const schema = buildSchema(source);

const filteredSchema = lib.buildPublicSchema(schema);
const filteredSchema = lib.buildPublicSchema({ schema });

expectGraphQlSdlEqual(
filteredSchema,
Expand All @@ -306,7 +305,7 @@ it("makes unions public when type is public", () => {
);
});

it("hides field/type if its interface is public", () => {
it("hides field/type if its interface is not public", () => {
const source = /* GraphQL */ `
interface Node {
id: ID!
Expand All @@ -331,7 +330,7 @@ it("hides field/type if its interface is public", () => {

const schema = buildSchema(source);

const filteredSchema = lib.buildPublicSchema(schema);
const filteredSchema = lib.buildPublicSchema({ schema });

expectGraphQlSdlEqual(
filteredSchema,
Expand Down Expand Up @@ -368,7 +367,52 @@ it("exposes field/type if its interface is public", () => {

const schema = buildSchema(source);

const filteredSchema = lib.buildPublicSchema(schema);
const filteredSchema = lib.buildPublicSchema({ schema });

expectGraphQlSdlEqual(
filteredSchema,
/* GraphQL */ `
interface Node {
id: ID!
}
type Person implements Node {
id: ID!
name: String!
}
type Query {
person: Person
hello2: String
}
`
);
});

it("exposes interface if interface field is public", () => {
const source = /* GraphQL */ `
interface Node {
id: ID! @public
}
type Book implements Node {
id: ID!
title: String!
}
type Person implements Node @public {
id: ID!
name: String!
}
type Query {
node: Node
person: Person @public
hello2: String @public
}
`;

const schema = buildSchema(source);

const filteredSchema = lib.buildPublicSchema({ schema });

expectGraphQlSdlEqual(
filteredSchema,
Expand Down Expand Up @@ -406,7 +450,7 @@ it("hides mutation with input types that are not marked as public", () => {

const schema = buildSchema(source);

const filteredSchema = lib.buildPublicSchema(schema);
const filteredSchema = lib.buildPublicSchema({ schema });

expectGraphQlSdlEqual(
filteredSchema,
Expand Down Expand Up @@ -440,7 +484,7 @@ it("shows mutation with input types that are marked as public", () => {

const schema = buildSchema(source);

const filteredSchema = lib.buildPublicSchema(schema);
const filteredSchema = lib.buildPublicSchema({ schema });

expectGraphQlSdlEqual(
filteredSchema,
Expand Down Expand Up @@ -477,7 +521,7 @@ it("exposes Scalars correctly", () => {

const schema = buildSchema(source);

const filteredSchema = lib.buildPublicSchema(schema);
const filteredSchema = lib.buildPublicSchema({ schema });

expectGraphQlSdlEqual(
filteredSchema,
Expand Down Expand Up @@ -509,7 +553,7 @@ it("exposes Input fields correctly", () => {
`;

const schema = buildSchema(source);
const filteredSchema = lib.buildPublicSchema(schema);
const filteredSchema = lib.buildPublicSchema({ schema });

expectGraphQlSdlEqual(
filteredSchema,
Expand Down
Loading

0 comments on commit 345fda6

Please sign in to comment.