rbdantic 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +245 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +5 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +852 -0
  8. data/README_CN.md +852 -0
  9. data/Rakefile +12 -0
  10. data/lib/rbdantic/base/access.rb +105 -0
  11. data/lib/rbdantic/base/dsl.rb +79 -0
  12. data/lib/rbdantic/base/validation.rb +152 -0
  13. data/lib/rbdantic/base.rb +30 -0
  14. data/lib/rbdantic/config.rb +60 -0
  15. data/lib/rbdantic/error_detail.rb +54 -0
  16. data/lib/rbdantic/field.rb +188 -0
  17. data/lib/rbdantic/json_schema/defs_registry.rb +79 -0
  18. data/lib/rbdantic/json_schema/generator.rb +148 -0
  19. data/lib/rbdantic/json_schema/types.rb +98 -0
  20. data/lib/rbdantic/serialization/dumper.rb +133 -0
  21. data/lib/rbdantic/serialization/json_serializer.rb +60 -0
  22. data/lib/rbdantic/validators/field_validator.rb +83 -0
  23. data/lib/rbdantic/validators/model_validator.rb +59 -0
  24. data/lib/rbdantic/validators/types/array.rb +77 -0
  25. data/lib/rbdantic/validators/types/base.rb +78 -0
  26. data/lib/rbdantic/validators/types/boolean.rb +37 -0
  27. data/lib/rbdantic/validators/types/float.rb +32 -0
  28. data/lib/rbdantic/validators/types/hash.rb +54 -0
  29. data/lib/rbdantic/validators/types/integer.rb +28 -0
  30. data/lib/rbdantic/validators/types/model.rb +75 -0
  31. data/lib/rbdantic/validators/types/number.rb +63 -0
  32. data/lib/rbdantic/validators/types/string.rb +70 -0
  33. data/lib/rbdantic/validators/types/symbol.rb +30 -0
  34. data/lib/rbdantic/validators/types/time.rb +33 -0
  35. data/lib/rbdantic/validators/types.rb +63 -0
  36. data/lib/rbdantic/validators/validator_context.rb +43 -0
  37. data/lib/rbdantic/version.rb +5 -0
  38. data/lib/rbdantic.rb +8 -0
  39. data/sig/rbdantic.rbs +4 -0
  40. metadata +84 -0
