Skip to content

use resolvers-composition instead of graphql-middleware #1499

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: v8
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/thick-bags-juggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'graphql-shield': major
---

use resolvers-composition instead of graphql-middleware
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"@changesets/changelog-github": "0.4.6",
"@types/jest": "28.1.6",
"@types/node": "18.6.4",
"babel-jest": "29.3.1",
"babel-jest": "28.1.3",
"bob-the-bundler": "^4.0.0",
"codecov": "3.8.3",
"husky": "8.0.1",
Expand Down
10 changes: 6 additions & 4 deletions packages/graphql-shield/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
"check": "tsc --noEmit"
},
"dependencies": {
"@graphql-tools/delegate": "9.0.17",
"@graphql-tools/wrap": "9.2.16",
"@graphql-tools/resolvers-composition": "6.5.12",
"@graphql-tools/schema": "9.0.10",
"@graphql-tools/utils": "9.1.1",
"@types/yup": "0.29.13",
"object-hash": "^3.0.0",
"tslib": "^2.4.0",
Expand All @@ -26,13 +31,10 @@
"@types/request-promise-native": "1.0.18",
"apollo-server": "3.10.0",
"graphql": "16.5.0",
"graphql-middleware": "6.1.33",
"graphql-shield-rules": "0.0.1",
"node-fetch": "^2.6.7"
},
"peerDependencies": {
"graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
"graphql-middleware": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^6.0.0"
"graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
},
"license": "MIT",
"main": "dist/cjs/index.js",
Expand Down
71 changes: 71 additions & 0 deletions packages/graphql-shield/src/fragments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// copied from https://github.com/dimatill/graphql-middleware/blob/1c33515adb45da9a7358e03d56cd5edbf6918649/src/fragments.ts
import { InlineFragmentNode, Kind, OperationDefinitionNode, parse, print } from 'graphql'

export type FragmentReplacement = {
field: string
fragment: string
}

function parseFragmentToInlineFragment(definitions: string): InlineFragmentNode {
if (definitions.trim().startsWith('fragment')) {
const document = parse(definitions)
for (const definition of document.definitions) {
if (definition.kind === Kind.FRAGMENT_DEFINITION) {
return {
kind: Kind.INLINE_FRAGMENT,
typeCondition: definition.typeCondition,
selectionSet: definition.selectionSet,
}
}
}
}

const query = parse(`{${definitions}}`).definitions[0] as OperationDefinitionNode
for (const selection of query.selectionSet.selections) {
if (selection.kind === Kind.INLINE_FRAGMENT) {
return selection
}
}

throw new Error('Could not parse fragment')
}

export function prepareFragmentReplacements(fragmentReplacements: FragmentReplacement[]) {
return fragmentReplacements
.filter((fragment) => Boolean(fragment))
.map((fragmentReplacement) => {
const fragment = parseFragmentToInlineFragment(fragmentReplacement.fragment)

const newSelections = fragment.selectionSet.selections.filter((node) => {
switch (node.kind) {
case Kind.FIELD: {
return node.name.value !== fragmentReplacement.field
}
default: {
return true
}
}
})

if (newSelections.length === 0) {
return null
}

const newFragment: InlineFragmentNode = {
...fragment,
selectionSet: {
kind: fragment.selectionSet.kind,
loc: fragment.selectionSet.loc,
selections: newSelections,
},
}

const parsedFragment = print(newFragment)

return {
field: fragmentReplacement.field,
fragment: parsedFragment,
}
})
.filter((fr): fr is NonNullable<typeof fr> => fr !== null)
}
96 changes: 28 additions & 68 deletions packages/graphql-shield/src/generator.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,23 @@
import {
IMiddleware,
IMiddlewareFunction,
IMiddlewareGeneratorConstructor,
} from 'graphql-middleware'
import {
GraphQLSchema,
GraphQLObjectType,
isObjectType,
isIntrospectionType,
GraphQLResolveInfo,
GraphQLFieldResolver,
} from 'graphql'
import {
IRules,
IOptions,
ShieldRule,
IRuleFieldMap,
IShieldContext,
IMiddlewareWithOptions,
IMiddlewareTypeMap,
IMiddlewareFieldMap,
} from './types.js'
import {
isRuleFunction,
isRuleFieldMap,
isRule,
isLogicRule,
withDefault,
} from './utils.js'
import { isRuleFunction, isRuleFieldMap, isRule, isLogicRule, withDefault } from './utils.js'
import { ValidationError } from './validation.js'
import { IMiddlewareWithOptions } from 'graphql-middleware/dist/types'

