Skip to content

Commit 69ae08e

Browse files
hector-del-rioconfuser
authored andcommitted
feat: added FIELD_DEFINITION support (confuser#17)
1 parent 13e0985 commit 69ae08e

File tree

6 files changed

+1163
-6
lines changed

6 files changed

+1163
-6
lines changed

index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class ConstraintDirective extends SchemaDirectiveVisitor {
1515
return new GraphQLDirective({
1616
name: directiveName,
1717
locations: [
18+
DirectiveLocation.FIELD_DEFINITION,
1819
DirectiveLocation.INPUT_FIELD_DEFINITION
1920
],
2021
args: {
@@ -42,6 +43,10 @@ class ConstraintDirective extends SchemaDirectiveVisitor {
4243
this.wrapType(field)
4344
}
4445

46+
visitFieldDefinition (field) {
47+
this.wrapType(field)
48+
}
49+
4550
wrapType (field) {
4651
const fieldName = field.astNode.name.value
4752

scalars/number.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ module.exports = class ConstraintNumberType extends GraphQLScalarType {
66
super({
77
name: `ConstraintNumber`,
88
serialize (value) {
9-
return type.serialize(value)
9+
value = type.serialize(value)
10+
11+
validate(fieldName, args, value)
12+
13+
return value
1014
},
1115
parseValue (value) {
1216
value = type.serialize(value)

scalars/string.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ module.exports = class ConstraintStringType extends GraphQLScalarType {
88
super({
99
name: `ConstraintString`,
1010
serialize (value) {
11-
return type.serialize(value)
11+
value = type.serialize(value)
12+
13+
validate(fieldName, args, value)
14+
15+
return value
1216
},
1317
parseValue (value) {
1418
value = type.serialize(value)

test/int.test.js

Lines changed: 288 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const formatError = (error) => {
66
return { message, code, fieldName, context }
77
}
88

9-
describe('@constraint Int', function () {
9+
describe('@constraint Int in INPUT_FIELD_DEFINITION', function () {
1010
const query = `mutation createBook($input: BookInput) {
1111
createBook(input: $input) {
1212
title
@@ -312,3 +312,290 @@ describe('@constraint Int', function () {
312312
})
313313
})
314314
})
315+
316+
describe('@constraint Int in FIELD_DEFINITION', function () {
317+
const query = `query {
318+
books {
319+
title
320+
}
321+
}`
322+
const resolvers = function (data) {
323+
return {
324+
Query: {
325+
books () {
326+
return data
327+
}
328+
}
329+
}
330+
}
331+
332+
describe('#min', function () {
333+
before(function () {
334+
this.typeDefs = `
335+
type Query {
336+
books: [Book]
337+
}
338+
type Book {
339+
title: Int @constraint(min: 2)
340+
}
341+
`
342+
})
343+
344+
it('should pass', async function () {
345+
const mockData = [{title: 2}, {title: 3}]
346+
const request = setup(this.typeDefs, formatError, resolvers(mockData))
347+
const { body, statusCode } = await request
348+
.post('/graphql')
349+
.set('Accept', 'application/json')
350+
.send({ query })
351+
352+
strictEqual(statusCode, 200)
353+
deepStrictEqual(body, { data: { books: mockData } })
354+
})
355+
356+
it('should fail', async function () {
357+
const mockData = [{title: 1}, {title: 2}]
358+
const request = setup(this.typeDefs, formatError, resolvers(mockData))
359+
const { body, statusCode } = await request
360+
.post('/graphql')
361+
.set('Accept', 'application/json')
362+
.send({ query })
363+
364+
strictEqual(statusCode, 200)
365+
strictEqual(body.errors[0].message, 'Must be at least 2')
366+
})
367+
368+
it('should throw custom error', async function () {
369+
const mockData = [{title: 1}, {title: 2}]
370+
const request = setup(this.typeDefs, formatError, resolvers(mockData))
371+
const { body, statusCode } = await request
372+
.post('/graphql')
373+
.set('Accept', 'application/json')
374+
.send({ query })
375+
376+
strictEqual(statusCode, 200)
377+
deepStrictEqual(body.errors[0], {
378+
message: 'Must be at least 2',
379+
code: 'ERR_GRAPHQL_CONSTRAINT_VALIDATION',
380+
fieldName: 'title',
381+
context: [{ arg: 'min', value: 2 }]
382+
})
383+
})
384+
})
385+
386+
describe('#max', function () {
387+
before(function () {
388+
this.typeDefs = `
389+
type Query {
390+
books: [Book]
391+
}
392+
type Book {
393+
title: Int @constraint(max: 2)
394+
}
395+
`
396+
})
397+
398+
it('should pass', async function () {
399+
const mockData = [{title: 1}, {title: 2}]
400+
const request = setup(this.typeDefs, formatError, resolvers(mockData))
401+
const { body, statusCode } = await request
402+
.post('/graphql')
403+
.set('Accept', 'application/json')
404+
.send({ query })
405+
406+
strictEqual(statusCode, 200)
407+
deepStrictEqual(body, { data: { books: mockData } })
408+
})
409+
410+
it('should fail', async function () {
411+
const mockData = [{title: 2}, {title: 3}]
412+
const request = setup(this.typeDefs, formatError, resolvers(mockData))
413+
const { body, statusCode } = await request
414+
.post('/graphql')
415+
.set('Accept', 'application/json')
416+
.send({ query })
417+
418+
strictEqual(statusCode, 200)
419+
strictEqual(body.errors[0].message, 'Must be no greater than 2')
420+
})
421+
422+
it('should throw custom error', async function () {
423+
const mockData = [{title: 2}, {title: 3}]
424+
const request = setup(this.typeDefs, formatError, resolvers(mockData))
425+
const { body, statusCode } = await request
426+
.post('/graphql')
427+
.set('Accept', 'application/json')
428+
.send({ query })
429+
430+
strictEqual(statusCode, 200)
431+
deepStrictEqual(body.errors[0], {
432+
message: 'Must be no greater than 2',
433+
code: 'ERR_GRAPHQL_CONSTRAINT_VALIDATION',
434+
fieldName: 'title',
435+
context: [{ arg: 'max', value: 2 }]
436+
})
437+
})
438+
})
439+
440+
describe('#exclusiveMin', function () {
441+
before(function () {
442+
this.typeDefs = `
443+
type Query {
444+
books: [Book]
445+
}
446+
type Book {
447+
title: Int @constraint(exclusiveMin: 2)
448+
}
449+
`
450+
})
451+
452+
it('should pass', async function () {
453+
const mockData = [{title: 3}, {title: 4}]
454+
const request = setup(this.typeDefs, formatError, resolvers(mockData))
455+
const { body, statusCode } = await request
456+
.post('/graphql')
457+
.set('Accept', 'application/json')
458+
.send({ query })
459+
460+
strictEqual(statusCode, 200)
461+
deepStrictEqual(body, { data: { books: mockData } })
462+
})
463+
464+
it('should fail', async function () {
465+
const mockData = [{title: 2}, {title: 3}]
466+
const request = setup(this.typeDefs, formatError, resolvers(mockData))
467+
const { body, statusCode } = await request
468+
.post('/graphql')
469+
.set('Accept', 'application/json')
470+
.send({ query })
471+
472+
strictEqual(statusCode, 200)
473+
strictEqual(body.errors[0].message, 'Must be greater than 2')
474+
})
475+
476+
it('should throw custom error', async function () {
477+
const mockData = [{title: 2}, {title: 3}]
478+
const request = setup(this.typeDefs, formatError, resolvers(mockData))
479+
const { body, statusCode } = await request
480+
.post('/graphql')
481+
.set('Accept', 'application/json')
482+
.send({ query })
483+
484+
strictEqual(statusCode, 200)
485+
deepStrictEqual(body.errors[0], {
486+
message: 'Must be greater than 2',
487+
code: 'ERR_GRAPHQL_CONSTRAINT_VALIDATION',
488+
fieldName: 'title',
489+
context: [{ arg: 'exclusiveMin', value: 2 }]
490+
})
491+
})
492+
})
493+
494+
describe('#exclusiveMax', function () {
495+
before(function () {
496+
this.typeDefs = `
497+
type Query {
498+
books: [Book]
499+
}
500+
type Book {
501+
title: Int @constraint(exclusiveMax: 2)
502+
}
503+
`
504+
})
505+
506+
it('should pass', async function () {
507+
const mockData = [{title: 0}, {title: 1}]
508+
const request = setup(this.typeDefs, formatError, resolvers(mockData))
509+
const { body, statusCode } = await request
510+
.post('/graphql')
511+
.set('Accept', 'application/json')
512+
.send({ query })
513+
514+
strictEqual(statusCode, 200)
515+
deepStrictEqual(body, { data: { books: mockData } })
516+
})
517+
518+
it('should fail', async function () {
519+
const mockData = [{title: 1}, {title: 2}]
520+
const request = setup(this.typeDefs, formatError, resolvers(mockData))
521+
const { body, statusCode } = await request
522+
.post('/graphql')
523+
.set('Accept', 'application/json')
524+
.send({ query })
525+
526+
strictEqual(statusCode, 200)
527+
strictEqual(body.errors[0].message, 'Must be no greater than 2')
528+
})
529+
530+
it('should throw custom error', async function () {
531+
const mockData = [{title: 1}, {title: 2}]
532+
const request = setup(this.typeDefs, formatError, resolvers(mockData))
533+
const { body, statusCode } = await request
534+
.post('/graphql')
535+
.set('Accept', 'application/json')
536+
.send({ query })
537+
538+
strictEqual(statusCode, 200)
539+
deepStrictEqual(body.errors[0], {
540+
message: 'Must be no greater than 2',
541+
code: 'ERR_GRAPHQL_CONSTRAINT_VALIDATION',
542+
fieldName: 'title',
543+
context: [{ arg: 'exclusiveMax', value: 2 }]
544+
})
545+
})
546+
})
547+
548+
describe('#multipleOf', function () {
549+
before(function () {
550+
this.typeDefs = `
551+
type Query {
552+
books: [Book]
553+
}
554+
type Book {
555+
title: Int @constraint(multipleOf: 2)
556+
}
557+
`
558+
})
559+
560+
it('should pass', async function () {
561+
const mockData = [{title: 2}, {title: 4}, {title: 6}]
562+
const request = setup(this.typeDefs, formatError, resolvers(mockData))
563+
const { body, statusCode } = await request
564+
.post('/graphql')
565+
.set('Accept', 'application/json')
566+
.send({ query })
567+
568+
strictEqual(statusCode, 200)
569+
deepStrictEqual(body, { data: { books: mockData } })
570+
})
571+
572+
it('should fail', async function () {
573+
const mockData = [{title: 1}, {title: 2}, {title: 3}]
574+
const request = setup(this.typeDefs, formatError, resolvers(mockData))
575+
const { body, statusCode } = await request
576+
.post('/graphql')
577+
.set('Accept', 'application/json')
578+
.send({ query })
579+
580+
strictEqual(statusCode, 200)
581+
strictEqual(body.errors[0].message, 'Must be a multiple of 2')
582+
})
583+
584+
it('should throw custom error', async function () {
585+
const mockData = [{title: 1}, {title: 2}, {title: 3}]
586+
const request = setup(this.typeDefs, formatError, resolvers(mockData))
587+
const { body, statusCode } = await request
588+
.post('/graphql')
589+
.set('Accept', 'application/json')
590+
.send({ query })
591+
592+
strictEqual(statusCode, 200)
593+
deepStrictEqual(body.errors[0], {
594+
message: 'Must be a multiple of 2',
595+
code: 'ERR_GRAPHQL_CONSTRAINT_VALIDATION',
596+
fieldName: 'title',
597+
context: [{ arg: 'multipleOf', value: 2 }]
598+
})
599+
})
600+
})
601+
})

test/setup.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ const { makeExecutableSchema } = require('graphql-tools')
55
const request = require('supertest')
66
const ConstraintDirective = require('../')
77

8-
module.exports = function (typeDefs, formatError) {
8+
module.exports = function (typeDefs, formatError, resolvers) {
99
const schema = makeExecutableSchema({
10-
typeDefs, schemaDirectives: { constraint: ConstraintDirective }
10+
typeDefs,
11+
schemaDirectives: { constraint: ConstraintDirective },
12+
resolvers
1113
})
1214
const app = express()
1315

0 commit comments

Comments
 (0)