0
0
mirror of https://github.com/honojs/hono.git synced 2024-11-30 01:56:18 +01:00
hono/deno_dist/middleware/validator/middleware.ts

215 lines
5.9 KiB
TypeScript

import type { Context } from '../../context.ts'
import type { MiddlewareHandler } from '../../hono.ts'
import { JSONPathCopy } from '../../utils/json.ts'
type Param =
| string
| number
| string[]
| number[]
| Record<string, string | number>
| ValidatorMessage
type Rule = Function | [Function, ...Param[]]
type Rules = Rule | Rule[]
type Done = (resultSet: ResultSet, context: Context) => Response | void
type RuleSet = {
body: Record<string, Rules>
json: Record<string, Rules>
header: Record<string, Rules>
query: Record<string, Rules>
done: Done
removeAdditional: boolean
}
type ResultSet = {
hasError: boolean
messages: string[]
}
type Result = {
rule: Function
params: Param[]
message?: string
}
export class ValidatorMessage {
value: string
constructor(value: string) {
this.value = value
}
getMessage(): string {
return this.value
}
}
const message = (value: string): ValidatorMessage => {
return new ValidatorMessage(value)
}
export const validatorMiddleware = <Validator>(validator: Validator) => {
return (
validatorFunction: (
validator: Validator,
message: (value: string) => ValidatorMessage,
context: Context
) => Partial<RuleSet>
): MiddlewareHandler => {
return async (c, next) => {
const validations = validatorFunction(validator, message, c)
const result: ResultSet = {
hasError: false,
messages: [],
}
const v = validations
v.removeAdditional = v.removeAdditional === undefined ? true : v.removeAdditional // default value is `true`
function validate(rules: Rules, value: string, messageFunc: (ruleName: string) => string) {
value ||= ''
let count = 0
const results: Result[] = []
const check = (rules: Rules) => {
if (!Array.isArray(rules)) {
if (rules instanceof ValidatorMessage) {
if (results[count - 1]) {
results[count - 1].message = rules.getMessage()
}
} else if (typeof rules === 'function') {
results[count] = {
rule: rules,
params: [],
}
count++
} else {
results[count - 1].params.push(rules)
}
} else {
if (typeof rules[0] === ('string' || 'number')) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
results[count - 1].params.push(rules)
} else {
for (const rule of rules) {
check(rule as Rules)
}
}
}
}
check(rules)
let invalid = false
results.map((r) => {
const ok = r.rule(value, ...r.params)
if (!invalid && ok === false) {
invalid = true
if (r.message) {
result.messages.push(r.message)
} else {
result.messages.push(messageFunc(r.rule.name))
}
}
if (typeof ok !== 'boolean') {
// ok is sanitized string
value = ok
}
})
if (invalid) {
result.hasError = true
return
}
}
if (v.query) {
const query = v.query
const realQueries = c.req.query()
const validatedQueries: Record<string, string> = {}
Object.keys(query).map((key) => {
validatedQueries[key] = realQueries[key] || ''
const message = (name: string) =>
`Invalid Value: the query parameter "${key}" is invalid - ${name}`
validate(query[key], validatedQueries[key], message)
})
if (!result.hasError && v.removeAdditional) {
c.req.queryData = validatedQueries
}
}
if (v.header) {
const header = v.header
const realHeaders: Record<string, string> = {}
for (const key of c.req.headers.keys()) {
realHeaders[key] = c.req.headers.get(key) || ''
}
const validatedHeaders: Record<string, string> = {}
Object.keys(header).map((key) => {
validatedHeaders[key] = realHeaders[key] || ''
const message = (name: string) =>
`Invalid Value: the request header "${key}" is invalid - ${name}`
validate(header[key], validatedHeaders[key], message)
})
if (!result.hasError && v.removeAdditional) {
c.req.headerData = validatedHeaders
}
}
if (v.body) {
const field = v.body
const parsedBody = (await c.req.parseBody()) as Record<string, string>
const kv = { ...parsedBody }
const validatedKv: Record<string, string> = {}
Object.keys(field).map(async (key) => {
validatedKv[key] = kv[key] || ''
const message = (name: string) =>
`Invalid Value: the request body "${key}" is invalid - ${name}`
validate(field[key], validatedKv[key], message)
})
if (!result.hasError && v.removeAdditional) {
c.req.bodyData = validatedKv
}
}
if (v.json) {
const field = v.json
let json: object
try {
json = await c.req.json()
} catch {
json = {}
}
const validatedJson: object = new (json as any).constructor()
Object.keys(field).map(async (key) => {
const value = JSONPathCopy(json, validatedJson, key)
const message = (name: string) =>
`Invalid Value: the JSON body "${key}" is invalid - ${name}`
validate(field[key], value, message)
})
if (!result.hasError && v.removeAdditional) {
c.req.jsonData = validatedJson
}
}
if (v.done) {
const res = v.done(result, c)
if (res) {
return res
}
}
if (result.hasError) {
return c.text(result.messages.join('\n'), 400)
}
await next()
}
}
}