/**
*
Expand All @@ -38,14 +30,9 @@ import { IMiddlewareWithOptions } from 'graphql-middleware/dist/types'
function generateFieldMiddlewareFromRule(
rule: ShieldRule,
options: IOptions,
): IMiddlewareFunction<object, object, IShieldContext> {
): IMiddlewareWithOptions<object, IShieldContext, object> {
async function middleware(
resolve: (
parent: object,
args: object,
ctx: IShieldContext,
info: GraphQLResolveInfo,
) => Promise<any>,
resolve: GraphQLFieldResolver<object, IShieldContext, object>,
parent: { [key: string]: any },
args: { [key: string]: any },
ctx: IShieldContext,
Expand All @@ -65,9 +52,12 @@ function generateFieldMiddlewareFromRule(
// Execution
try {
const res = await rule.resolve(parent, args, ctx, info, options)

if (res === true) {
return await resolve(parent, args, ctx, info)
const result = await resolve(parent, args, ctx, info)
if (result instanceof Error) {
throw result
}
return result
} else if (res === false) {
if (typeof options.fallbackError === 'function') {
return await options.fallbackError(null, parent, args, ctx, info)
Expand All @@ -94,17 +84,19 @@ function generateFieldMiddlewareFromRule(
return {
fragment: rule.extractFragment(),
resolve: middleware,
} as IMiddlewareWithOptions<object, object, IShieldContext>
}
}

if (isLogicRule(rule)) {
return {
fragments: rule.extractFragments(),
resolve: middleware,
} as IMiddlewareWithOptions<object, object, IShieldContext>
}
}

return middleware as IMiddlewareFunction<object, object, IShieldContext>
return {
resolve: middleware,
}
}

/**
Expand All @@ -116,16 +108,12 @@ function generateFieldMiddlewareFromRule(
* Generates middleware from rule for a particular type.
*
*/
function applyRuleToType(
type: GraphQLObjectType,
rules: ShieldRule | IRuleFieldMap,
options: IOptions,
): IMiddleware {
function applyRuleToType(type: GraphQLObjectType, rules: ShieldRule | IRuleFieldMap, options: IOptions): IMiddlewareFieldMap {
if (isRuleFunction(rules)) {
/* Apply defined rule function to every field */
const fieldMap = type.getFields()

const middleware = Object.keys(fieldMap).reduce((middleware, field) => {
const middleware = Object.keys(fieldMap).reduce<IMiddlewareFieldMap>((middleware, field) => {
return {
...middleware,
[field]: generateFieldMiddlewareFromRule(rules, options),
Expand All @@ -138,8 +126,7 @@ function applyRuleToType(
const fieldMap = type.getFields()

/* Extract default type wildcard if any and remove it for validation */
const defaultTypeRule = rules['*']
const {'*': _, ...rulesWithoutWildcard} = rules;
const { '*': defaultTypeRule, ...rulesWithoutWildcard } = rules
/* Validation */

const fieldErrors = Object.keys(rulesWithoutWildcard)
Expand All @@ -155,13 +142,10 @@ function applyRuleToType(

/* Generation */

const middleware = Object.keys(fieldMap).reduce(
const middleware = Object.keys(fieldMap).reduce<IMiddlewareFieldMap>(
(middleware, field) => ({
...middleware,
[field]: generateFieldMiddlewareFromRule(
withDefault(defaultTypeRule || options.fallbackRule)(rules[field]),
options,
),
[field]: generateFieldMiddlewareFromRule(withDefault(defaultTypeRule || options.fallbackRule)(rules[field]), options),
}),
{},
)
Expand All @@ -171,7 +155,7 @@ function applyRuleToType(
/* Apply fallbackRule to type with no defined rule */
const fieldMap = type.getFields()

const middleware = Object.keys(fieldMap).reduce(
const middleware = Object.keys(fieldMap).reduce<IMiddlewareFieldMap>(
(middleware, field) => ({
...middleware,
[field]: generateFieldMiddlewareFromRule(options.fallbackRule, options),
Expand All @@ -192,16 +176,12 @@ function applyRuleToType(
* Applies the same rule over entire schema.
*
*/
function applyRuleToSchema(
schema: GraphQLSchema,
rule: ShieldRule,
options: IOptions,
): IMiddleware {
function applyRuleToSchema(schema: GraphQLSchema, rule: ShieldRule, options: IOptions): IMiddlewareTypeMap {
const typeMap = schema.getTypeMap()

const middleware = Object.keys(typeMap)
.filter((type) => !isIntrospectionType(typeMap[type]))
.reduce((middleware, typeName) => {
.reduce<IMiddlewareTypeMap>((middleware, typeName) => {
const type = typeMap[typeName]

if (isObjectType(type)) {
Expand All @@ -225,11 +205,11 @@ function applyRuleToSchema(
* Converts rule tree to middleware.
*
*/
function generateMiddlewareFromSchemaAndRuleTree(
export function generateMiddlewareFromSchemaAndRuleTree(
schema: GraphQLSchema,
rules: IRules,
options: IOptions,
): IMiddleware {
): IMiddlewareTypeMap {
if (isRuleFunction(rules)) {
/* Applies rule to entire schema. */
return applyRuleToSchema(schema, rules, options)
Expand All @@ -256,7 +236,7 @@ function generateMiddlewareFromSchemaAndRuleTree(

const middleware = Object.keys(typeMap)
.filter((type) => !isIntrospectionType(typeMap[type]))
.reduce<IMiddleware>((middleware, typeName) => {
.reduce<IMiddlewareTypeMap>((middleware, typeName) => {
const type = typeMap[typeName]

if (isObjectType(type)) {
Expand All @@ -272,23 +252,3 @@ function generateMiddlewareFromSchemaAndRuleTree(
return middleware
}
}

/**
*
* @param ruleTree
* @param options
*
* Generates middleware from given rules.
*
*/
export function generateMiddlewareGeneratorFromRuleTree<
TSource = any,
TContext = any,
TArgs = any
>(
ruleTree: IRules,
options: IOptions,
): IMiddlewareGeneratorConstructor<TSource, TContext, TArgs> {
return (schema: GraphQLSchema) =>
generateMiddlewareFromSchemaAndRuleTree(schema, ruleTree, options)
}
Loading