data/README_CN.md ADDED
@@ -0,0 +1,852 @@
1
+ # Rbdantic
2
+
3
+ **Ruby 数据验证与设置管理** - 一个受 Pydantic 启发的 Ruby 数据验证库。
4
+
5
+ Rbdantic 将 Pydantic 强大的数据验证能力引入 Ruby,提供运行时数据验证、序列化和 JSON Schema 生成,配合直观的 DSL 语法。
6
+
7
+ [English Documentation](README.md)
8
+
9
+ ## 功能特性
10
+
11
+ - **基础模型类** - 定义带有类型检查字段的数据模型
12
+ - **字段约束** - 内置字符串、数字和数组约束
13
+ - **自定义验证器** - 支持多种模式的字段级和模型级验证器
14
+ - **类型强制转换** - 可配置严格程度的自动类型转换
15
+ - **嵌套模型** - 支持嵌套模型验证
16
+ - **模型继承** - 子类继承字段和验证器
17
+ - **模型配置** - 灵活的配置选项(额外字段、冻结模型等)
18
+ - **序列化** - 支持过滤选项的 Hash 或 JSON 转换
19
+ - **JSON Schema 生成** - 自动生成 API 文档所需的 JSON Schema
20
+ - **详细错误报告** - 带位置路径的结构化验证错误
21
+
22
+ ## 安装
23
+
24
+ 添加到 Gemfile:
25
+
26
+ ```ruby
27
+ gem 'rbdantic'
28
+ ```
29
+
30
+ 或直接安装:
31
+
32
+ ```bash
33
+ gem install rbdantic
34
+ ```
35
+
36
+ ## 快速入门
37
+
38
+ ```ruby
39
+ require 'rbdantic'
40
+
41
+ class User < Rbdantic::BaseModel
42
+ field :name, String, min_length: 1, max_length: 100
43
+ field :email, String, pattern: /\A[^@\s]+@[^@\s]+\z/
44
+ field :age, Integer, gt: 0, le: 150
45
+ field :tags, [String], default_factory: -> { [] }
46
+ end
47
+
48
+ # 创建有效用户
49
+ user = User.new(
50
+ name: "Alice",
51
+ email: "alice@example.com",
52
+ age: 30
53
+ )
54
+
55
+ puts user.name # => "Alice"
56
+ puts user.age # => 30
57
+ puts user.tags # => []
58
+
59
+ # 序列化为 Hash
60
+ puts user.model_dump
61
+ # => { name: "Alice", email: "alice@example.com", age: 30, tags: [] }
62
+
63
+ # 序列化为 JSON
64
+ puts user.model_dump_json
65
+ # => {"name":"Alice","email":"alice@example.com","age":30,"tags":[]}
66
+
67
+ # 验证错误
68
+ begin
69
+ User.new(name: "", email: "invalid", age: -1)
70
+ rescue Rbdantic::ValidationError => e
71
+ e.errors.each do |err|
72
+ puts "#{err.loc.join('.')}: #{err.msg}"
73
+ end
74
+ # name: String must be at least 1 characters
75
+ # email: String does not match pattern ...
76
+ # age: Value must be greater than 0
77
+ end
78
+ ```
79
+
80
+ ## 字段定义
81
+
82
+ ### 基本字段
83
+
84
+ ```ruby
85
+ class Product < Rbdantic::BaseModel
86
+ field :id, Integer
87
+ field :name, String
88
+ field :price, Float
89
+ field :active, Rbdantic::Boolean
90
+ end
91
+ ```
92
+
93
+ ### 默认值
94
+
95
+ ```ruby
96
+ class Config < Rbdantic::BaseModel
97
+ # 静态默认值
98
+ field :timeout, Integer, default: 30
99
+
100
+ # 动态默认值(工厂)
101
+ field :created_at, Time, default_factory: -> { Time.now }
102
+
103
+ # 可选字段(可以为 nil)
104
+ field :nickname, String, optional: true
105
+ end
106
+ ```
107
+
108
+ ### 字段约束
109
+
110
+ #### 字符串约束
111
+
112
+ ```ruby
113
+ class User < Rbdantic::BaseModel
114
+ field :username, String,
115
+ min_length: 3,
116
+ max_length: 20,
117
+ pattern: /\A[a-zA-Z0-9_]+\z/
118
+ end
119
+ ```
120
+
121
+ #### 数字约束
122
+
123
+ ```ruby
124
+ class Product < Rbdantic::BaseModel
125
+ field :price, Float,
126
+ gt: 0, # 大于
127
+ le: 10000 # 小于或等于
128
+
129
+ field :quantity, Integer,
130
+ ge: 0, # 大于或等于
131
+ multiple_of: 1
132
+ end
133
+ ```
134
+
135
+ #### 数组约束
136
+
137
+ ```ruby
138
+ class Order < Rbdantic::BaseModel
139
+ field :items, [String],
140
+ min_items: 1,
141
+ max_items: 100,
142
+ unique_items: true
143
+
144
+ end
145
+ ```
146
+
147
+ ### 字段内自定义验证器
148
+
149
+ ```ruby
150
+ class User < Rbdantic::BaseModel
151
+ # Proc 验证器,返回 false 表示失败
152
+ field :email, String,
153
+ validators: [->(v) { v.include?("@") || false }]
154
+
155
+ # Proc 验证器,返回错误消息
156
+ field :password, String,
157
+ validators: [->(v) { v.length >= 8 ? nil : "密码长度至少8个字符" }]
158
+ end
159
+ ```
160
+
161
+ ## 模型配置
162
+
163
+ 使用 `model_config` 配置模型行为:
164
+
165
+ ```ruby
166
+ class User < Rbdantic::BaseModel
167
+ model_config(
168
+ extra: :forbid, # 拒绝额外字段
169
+ frozen: true, # 创建后不可变
170
+ strict: true, # 严格类型检查
171
+ coerce_mode: :strict, # 不进行类型转换
172
+ validate_assignment: true # 字段赋值时验证
173
+ )
174
+
175
+ field :name, String
176
+ end
177
+ ```
178
+
179
+ ### 配置选项
180
+
181
+ | 选项 | 可选值 | 说明 |
182
+ |------|--------|------|
183
+ | `extra` | `:ignore`, `:forbid`, `:allow` | 如何处理未定义的额外字段 |
184
+ | `frozen` | `true`, `false` | 初始化后冻结模型使其不可变 |
185
+ | `strict` | `true`, `false` | 严格类型检查(不转换类型) |
186
+ | `coerce_mode` | `:strict`, `:coerce` | 启用/禁用类型强制转换 |
187
+ | `validate_assignment` | `true`, `false` | 字段赋值时进行验证 |
188
+
189
+ ### 额外字段行为
190
+
191
+ ```ruby
192
+ # 忽略额外字段(默认)
193
+ class ModelA < Rbdantic::BaseModel
194
+ model_config extra: :ignore
195
+ field :name, String
196
+ end
197
+ ModelA.new(name: "test", extra: "data") # extra 字段被丢弃
198
+
199
+ # 禁止额外字段
200
+ class ModelB < Rbdantic::BaseModel
201
+ model_config extra: :forbid
202
+ field :name, String
203
+ end
204
+ ModelB.new(name: "test", extra: "data") # 抛出 ValidationError
205
+
206
+ # 允许额外字段
207
+ class ModelC < Rbdantic::BaseModel
208
+ model_config extra: :allow
209
+ field :name, String
210
+ end
211
+ m = ModelC.new(name: "test", extra: "data")
212
+ m[:extra] # => "data"
213
+ ```
214
+
215
+ ## 验证器
216
+
217
+ ### 字段验证器
218
+
219
+ 字段验证器在不同阶段运行:
220
+
221
+ ```ruby
222
+ class User < Rbdantic::BaseModel
223
+ field :email, String
224
+
225
+ # 验证前 - 可转换值
226
+ field_validator :email, mode: :before do |value, ctx|
227
+ value&.downcase
228
+ end
229
+
230
+ # 验证后 - 验证转换后的值
231
+ field_validator :email, mode: :after do |value, ctx|
232
+ raise "邮箱格式无效" unless value.include?("@")
233
+ value
234
+ end
235
+ end
236
+ ```
237
+
238
+ #### 验证器模式
239
+
240
+ | 模式 | 说明 |
241
+ |------|------|
242
+ | `:before` | 类型验证前运行,可转换值 |
243
+ | `:after` | 类型验证后运行,验证最终值 |
244
+ | `:plain` | 替代类型验证运行(跳过类型检查) |
245
+ | `:wrap` | 所有其他验证器之后运行 |
246
+
247
+ ### 模型验证器
248
+
249
+ 模型验证器验证整个模型:
250
+
251
+ ```ruby
252
+ class Account < Rbdantic::BaseModel
253
+ field :password, String
254
+ field :confirm_password, String
255
+
256
+ # 前置验证器 - 预处理输入数据
257
+ model_validator mode: :before do |data|
258
+ data[:password] = data[:password]&.strip
259
+ data
260
+ end
261
+
262
+ # 后置验证器 - 验证模型状态
263
+ model_validator mode: :after do |model|
264
+ if model.password != model.confirm_password
265
+ raise "密码不匹配"
266
+ end
267
+ end
268
+ end
269
+ ```
270
+
271
+ ## 嵌套模型
272
+
273
+ Rbdantic 像 Pydantic 一样支持嵌套模型,让你可以构建带有层级验证的复杂数据结构。
274
+
275
+ ### 单层嵌套模型
276
+
277
+ ```ruby
278
+ class Address < Rbdantic::BaseModel
279
+ field :street, String, min_length: 1
280
+ field :city, String, min_length: 1
281
+ field :zip_code, String, pattern: /\A\d{5}\z/
282
+ end
283
+
284
+ class User < Rbdantic::BaseModel
285
+ field :name, String
286
+ field :address, Address # 嵌套模型类型
287
+ end
288
+
289
+ # 从哈希创建 - 嵌套模型自动验证
290
+ user = User.new(
291
+ name: "Alice",
292
+ address: {
293
+ street: "123 Main St",
294
+ city: "Boston",
295
+ zip_code: "02134"
296
+ }
297
+ )
298
+
299
+ puts user.address.class # => Address
300
+ puts user.address.city # => "Boston"
301
+
302
+ # 或传入已构建的嵌套模型实例
303
+ address = Address.new(street: "456 Oak Ave", city: "Cambridge", zip_code: "02139")
304
+ user = User.new(name: "Jane", address: address)
305
+
306
+ # 序列化 - 嵌套模型递归输出
307
+ user.model_dump
308
+ # => { name: "Jane", address: { street: "456 Oak Ave", city: "Cambridge", zip_code: "02139" } }
309
+ ```
310
+
311
+ ### 多层嵌套模型
312
+
313
+ 可以任意深度嵌套模型:
314
+
315
+ ```ruby
316
+ class Country < Rbdantic::BaseModel
317
+ field :code, String, pattern: /\A[A-Z]{2}\z/
318
+ field :name, String
319
+ end
320
+
321
+ class City < Rbdantic::BaseModel
322
+ field :name, String
323
+ field :country, Country # 嵌套中的嵌套
324
+ end
325
+
326
+ class Person < Rbdantic::BaseModel
327
+ field :name, String
328
+ field :birthplace, City # 两层嵌套
329
+ end
330
+
331
+ # 创建多层嵌套结构
332
+ person = Person.new(
333
+ name: "Alice",
334
+ birthplace: {
335
+ name: "Paris",
336
+ country: {
337
+ code: "FR",
338
+ name: "France"
339
+ }
340
+ }
341
+ )
342
+
343
+ puts person.birthplace.country.code # => "FR"
344
+ ```
345
+
346
+ ### 嵌套模型数组
347
+
348
+ 使用 `[Type]` 简写验证嵌套模型数组:
349
+
350
+ ```ruby
351
+ class Item < Rbdantic::BaseModel
352
+ field :name, String, min_length: 1
353
+ field :quantity, Integer, gt: 0
354
+ field :price, Float, ge: 0
355
+ end
356
+
357
+ class Order < Rbdantic::BaseModel
358
+ field :order_id, String
359
+ field :items, [Item], min_items: 1
360
+ end
361
+
362
+ # 创建包含多个商品的订单
363
+ order = Order.new(
364
+ order_id: "ORD-001",
365
+ items: [
366
+ { name: "Widget", quantity: 5, price: 9.99 },
367
+ { name: "Gadget", quantity: 2, price: 19.99 }
368
+ ]
369
+ )
370
+
371
+ puts order.items[0].class # => Item
372
+ puts order.items.length # => 2
373
+
374
+ # 序列化 - 数组元素递归输出
375
+ order.model_dump
376
+ # => { order_id: "ORD-001", items: [{ name: "Widget", quantity: 5, price: 9.99 }, ...] }
377
+ ```
378
+
379
+ ### 可选嵌套模型
380
+
381
+ ```ruby
382
+ class Profile < Rbdantic::BaseModel
383
+ field :bio, String
384
+ field :avatar_url, String
385
+ end
386
+
387
+ class User < Rbdantic::BaseModel
388
+ field :name, String
389
+ field :profile, Profile, optional: true # 可以是 nil
390
+ end
391
+
392
+ # 不带 profile
393
+ user = User.new(name: "Bob")
394
+ puts user.profile # => nil
395
+
396
+ # 带 profile
397
+ user = User.new(name: "Bob", profile: { bio: "Developer", avatar_url: "..." })
398
+ puts user.profile.bio # => "Developer"
399
+ ```
400
+
401
+ ### 嵌套模型验证错误
402
+
403
+ 嵌套模型中的错误包含完整路径:
404
+
405
+ ```ruby
406
+ begin
407
+ User.new(
408
+ name: "Alice",
409
+ address: {
410
+ street: "", # 无效: 太短
411
+ city: "Boston",
412
+ zip_code: "invalid" # 无效: 模式不匹配
413
+ }
414
+ )
415
+ rescue Rbdantic::ValidationError => e
416
+ e.errors.each do |err|
417
+ puts "#{err.loc.join('.')} - #{err.msg}"
418
+ end
419
+ # address.street - String must be at least 1 characters
420
+ # address.zip_code - String does not match pattern ...
421
+ end
422
+
423
+ # 多层嵌套错误路径
424
+ begin
425
+ Person.new(
426
+ name: "Bob",
427
+ birthplace: {
428
+ name: "London",
429
+ country: { code: "invalid", name: "UK" }
430
+ }
431
+ )
432
+ rescue Rbdantic::ValidationError => e
433
+ puts e.errors.first.loc # => [:birthplace, :country, :code]
434
+ end
435
+
436
+ # 数组元素错误路径
437
+ begin
438
+ Order.new(
439
+ order_id: "ORD-001",
440
+ items: [
441
+ { name: "Widget", quantity: 5, price: 9.99 },
442
+ { name: "", quantity: 0, price: -1 } # 索引1处的无效元素
443
+ ]
444
+ )
445
+ rescue Rbdantic::ValidationError => e
446
+ e.errors.each do |err|
447
+ puts "#{err.loc.join('.')} - #{err.msg}"
448
+ end
449
+ # items.1.name - String must be at least 1 characters
450
+ # items.1.quantity - Value must be greater than 0
451
+ # items.1.price - Value must be greater than or equal to 0
452
+ end
453
+ ```
454
+
455
+ ### 自引用模型
456
+
457
+ 模型可以引用自身实现递归结构:
458
+
459
+ ```ruby
460
+ class TreeNode < Rbdantic::BaseModel
461
+ field :value, String
462
+ field :children, [TreeNode], default_factory: -> { [] }
463
+ end
464
+
465
+ tree = TreeNode.new(
466
+ value: "root",
467
+ children: [
468
+ { value: "child1", children: [{ value: "grandchild1" }] },
469
+ { value: "child2" }
470
+ ]
471
+ )
472
+
473
+ puts tree.children[0].children[0].value # => "grandchild1"
474
+ ```
475
+
476
+ ## 继承
477
+
478
+ 字段、验证器和配置均可继承:
479
+
480
+ ```ruby
481
+ class Animal < Rbdantic::BaseModel
482
+ field :name, String
483
+ field :age, Integer, gt: 0
484
+
485
+ model_config extra: :ignore
486
+ end
487
+
488
+ class Dog < Animal
489
+ field :breed, String # 继承 name 和 age
490
+ end
491
+
492
+ class Cat < Animal
493
+ model_config extra: :allow
494
+ end
495
+ ```
496
+
497
+ **注意:** 子类会继承父类的 `model_config`,只需要覆盖想修改的配置项。
498
+
499
+ ## 序列化
500
+
501
+ ### model_dump
502
+
503
+ 将模型转换为 Hash,支持多种选项:
504
+
505
+ ```ruby
506
+ class User < Rbdantic::BaseModel
507
+ field :name, String
508
+ field :role, String, default: "user"
509
+ field :active, Rbdantic::Boolean, default: true
510
+ end
511
+
512
+ user = User.new(name: "Alice")
513
+
514
+ # 完整输出
515
+ user.model_dump
516
+ # => { name: "Alice", role: "user", active: true }
517
+
518
+ # 排除默认值字段
519
+ user.model_dump(exclude_defaults: true)
520
+ # => { name: "Alice" }
521
+
522
+ # 只包含指定字段
523
+ user.model_dump(include: [:name])
524
+ # => { name: "Alice" }
525
+
526
+ # 排除指定字段
527
+ user.model_dump(exclude: [:active])
528
+ # => { name: "Alice", role: "user" }
529
+
530
+ # 排除未设置字段(初始化时未提供的)
531
+ user.model_dump(exclude_unset: true)
532
+ # => { name: "Alice" }
533
+ ```
534
+
535
+ ### model_dump_json
536
+
537
+ 转换为 JSON 字符串:
538
+
539
+ ```ruby
540
+ user.model_dump_json
541
+ # => {"name":"Alice","role":"user","active":true}
542
+
543
+ # 带缩进
544
+ user.model_dump_json(indent: 2)
545
+ # => {
546
+ # "name": "Alice",
547
+ # "role": "user",
548
+ # "active": true
549
+ # }
550
+ ```
551
+
552
+ ## JSON Schema 生成
553
+
554
+ 为 API 文档自动生成 JSON Schema:
555
+
556
+ ```ruby
557
+ class User < Rbdantic::BaseModel
558
+ field :id, Integer, gt: 0
559
+ field :name, String, min_length: 1, max_length: 100
560
+ field :email, String, pattern: /\A[^@\s]+@[^@\s]+\z/
561
+ field :age, Integer, optional: true, ge: 0, le: 150
562
+ end
563
+
564
+ schema = User.model_json_schema
565
+ # => {
566
+ # "$schema": "https://json-schema.org/draft/2020-12/schema",
567
+ # "type": "object",
568
+ # "title": "User",
569
+ # "properties": {
570
+ # "id": { "type": "integer", "exclusiveMinimum": 0 },
571
+ # "name": { "type": "string", "minLength": 1, "maxLength": 100 },
572
+ # "email": { "type": "string", "pattern": "^[^@\\s]+@[^@\\s]+$" },
573
+ # "age": { "type": ["integer", "null"], "minimum": 0, "maximum": 150 }
574
+ # },
575
+ # "required": ["id", "name", "email"]
576
+ # }
577
+ ```
578
+
579
+ ## 类型强制转换
580
+
581
+ 当设置 `coerce_mode: :coerce` 时自动进行类型转换:
582
+
583
+ ```ruby
584
+ class Config < Rbdantic::BaseModel
585
+ model_config coerce_mode: :coerce
586
+
587
+ field :count, Integer
588
+ field :price, Float
589
+ field :enabled, Rbdantic::Boolean
590
+ end
591
+
592
+ config = Config.new(
593
+ count: "42", # 转换为 42
594
+ price: "19.99", # 转换为 19.99
595
+ enabled: "yes" # 转换为 true
596
+ )
597
+
598
+ config.count # => 42 (Integer)
599
+ config.price # => 19.99 (Float)
600
+ config.enabled # => true
601
+ ```
602
+
603
+ ### 支持的类型转换
604
+
605
+ | 目标类型 | 源示例 |
606
+ |----------|--------|
607
+ | `String` | 任何有 `to_s` 方法的值 |
608
+ | `Integer` | `"42"`, `42.0` |
609
+ | `Float` | `"3.14"`, `42` |
610
+ | `Rbdantic::Boolean` | `"true"`, `"yes"`, `"on"`, `"1"`, `1`, `"false"`, `"no"`, `"off"`, `"0"`, `0` |
611
+ | `Array` | 可用 `split` 分割的字符串,任何有 `to_a` 方法的值 |
612
+ | `Hash` | 键值对数组,任何有 `to_h` 方法的值 |
613
+
614
+ ## 验证错误
615
+
616
+ ValidationError 提供详细的错误信息:
617
+
618
+ ```ruby
619
+ begin
620
+ User.new(name: "", age: -1)
621
+ rescue Rbdantic::ValidationError => e
622
+ e.error_count # => 2
623
+ e.errors # => ErrorDetail 数组
624
+ e.as_json # => { errors: [...], error_count: 2 }
625
+ e.to_h # => 同 as_json
626
+
627
+ e.errors.each do |err|
628
+ err.type # => :string_too_short, :value_not_greater_than
629
+ err.loc # => [:name], [:age] (位置路径)
630
+ err.msg # => "String must be at least..."
631
+ err.input # => "" (原始输入值)
632
+ end
633
+ end
634
+ ```
635
+
636
+ ## 支持的类型
637
+
638
+ | 类型 | 说明 |
639
+ |------|------|
640
+ | `String` | 内置字符串类型 |
641
+ | `Integer` | 内置整数类型 |
642
+ | `Float` | 内置浮点数类型 |
643
+ | `Rbdantic::Boolean` | 布尔字段,接受 true/false |
644
+ | `Symbol` | Ruby 符号,最大长度 256 字符(防止 DoS 攻击) |
645
+ | `[Type]` | 带元素校验的数组 |
646
+ | `Hash` | 键值哈希类型 |
647
+ | `Time` | Ruby Time 类型 |
648
+ | `Rbdantic::BaseModel` 子类 | 嵌套模型验证 |
649
+
650
+ **注意:** 对外布尔字段统一使用 `Rbdantic::Boolean`。
651
+
652
+ ```ruby
653
+ class Config < Rbdantic::BaseModel
654
+ field :enabled, Rbdantic::Boolean
655
+ field :active, Rbdantic::Boolean, optional: true
656
+ end
657
+ ```
658
+
659
+ ## 格式验证
660
+
661
+ 内置常用格式的验证器:
662
+
663
+ ```ruby
664
+ class User < Rbdantic::BaseModel
665
+ field :email, String, format: :email # 基础邮箱验证
666
+ field :website, String, format: :uri # URI 验证 (http/https)
667
+ end
668
+ ```
669
+
670
+ | 格式 | 模式 |
671
+ |------|------|
672
+ | `:email` | 基础邮箱检查 (user@domain) |
673
+ | `:uri` | HTTP/HTTPS URI |
674
+
675
+ 复杂验证请使用自定义 `pattern` 正则或 `field_validator`。
676
+
677
+ ## 限制与安全
678
+
679
+ ### 安全限制
680
+
681
+ | 限制 | 值 | 目的 |
682
+ |------|-----|------|
683
+ | Symbol 最大长度 | 256 字符 | 防止 Symbol DoS 攻击 |
684
+ | 嵌套模型深度 | ~20 层 | 防止栈溢出 |
685
+
686
+ 这些限制防止恶意输入耗尽内存或导致栈溢出。
687
+
688
+ ### 线程安全
689
+
690
+ 模型初始化后的读取操作是线程安全的。但需注意:
691
+
692
+ - 初始化过程中的验证不是线程安全的(使用内部状态)
693
+ - `validate_assignment` 模式使用实例级锁
694
+ - 变更期间避免跨线程共享模型实例
695
+
696
+ ## 与 Pydantic 的差异
697
+
698
+ | 功能 | Pydantic | Rbdantic |
699
+ |------|----------|----------|
700
+ | 字段别名 | `Field(alias="name")` | `alias_name:` 配合 `by_alias: true` |
701
+ | 计算字段 | `@computed_field` | 不支持 |
702
+ | 泛型模型 | `BaseModel[T]` | 不支持 |
703
+ | 序列化别名 | `serialization_alias` | 使用 `alias_name:` 与 dump/schema 的 `by_alias:` |
704
+ | 模型复制/更新 | `model.copy(update={})` | 提供 `copy(deep:)` 与 `update(**data)` 辅助方法 |
705
+ | 判断联合类型 | `Annotated[Union, Field(discriminator)]` | 不支持 |
706
+ | 自定义类型适配器 | `TypeAdapter` | 使用验证器替代 |
707
+ | 布尔类型 | `bool` | `Rbdantic::Boolean` |
708
+ | 配置类 | `BaseModelConfig` | `model_config` 哈希 |
709
+
710
+ ### API 命名差异
711
+
712
+ | Pydantic | Rbdantic |
713
+ |----------|----------|
714
+ | `Field()` | `field :name, Type, **options` |
715
+ | `@field_validator` | `field_validator :name, mode: ...` |
716
+ | `@model_validator` | `model_validator mode: ...` |
717
+ | `model_config = ConfigDict(...)` | `model_config(...)` |
718
+ | `model_dump()` | `model_dump()` |
719
+ | `model_dump_json()` | `model_dump_json()` |
720
+ | `model_validate()` | `Model.model_validate(data)` |
721
+
722
+ ## 系统要求
723
+
724
+ - Ruby >= 2.7(支持关键字参数和模式匹配)
725
+ - 无外部依赖(纯 Ruby 实现)
726
+
727
+ ## 错误处理最佳实践
728
+
729
+ ### 捕获特定字段错误
730
+
731
+ ```ruby
732
+ begin
733
+ User.new(name: "", email: "invalid")
734
+ rescue Rbdantic::ValidationError => e
735
+ # 查找特定字段的错误
736
+ name_errors = e.errors.select { |err| err.loc.first == :name }
737
+ puts "名称错误: #{name_errors.map(&:msg).join(', ')}"
738
+
739
+ # 按字段分组错误
740
+ errors_by_field = e.errors.group_by { |err| err.loc.first }
741
+ errors_by_field.each do |field, errs|
742
+ puts "#{field}: #{errs.map(&:msg).join(', ')}"
743
+ end
744
+ end
745
+ ```
746
+
747
+ ### 自定义错误消息
748
+
749
+ 使用 `field_validator` 自定义消息:
750
+
751
+ ```ruby
752
+ class User < Rbdantic::BaseModel
753
+ field :password, String
754
+
755
+ field_validator :password, mode: :after do |value, ctx|
756
+ if value.length < 8
757
+ raise Rbdantic::ValidationError::ErrorDetail.new(
758
+ type: :password_too_short,
759
+ loc: [:password],
760
+ msg: "密码至少需要8个字符(当前#{value.length}个)",
761
+ input: value
762
+ )
763
+ end
764
+ value
765
+ end
766
+ end
767
+ ```
768
+
769
+ ### API 错误 JSON 响应
770
+
771
+ ```ruby
772
+ rescue Rbdantic::ValidationError => e
773
+ # 返回 JSON 用于 API 响应
774
+ status 400
775
+ json e.as_json
776
+ # => { "errors": [...], "error_count": 2 }
777
+ ```
778
+
779
+ ## API 参考
780
+
781
+ ### Rbdantic::BaseModel 类方法
782
+
783
+ | 方法 | 说明 |
784
+ |------|------|
785
+ | `field(name, type, **options)` | 定义字段及其类型和约束 |
786
+ | `model_config(**options)` | 配置模型行为 |
787
+ | `field_validator(name, mode:, &block)` | 定义字段级验证器 |
788
+ | `model_validator(mode:, &block)` | 定义模型级验证器 |
789
+ | `model_json_schema(**options)` | 生成 JSON Schema |
790
+ | `model_fields` | 返回字段定义哈希 |
791
+ | `model_config` | 返回模型配置 |
792
+ | `inherited(subclass)` | 继承钩子(内部使用) |
793
+
794
+ ### 实例方法
795
+
796
+ | 方法 | 说明 |
797
+ |------|------|
798
+ | `initialize(data = {})` | 创建并验证模型 |
799
+ | `model_dump(**options)` | 转换为 Hash |
800
+ | `model_dump_json(indent: nil)` | 转换为 JSON 字符串 |
801
+ | `[name]` | 括号访问字段值 |
802
+ | `[name] = value` | 括号赋值字段值 |
803
+
804
+ ### 字段选项
805
+
806
+ | 选项 | 类型 | 说明 |
807
+ |------|------|------|
808
+ | `default` | Any | 静态默认值 |
809
+ | `default_factory` | Proc | 动态默认值生成器 |
810
+ | `optional` | Boolean | 允许 nil 值 |
811
+ | `required` | Boolean | 设为 `false` 允许 nil(等同于 `optional: true`) |
812
+ | `validators` | Array | 自定义验证器 Proc |
813
+ | `alias_name` | Symbol | 输入/输出的替代名称(配合 `by_alias: true` 使用) |
814
+ | `format` | Symbol | 内置格式验证器(`:email`、`:uri`、`:uuid`) |
815
+ | `min_length` | Integer | 字符串最小长度 |
816
+ | `max_length` | Integer | 字符串最大长度 |
817
+ | `pattern` | Regexp | 字符串正则匹配 |
818
+ | `gt` | Numeric | 大于 |
819
+ | `ge` | Numeric | 大于或等于 |
820
+ | `lt` | Numeric | 小于 |
821
+ | `le` | Numeric | 小于或等于 |
822
+ | `multiple_of` | Numeric | 必须是该数的倍数 |
823
+ | `min_items` | Integer | 数组最小元素数 |
824
+ | `max_items` | Integer | 数组最大元素数 |
825
+ | `unique_items` | Boolean | 数组元素必须唯一 |
826
+
827
+ ## 开发
828
+
829
+ 检出仓库后:
830
+
831
+ ```bash
832
+ bin/setup # 安装依赖
833
+ rake spec # 运行测试
834
+ bin/console # 交互式提示
835
+ bundle exec rake install # 本地安装 gem
836
+ ```
837
+
838
+ ## 贡献
839
+
840
+ 欢迎提交 Bug 报告和 Pull Request。
841
+
842
+ ## 许可证
843
+
844
+ 本 gem 基于 [MIT 许可证](https://opensource.org/licenses/MIT) 开源。
845
+
846
+ ## 致谢
847
+
848
+ 本库受 [Pydantic](https://github.com/pydantic/pydantic) 启发 - 优秀的 Python 数据验证库。
849
+
850
+ ## 开发说明
851
+
852
+ 本库主要由 AI (Claude) 协助开发,展示了 AI 工具如何加速软件开发,同时保持代码质量和全面测试。