Skip to content

Commit f189491

Browse files
authored
fix: support GraphQLNotNull. (#34)
fixes #33
1 parent b5920c9 commit f189491

File tree

3 files changed

+123
-26
lines changed

3 files changed

+123
-26
lines changed

index.js

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
const { GraphQLFloat, GraphQLInt, GraphQLString, isNonNullType, isScalarType } = require('graphql')
1+
const { GraphQLFloat, GraphQLInt, GraphQLString, GraphQLNonNull, isNonNullType, isScalarType } = require('graphql')
22
const { getDirectives, mapSchema, MapperKind } = require('graphql-tools')
33
const ConstraintStringType = require('./scalars/string')
44
const ConstraintNumberType = require('./scalars/number')
55

66
function constraintDirective () {
77
const constraintTypes = {}
88

9-
function getConstraintType (fieldName, type, directiveArgumentMap) {
9+
function getConstraintType (fieldName, type, notNull, directiveArgumentMap) {
1010
// Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ as per graphql-js
11-
const uniqueTypeName = `${fieldName}_${type.name}_` + Object.entries(directiveArgumentMap)
11+
const uniqueTypeName = `${fieldName}_${type.name}_${notNull ? 'NotNull_' : ''}` + Object.entries(directiveArgumentMap)
1212
.map(([key, value]) => `${key}_${value.toString().replace(/\W/g, '')}`)
1313
.join('_')
1414
const key = Symbol.for(uniqueTypeName)
@@ -17,9 +17,17 @@ function constraintDirective () {
1717
if (constraintType) return constraintType
1818

1919
if (type === GraphQLString) {
20-
constraintType = new ConstraintStringType(fieldName, uniqueTypeName, type, directiveArgumentMap)
20+
if (notNull) {
21+
constraintType = new GraphQLNonNull(new ConstraintStringType(fieldName, uniqueTypeName, type, directiveArgumentMap))
22+
} else {
23+
constraintType = new ConstraintStringType(fieldName, uniqueTypeName, type, directiveArgumentMap)
24+
}
2125
} else if (type === GraphQLFloat || type === GraphQLInt) {
22-
constraintType = new ConstraintNumberType(fieldName, uniqueTypeName, type, directiveArgumentMap)
26+
if (notNull) {
27+
constraintType = new GraphQLNonNull(new ConstraintNumberType(fieldName, uniqueTypeName, type, directiveArgumentMap))
28+
} else {
29+
constraintType = new ConstraintNumberType(fieldName, uniqueTypeName, type, directiveArgumentMap)
30+
}
2331
} else {
2432
throw new Error(`Not a valid scalar type: ${type.toString()}`)
2533
}
@@ -30,10 +38,11 @@ function constraintDirective () {
3038
}
3139

3240
function wrapType (fieldConfig, directiveArgumentMap) {
33-
let originalType
41+
let originalType, notNull
3442

3543
if (isNonNullType(fieldConfig.type)) {
3644
originalType = fieldConfig.type.ofType
45+
notNull = true
3746
} else if (isScalarType(fieldConfig.type)) {
3847
originalType = fieldConfig.type
3948
} else {
@@ -42,7 +51,7 @@ function constraintDirective () {
4251

4352
const fieldName = fieldConfig.astNode.name.value
4453

45-
fieldConfig.type = getConstraintType(fieldName, originalType, directiveArgumentMap)
54+
fieldConfig.type = getConstraintType(fieldName, originalType, notNull, directiveArgumentMap)
4655
}
4756

4857
return schema => mapSchema(schema, {

test/int.test.js

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ describe('@constraint Int in INPUT_FIELD_DEFINITION', function () {
5252

5353
strictEqual(statusCode, 400)
5454
strictEqual(body.errors[0].message,
55-
'Variable "$input" got invalid value 2 at "input.title"; Expected type "title_Int_min_3". Must be at least 3')
55+
'Variable "$input" got invalid value 2 at "input.title"; Expected type "title_Int_NotNull_min_3". Must be at least 3')
5656
})
5757

5858
it('should throw custom error', async function () {
@@ -172,7 +172,7 @@ describe('@constraint Int in INPUT_FIELD_DEFINITION', function () {
172172

173173
strictEqual(statusCode, 400)
174174
strictEqual(body.errors[0].message,
175-
'Variable "$input" got invalid value 3 at "input.title"; Expected type "title_Int_exclusiveMin_3". Must be greater than 3')
175+
'Variable "$input" got invalid value 3 at "input.title"; Expected type "title_Int_NotNull_exclusiveMin_3". Must be greater than 3')
176176
})
177177

178178
it('should throw custom error', async function () {
@@ -233,7 +233,7 @@ describe('@constraint Int in INPUT_FIELD_DEFINITION', function () {
233233

234234
strictEqual(statusCode, 400)
235235
strictEqual(body.errors[0].message,
236-
'Variable "$input" got invalid value 3 at "input.title"; Expected type "title_Int_exclusiveMax_3". Must be less than 3')
236+
'Variable "$input" got invalid value 3 at "input.title"; Expected type "title_Int_NotNull_exclusiveMax_3". Must be less than 3')
237237
})
238238

239239
it('should throw custom error', async function () {
@@ -292,7 +292,7 @@ describe('@constraint Int in INPUT_FIELD_DEFINITION', function () {
292292

293293
strictEqual(statusCode, 400)
294294
strictEqual(body.errors[0].message,
295-
'Variable "$input" got invalid value 7 at "input.title"; Expected type "title_Int_multipleOf_2". Must be a multiple of 2')
295+
'Variable "$input" got invalid value 7 at "input.title"; Expected type "title_Int_NotNull_multipleOf_2". Must be a multiple of 2')
296296
})
297297

298298
it('should throw custom error', async function () {
@@ -311,6 +311,50 @@ describe('@constraint Int in INPUT_FIELD_DEFINITION', function () {
311311
})
312312
})
313313
})
314+
315+
describe('#notNull', function () {
316+
before(function () {
317+
this.typeDefs = `
318+
type Query {
319+
books: [Book]
320+
}
321+
type Book {
322+
title: String
323+
}
324+
type Mutation {
325+
createBook(input: BookInput): Book
326+
}
327+
input BookInput {
328+
title: Int! @constraint(multipleOf: 2)
329+
}`
330+
331+
this.request = setup(this.typeDefs)
332+
})
333+
334+
it('should fail with null', async function () {
335+
let { body, statusCode } = await this.request
336+
.post('/graphql')
337+
.set('Accept', 'application/json')
338+
.send({ query, variables: { input: { title: null } }
339+
})
340+
341+
strictEqual(statusCode, 400)
342+
strictEqual(body.errors[0].message,
343+
'Variable "$input" got invalid value null at "input.title"; Expected non-nullable type "title_Int_NotNull_multipleOf_2!" not to be null.')
344+
})
345+
346+
it('should fail with undefined', async function () {
347+
let { body, statusCode } = await this.request
348+
.post('/graphql')
349+
.set('Accept', 'application/json')
350+
.send({ query, variables: { input: { title: undefined } }
351+
})
352+
353+
strictEqual(statusCode, 400)
354+
strictEqual(body.errors[0].message,
355+
'Variable "$input" got invalid value {}; Field "title" of required type "title_Int_NotNull_multipleOf_2!" was not provided.')
356+
})
357+
})
314358
})
315359

316360
describe('@constraint Int in FIELD_DEFINITION', function () {

test/string.test.js

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ describe('@constraint String in INPUT_FIELD_DEFINITION', function () {
5252

5353
strictEqual(statusCode, 400)
5454
strictEqual(body.errors[0].message,
55-
'Variable "$input" got invalid value "a💩" at "input.title"; Expected type "title_String_minLength_3". Must be at least 3 characters in length')
55+
'Variable "$input" got invalid value "a💩" at "input.title"; Expected type "title_String_NotNull_minLength_3". Must be at least 3 characters in length')
5656
})
5757

5858
it('should throw custom error', async function () {
@@ -169,7 +169,7 @@ describe('@constraint String in INPUT_FIELD_DEFINITION', function () {
169169

170170
strictEqual(statusCode, 400)
171171
strictEqual(body.errors[0].message,
172-
'Variable "$input" got invalid value "bar💩" at "input.title"; Expected type "title_String_startsWith_". Must start with 💩')
172+
'Variable "$input" got invalid value "bar💩" at "input.title"; Expected type "title_String_NotNull_startsWith_". Must start with 💩')
173173
})
174174

175175
it('should throw custom error', async function () {
@@ -228,7 +228,7 @@ describe('@constraint String in INPUT_FIELD_DEFINITION', function () {
228228

229229
strictEqual(statusCode, 400)
230230
strictEqual(body.errors[0].message,
231-
'Variable "$input" got invalid value "💩bar" at "input.title"; Expected type "title_String_endsWith_". Must end with 💩')
231+
'Variable "$input" got invalid value "💩bar" at "input.title"; Expected type "title_String_NotNull_endsWith_". Must end with 💩')
232232
})
233233

234234
it('should throw custom error', async function () {
@@ -287,7 +287,7 @@ describe('@constraint String in INPUT_FIELD_DEFINITION', function () {
287287

288288
strictEqual(statusCode, 400)
289289
strictEqual(body.errors[0].message,
290-
'Variable "$input" got invalid value "fobar" at "input.title"; Expected type "title_String_contains_". Must contain 💩')
290+
'Variable "$input" got invalid value "fobar" at "input.title"; Expected type "title_String_NotNull_contains_". Must contain 💩')
291291
})
292292

293293
it('should throw custom error', async function () {
@@ -346,7 +346,7 @@ describe('@constraint String in INPUT_FIELD_DEFINITION', function () {
346346

347347
strictEqual(statusCode, 400)
348348
strictEqual(body.errors[0].message,
349-
'Variable "$input" got invalid value "💩foobar" at "input.title"; Expected type "title_String_notContains_foo". Must not contain foo')
349+
'Variable "$input" got invalid value "💩foobar" at "input.title"; Expected type "title_String_NotNull_notContains_foo". Must not contain foo')
350350
})
351351

352352
it('should throw custom error', async function () {
@@ -405,7 +405,7 @@ describe('@constraint String in INPUT_FIELD_DEFINITION', function () {
405405

406406
strictEqual(statusCode, 400)
407407
strictEqual(body.errors[0].message,
408-
'Variable "$input" got invalid value "£££" at "input.title"; Expected type "title_String_pattern_09azAZ". Must match ^[0-9a-zA-Z]*$')
408+
'Variable "$input" got invalid value "£££" at "input.title"; Expected type "title_String_NotNull_pattern_09azAZ". Must match ^[0-9a-zA-Z]*$')
409409
})
410410

411411
it('should throw custom error', async function () {
@@ -467,7 +467,7 @@ describe('@constraint String in INPUT_FIELD_DEFINITION', function () {
467467

468468
strictEqual(statusCode, 400)
469469
strictEqual(body.errors[0].message,
470-
'Variable "$input" got invalid value "£££" at "input.title"; Expected type "title_String_format_byte". Must be in byte format')
470+
'Variable "$input" got invalid value "£££" at "input.title"; Expected type "title_String_NotNull_format_byte". Must be in byte format')
471471
})
472472

473473
it('should throw custom error', async function () {
@@ -528,7 +528,7 @@ describe('@constraint String in INPUT_FIELD_DEFINITION', function () {
528528

529529
strictEqual(statusCode, 400)
530530
strictEqual(body.errors[0].message,
531-
'Variable "$input" got invalid value "a" at "input.title"; Expected type "title_String_format_datetime". Must be a date-time in RFC 3339 format')
531+
'Variable "$input" got invalid value "a" at "input.title"; Expected type "title_String_NotNull_format_datetime". Must be a date-time in RFC 3339 format')
532532
})
533533

534534
it('should throw custom error', async function () {
@@ -589,7 +589,7 @@ describe('@constraint String in INPUT_FIELD_DEFINITION', function () {
589589

590590
strictEqual(statusCode, 400)
591591
strictEqual(body.errors[0].message,
592-
'Variable "$input" got invalid value "a" at "input.title"; Expected type "title_String_format_date". Must be a date in ISO 8601 format')
592+
'Variable "$input" got invalid value "a" at "input.title"; Expected type "title_String_NotNull_format_date". Must be a date in ISO 8601 format')
593593
})
594594

595595
it('should throw custom error', async function () {
@@ -650,7 +650,7 @@ describe('@constraint String in INPUT_FIELD_DEFINITION', function () {
650650

651651
strictEqual(statusCode, 400)
652652
strictEqual(body.errors[0].message,
653-
'Variable "$input" got invalid value "a" at "input.title"; Expected type "title_String_format_email". Must be in email format')
653+
'Variable "$input" got invalid value "a" at "input.title"; Expected type "title_String_NotNull_format_email". Must be in email format')
654654
})
655655

656656
it('should throw custom error', async function () {
@@ -711,7 +711,7 @@ describe('@constraint String in INPUT_FIELD_DEFINITION', function () {
711711

712712
strictEqual(statusCode, 400)
713713
strictEqual(body.errors[0].message,
714-
'Variable "$input" got invalid value "a" at "input.title"; Expected type "title_String_format_ipv4". Must be in IP v4 format')
714+
'Variable "$input" got invalid value "a" at "input.title"; Expected type "title_String_NotNull_format_ipv4". Must be in IP v4 format')
715715
})
716716

717717
it('should throw custom error', async function () {
@@ -772,7 +772,7 @@ describe('@constraint String in INPUT_FIELD_DEFINITION', function () {
772772

773773
strictEqual(statusCode, 400)
774774
strictEqual(body.errors[0].message,
775-
'Variable "$input" got invalid value "a" at "input.title"; Expected type "title_String_format_ipv6". Must be in IP v6 format')
775+
'Variable "$input" got invalid value "a" at "input.title"; Expected type "title_String_NotNull_format_ipv6". Must be in IP v6 format')
776776
})
777777

778778
it('should throw custom error', async function () {
@@ -833,7 +833,7 @@ describe('@constraint String in INPUT_FIELD_DEFINITION', function () {
833833

834834
strictEqual(statusCode, 400)
835835
strictEqual(body.errors[0].message,
836-
'Variable "$input" got invalid value "a" at "input.title"; Expected type "title_String_format_uri". Must be in URI format')
836+
'Variable "$input" got invalid value "a" at "input.title"; Expected type "title_String_NotNull_format_uri". Must be in URI format')
837837
})
838838

839839
it('should throw custom error', async function () {
@@ -894,7 +894,7 @@ describe('@constraint String in INPUT_FIELD_DEFINITION', function () {
894894

895895
strictEqual(statusCode, 400)
896896
strictEqual(body.errors[0].message,
897-
'Variable "$input" got invalid value "a" at "input.title"; Expected type "title_String_format_uuid". Must be in UUID format')
897+
'Variable "$input" got invalid value "a" at "input.title"; Expected type "title_String_NotNull_format_uuid". Must be in UUID format')
898898
})
899899

900900
it('should throw custom error', async function () {
@@ -943,7 +943,7 @@ describe('@constraint String in INPUT_FIELD_DEFINITION', function () {
943943

944944
strictEqual(statusCode, 400)
945945
strictEqual(body.errors[0].message,
946-
'Variable "$input" got invalid value "a" at "input.title"; Expected type "title_String_format_test". Invalid format type test')
946+
'Variable "$input" got invalid value "a" at "input.title"; Expected type "title_String_NotNull_format_test". Invalid format type test')
947947
})
948948

949949
it('should throw custom error', async function () {
@@ -963,6 +963,50 @@ describe('@constraint String in INPUT_FIELD_DEFINITION', function () {
963963
})
964964
})
965965
})
966+
967+
describe('#notNull', function () {
968+
before(function () {
969+
this.typeDefs = `
970+
type Query {
971+
books: [Book]
972+
}
973+
type Book {
974+
title: String
975+
}
976+
type Mutation {
977+
createBook(input: BookInput): Book
978+
}
979+
input BookInput {
980+
title: String! @constraint(minLength: 3)
981+
}`
982+
983+
this.request = setup(this.typeDefs)
984+
})
985+
986+
it('should fail with null', async function () {
987+
const { body, statusCode } = await this.request
988+
.post('/graphql')
989+
.set('Accept', 'application/json')
990+
.send({ query, variables: { input: { title: null } }
991+
})
992+
993+
strictEqual(statusCode, 400)
994+
strictEqual(body.errors[0].message,
995+
'Variable "$input" got invalid value null at "input.title"; Expected non-nullable type "title_String_NotNull_minLength_3!" not to be null.')
996+
})
997+
998+
it('should fail with undefined', async function () {
999+
const { body, statusCode } = await this.request
1000+
.post('/graphql')
1001+
.set('Accept', 'application/json')
1002+
.send({ query, variables: { input: { title: undefined } }
1003+
})
1004+
1005+
strictEqual(statusCode, 400)
1006+
strictEqual(body.errors[0].message,
1007+
'Variable "$input" got invalid value {}; Field "title" of required type "title_String_NotNull_minLength_3!" was not provided.')
1008+
})
1009+
})
9661010
})
9671011

9681012
describe('@constraint String in FIELD_DEFINITION', function () {

0 commit comments

Comments
 (0)