Skip to content

Commit b508d3e

Browse files
authored
fix: array/list size validation for list of other scalar types (#105)
1 parent e6ef242 commit b508d3e

File tree

2 files changed

+170
-4
lines changed

2 files changed

+170
-4
lines changed

lib/query-validation-visitor.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ function validateArrayTypeValue (context, valueTypeDef, typeDefWithDirective, va
186186
// Validate array/list size
187187
const directiveArgumentMap = getDirectiveValues(constraintDirectiveTypeDefsObj, typeDefWithDirective.astNode)
188188

189+
let hasNonListValidation = false
190+
189191
if (directiveArgumentMap) {
190192
let errMessageBase
191193

@@ -205,6 +207,13 @@ function validateArrayTypeValue (context, valueTypeDef, typeDefWithDirective, va
205207
errMessageBase + `must be no more than ${directiveArgumentMap.maxItems} in length`,
206208
[{ arg: 'maxItems', value: directiveArgumentMap.maxItems }]))
207209
}
210+
211+
for (const key in directiveArgumentMap) {
212+
if (key !== 'maxItems' && key !== 'minItems') {
213+
hasNonListValidation = true
214+
break
215+
}
216+
}
208217
}
209218

210219
// Validate array content
@@ -214,7 +223,7 @@ function validateArrayTypeValue (context, valueTypeDef, typeDefWithDirective, va
214223

215224
if (isInputObjectType(valueTypeDefArray)) {
216225
validateInputTypeValue(context, valueTypeDefArray, argName, variableName, element, currentField, iFieldNameFullIndexed)
217-
} else {
226+
} else if (hasNonListValidation) {
218227
const atMessage = ` at "${iFieldNameFullIndexed}"`
219228

220229
validateScalarTypeValue(context, currentField, typeDefWithDirective, valueTypeDef, element, variableName, argName, iFieldNameFullIndexed, atMessage)

test/array-size.test.js

Lines changed: 160 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const { valueByImplType, isStatusCodeError } = require('./testutils')
33

44
module.exports.test = function (setup, implType) {
55
describe('Array size', function () {
6-
describe('Input object', function () {
6+
describe('Inside Input Object', function () {
77
const query = `mutation createBook($input: BookInput) {
88
createBook(input: $input) {
99
title
@@ -126,7 +126,7 @@ module.exports.test = function (setup, implType) {
126126
})
127127
})
128128

129-
describe('Input argument', function () {
129+
describe('Scalar argument', function () {
130130
describe('#minItems', function () {
131131
before(async function () {
132132
this.typeDefs = `
@@ -209,7 +209,7 @@ module.exports.test = function (setup, implType) {
209209
title: String
210210
}
211211
type Mutation {
212-
createBook(input: [Int] @constraint(maxItems: 2)): Book
212+
createBook(input: [Int] @constraint(maxItems: 2, max: 100)): Book
213213
}`
214214

215215
this.request = await setup(this.typeDefs)
@@ -260,13 +260,170 @@ module.exports.test = function (setup, implType) {
260260
.send({ query })
261261

262262
isStatusCodeError(statusCode, implType)
263+
strictEqual(body.errors.length, 1)
263264
strictEqual(
264265
body.errors[0].message,
265266
'Argument "input" of "createBook"' +
266267
valueByImplType(implType, '; Expected type "title_List_Int_NotNull_max_3"') +
267268
' must be no more than 2 in length'
268269
)
269270
})
271+
272+
it('should fail - another scalar validation', async function () {
273+
const query = `mutation createBook {
274+
createBook(input: [2, 101]) {
275+
title
276+
}
277+
}`
278+
279+
const { body, statusCode } = await this.request
280+
.post('/graphql')
281+
.set('Accept', 'application/json')
282+
.send({ query })
283+
284+
isStatusCodeError(statusCode, implType)
285+
strictEqual(body.errors.length, 1)
286+
strictEqual(
287+
body.errors[0].message,
288+
'Argument "input" of "createBook" got invalid value 101 at "[1]"' +
289+
valueByImplType(implType, '; Expected type "title_List_Int_NotNull_max_3"') +
290+
'. Must be no greater than 100'
291+
)
292+
})
293+
})
294+
})
295+
296+
describe('Scalar argument of ID type', function () {
297+
describe('#minItems', function () {
298+
before(async function () {
299+
this.typeDefs = `
300+
type Query {
301+
books: [Book]
302+
}
303+
type Book {
304+
title: String
305+
}
306+
type Mutation {
307+
createBook(input: [ID]! @constraint(minItems: 3)): Book
308+
}`
309+
310+
this.request = await setup(this.typeDefs)
311+
})
312+
313+
it('should pass', async function () {
314+
const query = `mutation createBook {
315+
createBook(input: ["2","3","4"]) {
316+
title
317+
}
318+
}`
319+
const { body, statusCode } = await this.request
320+
.post('/graphql')
321+
.set('Accept', 'application/json')
322+
.send({ query })
323+
324+
strictEqual(statusCode, 200)
325+
deepStrictEqual(body, { data: { createBook: null } })
326+
})
327+
328+
it('should fail', async function () {
329+
const query = `mutation createBook {
330+
createBook(input: ["2","3"]) {
331+
title
332+
}
333+
}`
334+
const { body, statusCode } = await this.request
335+
.post('/graphql')
336+
.set('Accept', 'application/json')
337+
.send({ query })
338+
339+
isStatusCodeError(statusCode, implType)
340+
strictEqual(body.errors.length, 1)
341+
strictEqual(
342+
body.errors[0].message,
343+
'Argument "input" of "createBook"' +
344+
valueByImplType(implType, '; Expected type "title_List_ListNotNull_Int_NotNull_min_3"') +
345+
' must be at least 3 in length'
346+
)
347+
})
348+
})
349+
})
350+
351+
describe('InputObject argument', function () {
352+
describe('#minItems', function () {
353+
before(async function () {
354+
this.typeDefs = `
355+
type Query {
356+
books: [Book]
357+
}
358+
type Book {
359+
title: String
360+
}
361+
type Mutation {
362+
createBook(input: [BookInput]! @constraint(minItems: 3)): Book
363+
}
364+
input BookInput {
365+
title: String @constraint(maxLength: 2)
366+
}
367+
`
368+
this.request = await setup(this.typeDefs)
369+
})
370+
371+
it('should pass', async function () {
372+
const query = `mutation createBook {
373+
createBook(input: [{title:"aa"},{title:"ab"},{title:"ba"}]) {
374+
title
375+
}
376+
}`
377+
const { body, statusCode } = await this.request
378+
.post('/graphql')
379+
.set('Accept', 'application/json')
380+
.send({ query })
381+
382+
strictEqual(statusCode, 200)
383+
deepStrictEqual(body, { data: { createBook: null } })
384+
})
385+
386+
it('should fail', async function () {
387+
const query = `mutation createBook {
388+
createBook(input: [{title:"aa"},{title:"ab"}]) {
389+
title
390+
}
391+
}`
392+
const { body, statusCode } = await this.request
393+
.post('/graphql')
394+
.set('Accept', 'application/json')
395+
.send({ query })
396+
397+
isStatusCodeError(statusCode, implType)
398+
strictEqual(body.errors.length, 1)
399+
strictEqual(
400+
body.errors[0].message,
401+
'Argument "input" of "createBook"' +
402+
valueByImplType(implType, '; Expected type "title_List_ListNotNull_Int_NotNull_min_3"') +
403+
' must be at least 3 in length'
404+
)
405+
})
406+
407+
it('should fail - another validation in input object', async function () {
408+
const query = `mutation createBook {
409+
createBook(input: [{title:"aa"},{title:"abc"},{title:"as"}]) {
410+
title
411+
}
412+
}`
413+
const { body, statusCode } = await this.request
414+
.post('/graphql')
415+
.set('Accept', 'application/json')
416+
.send({ query })
417+
418+
isStatusCodeError(statusCode, implType)
419+
strictEqual(body.errors.length, 1)
420+
strictEqual(
421+
body.errors[0].message,
422+
'Argument "input" of "createBook" got invalid value "abc" at "[1].title"' +
423+
valueByImplType(implType, '; Expected type "title_List_ListNotNull_Int_NotNull_min_3"') +
424+
'. Must be no more than 2 characters in length'
425+
)
426+
})
270427
})
271428
})
272429
})

0 commit comments

Comments
 (0)