diff --git a/deno_dist/validator/validator.ts b/deno_dist/validator/validator.ts index e2db675f..476a903e 100644 --- a/deno_dist/validator/validator.ts +++ b/deno_dist/validator/validator.ts @@ -4,7 +4,7 @@ import { rule } from './rule.ts' import { sanitizer } from './sanitizer.ts' import type { Schema } from './schema.ts' -type Target = 'query' | 'header' | 'body' | 'json' +type Target = 'query' | 'queries' | 'header' | 'body' | 'json' type Type = JSONPrimitive | JSONObject | JSONArray | File type RuleFunc = (value: Type) => boolean type Rule = { @@ -90,6 +90,7 @@ export class VArray extends VObjectBase { export class Validator { isArray: boolean = false query = (key: string): VString => new VString({ target: 'query', key: key }) + queries = (key: string): VStringArray => new VStringArray({ target: 'queries', key: key }) header = (key: string): VString => new VString({ target: 'header', key: key }) body = (key: string): VString => new VString({ target: 'body', key: key }) json = (key: string) => { @@ -216,6 +217,9 @@ export abstract class VBase { if (this.target === 'query') { value = req.query(this.key) } + if (this.target === 'queries') { + value = req.queries(this.key) + } if (this.target === 'header') { value = req.header(this.key) } @@ -327,6 +331,7 @@ export abstract class VBase { if (this._optional && typeof value === 'undefined') return true if (Array.isArray(value)) { + if (value.length === 0 && !this._optional) return false // Sanitize for (const sanitizer of this.sanitizers) { value = value.map((innerVal: any) => sanitizer(innerVal)) as JSONArray @@ -362,6 +367,9 @@ export abstract class VBase { case 'query': keyText = `the query parameter "${this.key}"` break + case 'queries': + keyText = `the query parameters "${this.key}"` + break case 'header': keyText = `the request header "${this.key}"` break diff --git a/src/middleware/validator/middleware.test.ts b/src/middleware/validator/middleware.test.ts index 3f42167c..5cf12ad0 100644 --- a/src/middleware/validator/middleware.test.ts +++ b/src/middleware/validator/middleware.test.ts @@ -46,6 +46,43 @@ describe('Basic - query', () => { }) }) +describe('Basic - queries', () => { + const app = new Hono() + + app.get( + '/', + validator((v) => ({ + ids: v.queries('id').isNumeric().isRequired(), + tags: v.queries('tag').isOptional(), + })), + (c) => { + return c.text('Valid') + } + ) + + it('Should return 200 response', async () => { + const res = await app.request('http://localhost/?id=123&id=456') + expect(res.status).toBe(200) + }) + + it('Should return 400 response - id is not set', async () => { + const res = await app.request('http://localhost/') + expect(res.status).toBe(400) + const messages = [ + 'Invalid Value []: the query parameters "id" is invalid - isNumeric', + 'Invalid Value []: the query parameters "id" is invalid - isRequired', + ] + expect(await res.text()).toBe(messages.join('\n')) + }) + + it('Should return 400 response - is is not numeric', async () => { + const res = await app.request('http://localhost/?id=one') + expect(res.status).toBe(400) + const messages = ['Invalid Value ["one"]: the query parameters "id" is invalid - isNumeric'] + expect(await res.text()).toBe(messages.join('\n')) + }) +}) + describe('Basic - body', () => { const app = new Hono() diff --git a/src/validator/validator.test.ts b/src/validator/validator.test.ts index 210184ad..54156315 100644 --- a/src/validator/validator.test.ts +++ b/src/validator/validator.test.ts @@ -43,6 +43,51 @@ describe('Basic - query', () => { }) }) +describe('Basic - queries', () => { + const v = new Validator() + + const req = new Request('http://localhost/?tag=foo&tag=bar&id=123&id=456') + + it('Should be valid - tag', async () => { + const validator = v.queries('tag').isRequired() + const results = await validator.validate(req) + expect(results[0].isValid).toBe(true) + expect(results[0].message).toBeUndefined() + expect(results[0].value).toEqual(['foo', 'bar']) + expect(results[1].isValid).toBe(true) + expect(results[1].message).toBeUndefined() + }) + + it('Should be invalid - ids', async () => { + const validator = v.queries('ids').isRequired() + const results = await validator.validate(req) + console.log(results) + expect(results[0].isValid).toBe(true) + expect(results[1].isValid).toBe(false) + expect(results[1].message).toBe( + 'Invalid Value []: the query parameters "ids" is invalid - isRequired' + ) + }) + + it('Should be valid - id', async () => { + const validator = v.queries('id').isNumeric() + const results = await validator.validate(req) + expect(results[0].isValid).toBe(true) + expect(results[0].message).toBeUndefined() + expect(results[1].isValid).toBe(true) + expect(results[1].message).toBeUndefined() + }) + + it('Should be valid - comment is options', async () => { + const validator = v.queries('comment').isOptional() + const results = await validator.validate(req) + expect(results[0].isValid).toBe(true) + expect(results[0].message).toBeUndefined() + expect(results[1].isValid).toBe(true) + expect(results[1].message).toBeUndefined() + }) +}) + describe('Basic - header', () => { const v = new Validator() diff --git a/src/validator/validator.ts b/src/validator/validator.ts index 4de13b69..603a627d 100644 --- a/src/validator/validator.ts +++ b/src/validator/validator.ts @@ -4,7 +4,7 @@ import { rule } from './rule' import { sanitizer } from './sanitizer' import type { Schema } from './schema' -type Target = 'query' | 'header' | 'body' | 'json' +type Target = 'query' | 'queries' | 'header' | 'body' | 'json' type Type = JSONPrimitive | JSONObject | JSONArray | File type RuleFunc = (value: Type) => boolean type Rule = { @@ -90,6 +90,7 @@ export class VArray extends VObjectBase { export class Validator { isArray: boolean = false query = (key: string): VString => new VString({ target: 'query', key: key }) + queries = (key: string): VStringArray => new VStringArray({ target: 'queries', key: key }) header = (key: string): VString => new VString({ target: 'header', key: key }) body = (key: string): VString => new VString({ target: 'body', key: key }) json = (key: string) => { @@ -216,6 +217,9 @@ export abstract class VBase { if (this.target === 'query') { value = req.query(this.key) } + if (this.target === 'queries') { + value = req.queries(this.key) + } if (this.target === 'header') { value = req.header(this.key) } @@ -327,6 +331,7 @@ export abstract class VBase { if (this._optional && typeof value === 'undefined') return true if (Array.isArray(value)) { + if (value.length === 0 && !this._optional) return false // Sanitize for (const sanitizer of this.sanitizers) { value = value.map((innerVal: any) => sanitizer(innerVal)) as JSONArray @@ -362,6 +367,9 @@ export abstract class VBase { case 'query': keyText = `the query parameter "${this.key}"` break + case 'queries': + keyText = `the query parameters "${this.key}"` + break case 'header': keyText = `the request header "${this.key}"` break