Skip to content

Commit

Permalink
feat(validation): Added support for type and field validation.
Browse files Browse the repository at this point in the history
+ I have introduced tightening option of no locals to keep the code as clean as possible.
+ This also adds nice error messages for developers when they apply rule to a type or field that doesn't exist.
  • Loading branch information
maticzav committed Aug 30, 2018
1 parent 0c1395a commit 978f2c6
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 60 deletions.
29 changes: 28 additions & 1 deletion src/generator.ts
Expand Up @@ -4,9 +4,10 @@ import {
IMiddlewareGeneratorConstructor,
} from 'graphql-middleware'
import { GraphQLSchema, GraphQLObjectType, isObjectType } from 'graphql'
import { allow, deny } from './constructors'
import { IRules, IOptions, ShieldRule, IRuleFieldMap } from './types'
import { isRuleFunction, isRuleFieldMap, isRule, isLogicRule } from './utils'
import { allow, deny } from './constructors'
import { ValidationError } from './validation'

/**
*
Expand Down Expand Up @@ -99,6 +100,19 @@ function applyRuleToType(
} else if (isRuleFieldMap(rules)) {
const fieldMap = type.getFields()

// Validation

const fieldErrors = Object.keys(rules)
.filter(type => !Object.prototype.hasOwnProperty.call(fieldMap, type))
.map(field => `{type.name}.{field}`)
if (fieldErrors.length > 0) {
throw new ValidationError(
`It seems like you have applied rules to "${fieldErrors}" fields but Shield cannot find them in your schema.`,
)
}

// Generationn

const middleware = Object.keys(fieldMap).reduce((middleware, field) => {
if (rules[field]) {
return {
Expand Down Expand Up @@ -184,6 +198,19 @@ function generateMiddlewareFromSchemaAndRuleTree(
} else {
const typeMap = schema.getTypeMap()

// Validation

const typeErrors = Object.keys(rules).filter(
type => !Object.prototype.hasOwnProperty.call(typeMap, type),
)
if (typeErrors.length > 0) {
throw new ValidationError(
`It seems like you have applied rules to "${typeErrors}" types but Shield cannot find them in your schema.`,
)
}

// Generation

const middleware = Object.keys(typeMap)
.filter(type => isObjectType(typeMap[type]))
.reduce(
Expand Down
59 changes: 2 additions & 57 deletions src/test/generator.test.ts
@@ -1,9 +1,8 @@
import test from 'ava'
import { graphql } from 'graphql'
import { applyMiddleware, middleware } from 'graphql-middleware'
import { applyMiddleware } from 'graphql-middleware'
import { makeExecutableSchema } from 'graphql-tools'
import { shield, rule, allow, deny } from '../'
import { generateMiddlewareGeneratorFromRuleTree } from '../generator'
import { shield, allow, deny } from '../'

test('Generator - whitelist permissions.', async t => {
// Schema
Expand Down Expand Up @@ -161,60 +160,6 @@ test('Generator - blacklist permissions.', async t => {
t.not(res.errors.length, 0)
})

test('Generator - apply fragments in generated middleware correctly.', async t => {
// Schema
const typeDefs = `
type Query {
test: String!
type: Test!
}
type Test {
typeTest: String!
}
`
const resolvers = {
Query: {
test: () => 'pass',
},
Test: {
typeTest: () => 'pass',
},
}

const schema = makeExecutableSchema({
typeDefs,
resolvers,
})

// Permissions
const ruleWithFragment = rule({
fragment: 'pass',
})(async (parent, args, ctx, info) => {
return true
})

const permissions = shield({
Query: {
test: ruleWithFragment,
},
Test: ruleWithFragment,
})

const { fragmentReplacements } = applyMiddleware(schema, permissions)

t.deepEqual(fragmentReplacements, [
{
field: 'test',
fragment: 'pass',
},
{
field: 'typeTest',
fragment: 'pass',
},
])
})

test('Generator generates schema wide middleware correctly.', async t => {
// Schema
const typeDefs = `
Expand Down
77 changes: 77 additions & 0 deletions src/test/validation.test.ts
@@ -0,0 +1,77 @@
import test from 'ava'
import { applyMiddleware } from 'graphql-middleware'
import { makeExecutableSchema } from 'graphql-tools'
import { ValidationError } from '../validation'
import { shield, allow } from '../'

test('Finds a type missing in schema and warns developer.', async t => {
// Schema
const typeDefs = `
type Query {
a: String!
}
`
const resolvers = {
Query: {
a: () => 'a',
},
}

const schema = makeExecutableSchema({
typeDefs,
resolvers,
})

// Permissions

const permissions = shield({
Query: allow,
Fail1: allow,
Fail2: allow,
})

t.throws(
() => {
applyMiddleware(schema, permissions)
},
ValidationError,
`It seems like you have applied rules to Fail1, Fail2 types but Shield cannot find them in your schema.`,
)
})

test('Finds the fields missing in schema and warns developer.', async t => {
// Schema
const typeDefs = `
type Query {
a: String!
}
`
const resolvers = {
Query: {
a: () => 'a',
},
}

const schema = makeExecutableSchema({
typeDefs,
resolvers,
})

// Permissions

const permissions = shield({
Query: {
a: allow,
b: allow,
c: allow,
},
})

t.throws(
() => {
applyMiddleware(schema, permissions)
},
ValidationError,
`It seems like you have applied rules to Query.b, Query.c fields but Shield cannot find them in your schema.`,
)
})
5 changes: 3 additions & 2 deletions src/utils.ts
@@ -1,5 +1,6 @@
import { ShieldRule, IRules, ILogicRule, IRuleFieldMap } from './types'
import { Rule, LogicRule } from './rules'
import { ValidationError } from './validation'

/**
*
Expand Down Expand Up @@ -111,8 +112,8 @@ export function validateRules(ruleTree: IRules): IRules {
if (!_map.has(rule.name)) {
return _map.set(rule.name, rule)
} else if (!_map.get(rule.name).equals(rule)) {
throw new Error(
`Rule "${rule.name}" seems to point to two different things.`,
throw new ValidationError(
`Rule "${rule.name}" seems to point at two different things.`,
)
} else {
return _map
Expand Down
5 changes: 5 additions & 0 deletions src/validation.ts
@@ -0,0 +1,5 @@
export class ValidationError extends Error {
constructor(message: string) {
super(message)
}
}
1 change: 1 addition & 0 deletions tsconfig.json
Expand Up @@ -3,6 +3,7 @@
"target": "es6",
"module": "commonjs",
"moduleResolution": "node",
"noUnusedLocals": true,
"rootDir": "src",
"outDir": "dist",
"sourceMap": true,
Expand Down

0 comments on commit 978f2c6

Please sign in to comment.