Files
meiseayoung 4e2c6a90c3 feat: add 7 built-in middleware components inspired by Hono.js
New Middleware:
- CORS: Cross-Origin Resource Sharing with full configuration support
- Cookie Helper: Cookie management with signed cookie support (HMAC-SHA256)
- JWT: JSON Web Token authentication (HS256/384/512)
- Bearer Auth: Simple Bearer token authentication
- Compression: Gzip and deflate response compression
- Rate Limiting: Request rate limiting with customizable stores
- Validator: Schema-based request validation

Features:
- Context store for middleware data sharing (get/set/get_client_ip)
- Middleware export module with utility functions
- Comprehensive property-based tests for all middleware
- Example file demonstrating all middleware usage

Files Added:
- cors.v, cookie.v, jwt.v, bearer_auth.v, compress.v, rate_limit.v, validator.v
- middleware.v (unified exports)
- examples/middleware_demo.v
- tests/test_*.v (9 new test files)

Documentation:
- Updated README.md with detailed middleware usage guide
- Added Schema Builder API documentation
2026-01-04 11:11:15 +08:00

975 lines
21 KiB
V

module hono
import net.http
import x.json2
import regex
// ValidationTarget 枚举 - 验证目标类型
pub enum ValidationTarget {
json // JSON body
query // Query parameters
param // Path parameters
header // Request headers
form // Form data
}
// Schema 联合类型 - 支持多种 Schema 类型
pub type Schema = StringSchema | IntSchema | FloatSchema | BoolSchema | ArraySchema | ObjectSchema
// StringSchema 结构体 - 字符串验证规则
pub struct StringSchema {
pub:
required bool
min_length int
max_length int
pattern string
enum_values []string
default_val string
}
// IntSchema 结构体 - 整数验证规则
pub struct IntSchema {
pub:
required bool
min ?int
max ?int
enum_values []int
default_val int
}
// FloatSchema 结构体 - 浮点数验证规则
pub struct FloatSchema {
pub:
required bool
min ?f64
max ?f64
default_val f64
}
// BoolSchema 结构体 - 布尔验证规则
pub struct BoolSchema {
pub:
required bool
default_val bool
}
// ArraySchema 结构体 - 数组验证规则
pub struct ArraySchema {
pub:
required bool
min_items int
max_items int
items &Schema = unsafe { nil }
}
// ObjectSchema 结构体 - 对象验证规则
pub struct ObjectSchema {
pub:
required bool
properties map[string]Schema
}
// ValidationResult 结构体 - 验证结果
pub struct ValidationResult {
pub:
success bool
errors []ValidationError
data map[string]json2.Any
}
// ValidationError 结构体 - 验证错误
pub struct ValidationError {
pub:
field string
message string
code string
}
// ValidatorOptions 结构体 - 验证器配置选项
pub struct ValidatorOptions {
pub:
on_error ?fn ([]ValidationError, mut Context) http.Response
}
// ============================================================================
// Schema Builder API - 工厂函数
// ============================================================================
// v_string - 创建字符串 Schema
pub fn v_string() StringSchema {
return StringSchema{}
}
// v_int - 创建整数 Schema
pub fn v_int() IntSchema {
return IntSchema{}
}
// v_float - 创建浮点数 Schema
pub fn v_float() FloatSchema {
return FloatSchema{}
}
// v_bool - 创建布尔 Schema
pub fn v_bool() BoolSchema {
return BoolSchema{}
}
// v_array - 创建数组 Schema
pub fn v_array(items Schema) ArraySchema {
// 直接存储 Schema 的副本
return ArraySchema{
items: &items
}
}
// v_object - 创建对象 Schema
pub fn v_object(properties map[string]Schema) ObjectSchema {
return ObjectSchema{
properties: properties
}
}
// ============================================================================
// StringSchema 链式方法
// ============================================================================
// required - 设置为必需字段
pub fn (s StringSchema) required() StringSchema {
return StringSchema{
...s
required: true
}
}
// min - 设置最小长度
pub fn (s StringSchema) min(len int) StringSchema {
return StringSchema{
...s
min_length: len
}
}
// max - 设置最大长度
pub fn (s StringSchema) max(len int) StringSchema {
return StringSchema{
...s
max_length: len
}
}
// pattern - 设置正则表达式模式
pub fn (s StringSchema) pattern(regex_pattern string) StringSchema {
return StringSchema{
...s
pattern: regex_pattern
}
}
// enum_of - 设置枚举值
pub fn (s StringSchema) enum_of(values []string) StringSchema {
return StringSchema{
...s
enum_values: values
}
}
// default_value - 设置默认值
pub fn (s StringSchema) default_value(val string) StringSchema {
return StringSchema{
...s
default_val: val
}
}
// ============================================================================
// IntSchema 链式方法
// ============================================================================
// required - 设置为必需字段
pub fn (s IntSchema) required() IntSchema {
return IntSchema{
...s
required: true
}
}
// min - 设置最小值
pub fn (s IntSchema) min(val int) IntSchema {
return IntSchema{
...s
min: val
}
}
// max - 设置最大值
pub fn (s IntSchema) max(val int) IntSchema {
return IntSchema{
...s
max: val
}
}
// enum_of - 设置枚举值
pub fn (s IntSchema) enum_of(values []int) IntSchema {
return IntSchema{
...s
enum_values: values
}
}
// default_value - 设置默认值
pub fn (s IntSchema) default_value(val int) IntSchema {
return IntSchema{
...s
default_val: val
}
}
// ============================================================================
// FloatSchema 链式方法
// ============================================================================
// required - 设置为必需字段
pub fn (s FloatSchema) required() FloatSchema {
return FloatSchema{
...s
required: true
}
}
// min - 设置最小值
pub fn (s FloatSchema) min(val f64) FloatSchema {
return FloatSchema{
...s
min: val
}
}
// max - 设置最大值
pub fn (s FloatSchema) max(val f64) FloatSchema {
return FloatSchema{
...s
max: val
}
}
// default_value - 设置默认值
pub fn (s FloatSchema) default_value(val f64) FloatSchema {
return FloatSchema{
...s
default_val: val
}
}
// ============================================================================
// BoolSchema 链式方法
// ============================================================================
// required - 设置为必需字段
pub fn (s BoolSchema) required() BoolSchema {
return BoolSchema{
...s
required: true
}
}
// default_value - 设置默认值
pub fn (s BoolSchema) default_value(val bool) BoolSchema {
return BoolSchema{
...s
default_val: val
}
}
// ============================================================================
// ArraySchema 链式方法
// ============================================================================
// required - 设置为必需字段
pub fn (s ArraySchema) required() ArraySchema {
return ArraySchema{
...s
required: true
}
}
// min_items_count - 设置最小元素数量
pub fn (s ArraySchema) min_items_count(count int) ArraySchema {
return ArraySchema{
...s
min_items: count
}
}
// max_items_count - 设置最大元素数量
pub fn (s ArraySchema) max_items_count(count int) ArraySchema {
return ArraySchema{
...s
max_items: count
}
}
// ============================================================================
// ObjectSchema 链式方法
// ============================================================================
// required - 设置为必需字段
pub fn (s ObjectSchema) required() ObjectSchema {
return ObjectSchema{
...s
required: true
}
}
// ============================================================================
// 验证逻辑实现
// ============================================================================
// validate_value - 验证单个值
fn validate_value(field_name string, value json2.Any, schema Schema) []ValidationError {
mut errors := []ValidationError{}
match schema {
StringSchema {
errors << validate_string(field_name, value, schema)
}
IntSchema {
errors << validate_int(field_name, value, schema)
}
FloatSchema {
errors << validate_float(field_name, value, schema)
}
BoolSchema {
errors << validate_bool(field_name, value, schema)
}
ArraySchema {
errors << validate_array(field_name, value, schema)
}
ObjectSchema {
errors << validate_object(field_name, value, schema)
}
}
return errors
}
// validate_string - 验证字符串值
fn validate_string(field_name string, value json2.Any, schema StringSchema) []ValidationError {
mut errors := []ValidationError{}
// 检查值是否为 null
if value is json2.Null {
if schema.required {
errors << ValidationError{
field: field_name
message: 'Field is required'
code: 'required'
}
}
return errors
}
// 获取字符串值
str_val := value.str()
// 检查空字符串
if str_val.len == 0 && schema.required {
errors << ValidationError{
field: field_name
message: 'Field is required'
code: 'required'
}
return errors
}
// 检查最小长度
if schema.min_length > 0 && str_val.len < schema.min_length {
errors << ValidationError{
field: field_name
message: 'String length must be at least ${schema.min_length}'
code: 'min_length'
}
}
// 检查最大长度
if schema.max_length > 0 && str_val.len > schema.max_length {
errors << ValidationError{
field: field_name
message: 'String length must be at most ${schema.max_length}'
code: 'max_length'
}
}
// 检查正则表达式模式
if schema.pattern.len > 0 {
mut re := regex.regex_opt(schema.pattern) or {
errors << ValidationError{
field: field_name
message: 'Invalid pattern: ${schema.pattern}'
code: 'invalid_pattern'
}
return errors
}
if !re.matches_string(str_val) {
errors << ValidationError{
field: field_name
message: 'Value does not match pattern: ${schema.pattern}'
code: 'pattern'
}
}
}
// 检查枚举值
if schema.enum_values.len > 0 {
if str_val !in schema.enum_values {
errors << ValidationError{
field: field_name
message: 'Value must be one of: ${schema.enum_values.join(", ")}'
code: 'enum'
}
}
}
return errors
}
// validate_int - 验证整数值
fn validate_int(field_name string, value json2.Any, schema IntSchema) []ValidationError {
mut errors := []ValidationError{}
// 检查值是否为 null
if value is json2.Null {
if schema.required {
errors << ValidationError{
field: field_name
message: 'Field is required'
code: 'required'
}
}
return errors
}
// 尝试转换为整数
int_val := value.int()
// 检查最小值
if min := schema.min {
if int_val < min {
errors << ValidationError{
field: field_name
message: 'Value must be at least ${min}'
code: 'min'
}
}
}
// 检查最大值
if max := schema.max {
if int_val > max {
errors << ValidationError{
field: field_name
message: 'Value must be at most ${max}'
code: 'max'
}
}
}
// 检查枚举值
if schema.enum_values.len > 0 {
if int_val !in schema.enum_values {
errors << ValidationError{
field: field_name
message: 'Value must be one of: ${schema.enum_values}'
code: 'enum'
}
}
}
return errors
}
// validate_float - 验证浮点数值
fn validate_float(field_name string, value json2.Any, schema FloatSchema) []ValidationError {
mut errors := []ValidationError{}
// 检查值是否为 null
if value is json2.Null {
if schema.required {
errors << ValidationError{
field: field_name
message: 'Field is required'
code: 'required'
}
}
return errors
}
// 尝试转换为浮点数
float_val := value.f64()
// 检查最小值
if min := schema.min {
if float_val < min {
errors << ValidationError{
field: field_name
message: 'Value must be at least ${min}'
code: 'min'
}
}
}
// 检查最大值
if max := schema.max {
if float_val > max {
errors << ValidationError{
field: field_name
message: 'Value must be at most ${max}'
code: 'max'
}
}
}
return errors
}
// validate_bool - 验证布尔值
fn validate_bool(field_name string, value json2.Any, schema BoolSchema) []ValidationError {
mut errors := []ValidationError{}
// 检查值是否为 null
if value is json2.Null {
if schema.required {
errors << ValidationError{
field: field_name
message: 'Field is required'
code: 'required'
}
}
return errors
}
// 验证是否为布尔类型
match value {
bool {
// 有效的布尔值
}
else {
// 尝试从字符串转换
str_val := value.str().to_lower()
if str_val !in ['true', 'false', '1', '0'] {
errors << ValidationError{
field: field_name
message: 'Value must be a boolean'
code: 'type'
}
}
}
}
return errors
}
// validate_array - 验证数组值
fn validate_array(field_name string, value json2.Any, schema ArraySchema) []ValidationError {
mut errors := []ValidationError{}
// 检查值是否为 null
if value is json2.Null {
if schema.required {
errors << ValidationError{
field: field_name
message: 'Field is required'
code: 'required'
}
}
return errors
}
// 获取数组
arr := value.arr()
// 检查最小元素数量
if schema.min_items > 0 && arr.len < schema.min_items {
errors << ValidationError{
field: field_name
message: 'Array must have at least ${schema.min_items} items'
code: 'min_items'
}
}
// 检查最大元素数量
if schema.max_items > 0 && arr.len > schema.max_items {
errors << ValidationError{
field: field_name
message: 'Array must have at most ${schema.max_items} items'
code: 'max_items'
}
}
// 验证数组元素
if schema.items != unsafe { nil } {
for i, item in arr {
item_errors := validate_value('${field_name}[${i}]', item, *schema.items)
errors << item_errors
}
}
return errors
}
// validate_object - 验证对象值(递归验证嵌套属性)
fn validate_object(field_name string, value json2.Any, schema ObjectSchema) []ValidationError {
mut errors := []ValidationError{}
// 检查值是否为 null
if value is json2.Null {
if schema.required {
errors << ValidationError{
field: field_name
message: 'Field is required'
code: 'required'
}
}
return errors
}
// 获取对象
obj := value.as_map()
// 验证每个属性
for prop_name, prop_schema in schema.properties {
full_field_name := if field_name.len > 0 { '${field_name}.${prop_name}' } else { prop_name }
if prop_name in obj {
prop_value := obj[prop_name] or { json2.Null{} }
prop_errors := validate_value(full_field_name, prop_value, prop_schema)
errors << prop_errors
} else {
// 字段不存在,检查是否必需
is_required := match prop_schema {
StringSchema { prop_schema.required }
IntSchema { prop_schema.required }
FloatSchema { prop_schema.required }
BoolSchema { prop_schema.required }
ArraySchema { prop_schema.required }
ObjectSchema { prop_schema.required }
}
if is_required {
errors << ValidationError{
field: full_field_name
message: 'Field is required'
code: 'required'
}
}
}
}
return errors
}
// validate_schema - 验证数据是否符合 ObjectSchema
pub fn validate_schema(data map[string]json2.Any, schema ObjectSchema) ValidationResult {
mut errors := []ValidationError{}
// 验证每个属性
for prop_name, prop_schema in schema.properties {
if prop_name in data {
prop_value := data[prop_name] or { json2.Null{} }
prop_errors := validate_value(prop_name, prop_value, prop_schema)
errors << prop_errors
} else {
// 字段不存在,检查是否必需
is_required := match prop_schema {
StringSchema { prop_schema.required }
IntSchema { prop_schema.required }
FloatSchema { prop_schema.required }
BoolSchema { prop_schema.required }
ArraySchema { prop_schema.required }
ObjectSchema { prop_schema.required }
}
if is_required {
errors << ValidationError{
field: prop_name
message: 'Field is required'
code: 'required'
}
}
}
}
return ValidationResult{
success: errors.len == 0
errors: errors
data: data
}
}
// ============================================================================
// 验证中间件实现
// ============================================================================
// validator - 验证中间件工厂函数
// 返回一个 ContextMiddleware,用于验证请求数据
pub fn validator(target ValidationTarget, schema ObjectSchema, options ...ValidatorOptions) ContextMiddleware {
opts := if options.len > 0 { options[0] } else { ValidatorOptions{} }
return fn [target, schema, opts] (mut c Context, next fn (mut Context) http.Response) http.Response {
// 根据目标类型获取数据
data := get_validation_data(c, target) or {
// 解析错误
errors := [ValidationError{
field: ''
message: err.msg()
code: 'parse_error'
}]
// 使用自定义错误处理器或默认响应
if on_error := opts.on_error {
return on_error(errors, mut c)
}
c.status(400)
return c.json(build_error_response(errors))
}
// 验证数据
result := validate_schema(data, schema)
if !result.success {
// 使用自定义错误处理器或默认响应
if on_error := opts.on_error {
return on_error(result.errors, mut c)
}
c.status(400)
return c.json(build_error_response(result.errors))
}
// 将验证后的数据存储到 Context
store_validated_data(mut c, result.data)
// 继续处理请求
return next(mut c)
}
}
// get_validation_data - 根据目标类型获取要验证的数据
fn get_validation_data(c Context, target ValidationTarget) !map[string]json2.Any {
match target {
.json {
return parse_json_body(c)
}
.query {
return parse_query_params(c)
}
.param {
return parse_path_params(c)
}
.header {
return parse_headers(c)
}
.form {
return parse_form_data(c)
}
}
}
// parse_json_body - 解析 JSON 请求体
fn parse_json_body(c Context) !map[string]json2.Any {
if c.body.len == 0 {
return map[string]json2.Any{}
}
raw := json2.decode[json2.Any](c.body) or {
return error('Invalid JSON: ${err}')
}
return raw.as_map()
}
// parse_query_params - 解析查询参数
fn parse_query_params(c Context) !map[string]json2.Any {
mut data := map[string]json2.Any{}
for key, value in c.query {
data[key] = json2.Any(value)
}
return data
}
// parse_path_params - 解析路径参数
fn parse_path_params(c Context) !map[string]json2.Any {
mut data := map[string]json2.Any{}
for key, value in c.params {
data[key] = json2.Any(value)
}
return data
}
// parse_headers - 解析请求头
fn parse_headers(c Context) !map[string]json2.Any {
mut data := map[string]json2.Any{}
// 获取常用请求头
common_headers := ['Content-Type', 'Accept', 'Authorization', 'X-Request-ID',
'X-Forwarded-For', 'User-Agent', 'Host', 'Origin', 'Referer']
for header_name in common_headers {
if header_value := c.req.header.get_custom(header_name) {
data[header_name] = json2.Any(header_value)
}
}
return data
}
// parse_form_data - 解析表单数据
fn parse_form_data(c Context) !map[string]json2.Any {
mut data := map[string]json2.Any{}
if c.body.len == 0 {
return data
}
// 解析 application/x-www-form-urlencoded 格式
pairs := c.body.split('&')
for pair in pairs {
eq_pos := pair.index('=') or { continue }
if eq_pos == 0 {
continue
}
key := pair[..eq_pos]
value := if eq_pos + 1 < pair.len { pair[eq_pos + 1..] } else { '' }
// URL 解码
decoded_key := url_decode(key)
decoded_value := url_decode(value)
data[decoded_key] = json2.Any(decoded_value)
}
return data
}
// url_decode - URL 解码
fn url_decode(s string) string {
mut result := []u8{}
mut i := 0
for i < s.len {
if s[i] == `%` && i + 2 < s.len {
// 尝试解码十六进制
hex_str := s[i + 1..i + 3]
if hex_val := hex_to_byte(hex_str) {
result << hex_val
i += 3
continue
}
} else if s[i] == `+` {
result << ` `
i++
continue
}
result << s[i]
i++
}
return result.bytestr()
}
// hex_to_byte - 将两位十六进制字符串转换为字节
fn hex_to_byte(hex string) ?u8 {
if hex.len != 2 {
return none
}
high := hex_char_to_val(hex[0]) or { return none }
low := hex_char_to_val(hex[1]) or { return none }
return u8(high * 16 + low)
}
// hex_char_to_val - 将十六进制字符转换为数值
fn hex_char_to_val(c u8) ?int {
if c >= `0` && c <= `9` {
return int(c - `0`)
}
if c >= `a` && c <= `f` {
return int(c - `a` + 10)
}
if c >= `A` && c <= `F` {
return int(c - `A` + 10)
}
return none
}
// store_validated_data - 将验证后的数据存储到 Context
fn store_validated_data(mut c Context, data map[string]json2.Any) {
// 将数据序列化为 JSON 字符串存储
mut json_parts := []string{}
for key, value in data {
json_parts << '"${key}":${value.json_str()}'
}
json_str := '{${json_parts.join(",")}}'
c.set('validated_data', json_str)
// 同时存储各个字段的字符串值(便于快速访问)
for key, value in data {
c.set('validated_${key}', value.str())
}
}
// build_error_response - 构建错误响应 JSON
fn build_error_response(errors []ValidationError) string {
mut error_parts := []string{}
for err in errors {
error_parts << '{"field":"${err.field}","message":"${err.message}","code":"${err.code}"}'
}
return '{"error":"Bad Request","errors":[${error_parts.join(",")}]}'
}
// get_validated_data - 从 Context 获取验证后的数据
pub fn get_validated_data(c Context) map[string]string {
mut data := map[string]string{}
// 从 store 中获取所有以 validated_ 开头的键
for key, value in c.store {
if key.starts_with('validated_') && key != 'validated_data' {
field_name := key[10..] // 移除 'validated_' 前缀
data[field_name] = value
}
}
return data
}
// get_validated_json - 从 Context 获取验证后的完整 JSON 数据
pub fn get_validated_json(c Context) ?string {
return c.get('validated_data')
}
// get_validated_field - 从 Context 获取单个验证后的字段值
pub fn get_validated_field(c Context, field string) ?string {
return c.get('validated_${field}')
}