rydantic 0.3.0

Type-driven models, validation, JSON Schema and native Cap'n Proto for Rust
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
# Rydantic

[![CI](https://github.com/trotsky1997/Rydantic/actions/workflows/ci.yml/badge.svg)](https://github.com/trotsky1997/Rydantic/actions/workflows/ci.yml)

Rust 的统一数据模型库,当前版本 **0.3.0**,支持 JSON 与原生 Cap’n Proto。

0.3 新增 `#[rydantic::capnp_models]`:从 Rust 模型自动推导 `.capnp` Schema、生成官方 Rust 绑定并关联模型。无需维护 Schema、绑定或 `build.rs`。

一份 `#[derive(Model)]` 声明提供:JSON 解析、显式类型转换、字符串规范化、约束校验、结构化错误、Serde 序列化/反序列化,以及 JSON Schema Draft 2020-12。

工作区包含运行时 `rydantic`、过程宏 `rydantic-derive`、依赖重命名示例和 Cap’n Proto 协议集成示例。已使用 Rust 1.98.1 验证;工作区附带 Cargo.lock。

## 快速开始

在项目的 Cargo.toml 中添加 crates.io 依赖:

```toml
[dependencies]
rydantic = "0.3.0"
```

```rust
use rydantic::{Model, SchemaMode};

#[derive(Debug, Model)]
#[model(extra = "forbid")]
struct User {
    #[model(trim, min_length = 3, max_length = 30)]
    name: String,

    #[model(coerce, ge = 18, le = 120)]
    age: u8,

    #[model(trim, lowercase, email)]
    email: String,

    #[model(default)]
    tags: Vec<String>,

    note: Option<String>,
}

fn main() -> Result<(), rydantic::ValidationErrors> {
    let user = User::parse_json(
        r#"{"name":" Alice ","age":"25","email":" ALICE@EXAMPLE.COM "}"#,
    )?;

    assert_eq!(user.name, "Alice");
    assert_eq!(user.age, 25);
    assert_eq!(user.email, "alice@example.com");
    println!("{}", user.dump_json()?);

    let schema = User::json_schema(SchemaMode::Output);
    assert!(schema.is_object());
    Ok(())
}
```

`Model` 同时生成 Serde 的 `Serialize`、`Deserialize` 实现。模型只需派生一次 `Model`;Serde 实现中的反序列化会执行完整校验,序列化会重新校验当前对象。

```sh
cargo run -p rydantic --example user
cargo run -p rydantic --example tool_call
cargo run -p rydantic --example tool_call -- --schema
cargo run -p rydantic --example tool_call -- '{"kind":"ping"}'
```

## 已实现的类型与规则

支持 `String`、借用的 `&str`/`Cow<str>`、`bool`、8/16/32/64/128 位有符号和无符号整数、`isize`/`usize`、`f32`/`f64`、`Option<T>`、`Box<T>`、`Vec<T>`、`[T; N]`、字符串键的 `BTreeMap`/`HashMap`、`serde_json::Value`、保留原文的 `Box<RawValue>`,以及嵌套、递归、泛型模型。

| 字段属性 | 行为 |
| --- | --- |
| `trim`、`lowercase`、`uppercase` | 按声明的固定流水线规范化字符串 |
| `coerce` | 数字字符串转换为数值;`"true"`/`"false"` 转为布尔值;数值/布尔值转为字符串 |
| `min_length`、`max_length` | 字符串 Unicode 标量数量、集合元素数量、映射条目数量 |
| `ge`、`gt`、`le`、`lt` | 数值范围;阈值使用数值字面量或精确十进制字符串 |
| `pattern = "..."` | Rust regex;模式在编译期检查,在运行时缓存 |
| `email` | 使用 `email_address` 的邮箱地址规则 |
| `default` | 字段类型的 `Default::default()` |
| `default = expression` | 显式默认值;字符串字面量可直接用于 String |
| `default_factory = path` | 每次缺失字段时调用工厂函数 |
| `required` | 即使字段为 Option,也要求输入中出现该字段 |
| `rename = "..."` | 同时设置输入与输出名称 |
| `validation_alias`、`serialization_alias` | 分别设置输入与输出名称 |
| `exclude` | 输出与输出 Schema 中省略,输入仍按规则处理 |
| `before = path` | 原始 JSON 值处理函数 |
| `validate = path` | 字段类型构造完成后的自定义校验 |
| `description = "..."` | Schema 描述;也自动读取字段文档注释 |
| `flatten` | 将具名模型或字符串键映射展开到当前 JSON 对象 |
| `extra` | 保存未知字段的映射,配合容器 `extra = "allow"` |
| `async_validate = path` | 等待异步字段校验函数 |

容器属性:`extra = "forbid"`(默认)、`extra = "ignore"`、`extra = "allow"`、`title = "..."`、`validate = path`、`async_validate = path`、枚举的 `tag = "..."` 或 `untagged`。

解析顺序为:读取输入/产生默认值 → before → 类型解析与显式转换 → trim → 大小写转换 → 字段约束 → 字段自定义校验 → 模型自定义校验。默认值也经过同一流程。独立字段的错误会收集到同一份结果中;父模型的自定义校验在其字段全部成功后执行。

数字字符串需要显式启用 `coerce`。JSON 数值 `25.0` 可用于整数,`25.5` 会失败;这与 JSON Schema 对数学整数的定义一致。范围比较保留 128 位整数精度;极大指数输入会经过有界处理。浮点数遵循 Rust 的 IEEE 754 表示,NaN、无穷值以及超出目标类型范围的数值会失败。

## 缺失字段与 null

| 声明 | 输入可省略 | 接受 null |
| --- | --- | --- |
| `name: String` | 否 | 否 |
| `name: Option<String>` | 是,得到 None | 是 |
| `#[model(required)] name: Option<String>` | 否 | 是 |
| `#[model(default = "guest")] name: String` | 是,得到默认值 | 否 |

## 自定义校验

```rust
use rydantic::{Model, ValidationError, ValidationErrors, Value};

fn expand(value: Value) -> Result<Value, ValidationError> {
    if value == "many" { Ok(rydantic::json!(10)) } else { Ok(value) }
}

fn even(value: &u32) -> Result<(), ValidationError> {
    if value.is_multiple_of(2) { Ok(()) }
    else { Err(ValidationError::new("even", "value must be even")) }
}

fn check_window(value: &Window) -> Result<(), ValidationErrors> {
    if value.end >= value.start { Ok(()) }
    else { Err(ValidationError::custom("end must be >= start").at("end").into()) }
}

#[derive(Model)]
#[model(validate = check_window)]
struct Window {
    start: u32,
    #[model(before = expand, validate = even)]
    end: u32,
}
```

嵌套时自定义错误路径会自动加上父级路径。`ValidationErrors::errors()` 提供错误切片,`to_json()` 提供结构化 JSON。每个错误包含 `loc`、`code`、`message`、`stage` 与约束参数;`pointer()` 输出转义后的 JSON Pointer。

```json
[
  {
    "loc": ["addresses", 0, "city"],
    "code": "too_short",
    "message": "length must be >= 2",
    "stage": "validate",
    "params": {"actual": 1, "limit": 2}
  }
]
```

## 枚举与工具参数

无字段枚举编码为字符串。带字段枚举通过显式 discriminator 编码为对象:

```rust
use rydantic::Model;

#[derive(Model)]
#[model(tag = "kind")]
enum ToolCall {
    #[model(rename = "search")]
    Search {
        #[model(min_length = 1)]
        query: String,
        #[model(default = 3, ge = 1, le = 10)]
        limit: u8,
    },
    #[model(rename = "ping")]
    Ping,
}
```

对应输入为 `{"kind":"search","query":"Rust"}` 或 `{"kind":"ping"}`,生成的 Schema 使用 `oneOf`、`const` 和明确的必需字段。

## 借用模型

带生命周期的声明使用同一个 `Model` derive,并通过 `BorrowModel` 解析。输入缓冲区需要覆盖模型的使用周期。

```rust
use rydantic::{BorrowModel, Model};
use std::borrow::Cow;

#[derive(Debug, Model)]
struct View<'a> {
    #[model(trim)]
    name: &'a str,
    #[model(trim, lowercase)]
    note: Cow<'a, str>,
}

let input = String::from(r#"{"name":" Alice ","note":" HELLO\nWORLD "}"#);
let view = View::parse_json(&input)?;
assert_eq!(view.name, "Alice");
assert_eq!(view.note, "hello\nworld");
```

`&str` 引用未转义的原始 JSON 字符串;`trim` 通过切片保留借用。需要解码转义或改变大小写时,使用 `Cow<str>`,它按需分配。需要分配的 `&str` 输入会返回 `borrowed_string`/`borrowed_transform` 错误。`before` 产生拥有所有权的 JSON 值,适合搭配 `Cow` 或拥有所有权的字段。

借用模型支持嵌套、递归、泛型、多生命周期、集合、flatten、Serde `from_str`/`from_slice`,以及 `BorrowValidated` 只读包装。`parse_slice` 从字节缓冲区借用;读取器入口用于拥有所有权的模型。

## Tuple struct 与 untagged enum

```rust
use rydantic::Model;

#[derive(Debug, Model)]
struct Username(#[model(trim, min_length = 2)] String);

#[derive(Debug, Model)]
struct Point(#[model(ge = 0)] i32, i32);

#[derive(Debug, Model)]
#[model(untagged)]
enum ResultValue {
    Count(#[model(ge = 1)] u32),
    Named { name: String },
    Pair(String, u8),
    Empty,
}
```

newtype 编码为内部值,多个 tuple 字段编码为固定长度数组,unit struct 编码为 `null`,空 tuple struct 编码为 `[]`。Tuple 字段支持校验和转换;名称、默认值、flatten 与 exclude 用在具名字段上。

Untagged 按声明顺序试解析,选择第一个通过同步校验的分支。失败时返回 `union_no_match` 和各分支的结构化错误;Schema 使用 `anyOf`。异步校验随后检查已选分支。内部标签枚举继续使用具名字段或 unit variant。

## Flatten 与额外字段保留

```rust
use rydantic::{Model, Value};
use std::collections::BTreeMap;

#[derive(Debug, Model)]
struct Address { city: String }

#[derive(Debug, Model)]
#[model(extra = "allow")]
struct Person {
    id: u32,
    #[model(flatten)]
    address: Option<Address>,
    #[model(extra)]
    extra: BTreeMap<String, Value>,
}

let person = Person::parse_json(r#"{"id":1,"city":"Paris","score":9}"#)?;
assert_eq!(person.extra["score"], 9);
assert_eq!(person.dump()?["city"], "Paris");
```

Flatten 支持具名模型、`Option`/`Box` 包装和字符串键的 `BTreeMap`/`HashMap`。先消费声明字段,再消费展开字段,最后由一个映射接收剩余键。允许多个展开的固定对象;每层允许一个接收未知键的字段。`extra = "allow"` 要求显式声明一个 `#[model(extra)]` 映射,映射值也会进行类型检查。

Optional flatten 在其所有键缺失时得到 `None`;出现部分键时检查内部必需字段。空的展开输入可以触发显式默认值。错误位置沿用展开后的 JSON 路径。声明字段的输入/输出名称会保留,额外字段键与这些名称重叠时返回 `field_collision`;序列化合并也检查键冲突。

## 日期与 Decimal

```toml
rydantic = { version = "0.3.0", features = ["chrono", "decimal"] }
```

`chrono` feature 支持 `NaiveDate`、`NaiveTime`、`NaiveDateTime`、`DateTime<Utc>`、`DateTime<FixedOffset>`,并从 `rydantic::chrono` 重导出类型。格式分别为 `YYYY-MM-DD`、`HH:MM:SS[.fraction]`、无时区的 ISO 日期时间、RFC 3339 日期时间。校验实际日历、四位年份、最多 9 位小数秒与 RFC 3339 时区;UTC 类型会换算到 UTC。

`decimal` feature 提供 `rydantic::rust_decimal::Decimal`,保留 96 位系数与最多 28 位小数。默认通过十进制字符串输入和输出,保留 scale;显式 `coerce` 也接受 JSON 数值。精度损失会失败,范围约束按精确十进制比较。

```rust
use rydantic::{Model, rust_decimal::Decimal};

#[derive(Debug, Model)]
struct Price {
    #[model(ge = "0.1000000000000000000000000001")]
    amount: Decimal,
}
```

日期语义、Decimal 精度和字符串数值范围会标记为 Schema 本地规则。运行 `cargo run -p rydantic --example dates_decimal --features chrono,decimal` 查看完整示例。

## 异步校验

```rust
use rydantic::{Model, ValidationError};

async fn available(name: &str) -> Result<(), ValidationError> {
    if name == "taken" {
        Err(ValidationError::new("taken", "name is already registered"))
    } else { Ok(()) }
}

#[derive(Debug, Model)]
struct Account {
    #[model(min_length = 2, async_validate = available)]
    name: String,
}

// 在 async 函数中:
// let account = Account::parse_json_async(input).await?;
// let json = account.dump_json_async().await?;
```

字段异步函数返回 `Result<(), ValidationError>`;容器异步函数返回 `Result<(), ValidationErrors>`。同步解析和校验全部成功后,按字段顺序等待异步校验;嵌套模型与集合会继续向内执行,同层字段错误会收集到一起。运行时采用 `Send` future,可搭配 Tokio 等执行器。

包含异步规则的模型及其父模型,使用 `parse_json_async`/`parse_value_async`、`validate_async`、`dump_async`/`dump_json_async`。同步解析、Serde 和同步 dump 会返回 `async_validation_required`。拥有所有权的模型可使用 `parse_validated_json_async`/`AsyncValidated::new`,其只读结果支持 Serde 序列化。借用模型也支持异步解析、校验和 dump。

derive 生成的异步支持要求字段类型实现 `AsyncCheck`,该 trait 要求 `Sync`。纯同步自定义类型可以添加空实现 `impl AsyncCheck for MyType {}`;自定义类型通过它提供异步遍历。存在异步规则时,`JsonType::async_required`/`BorrowType::async_required` 需返回对应元数据。库本身无需依赖执行器。`asynchronous` 示例使用开发依赖 Tokio。

## 流式解析

```rust
use rydantic::{stream, Model};
use std::io::Cursor;

#[derive(Debug, Model)]
struct Row { id: u32 }

for row in stream::ndjson::<_, Row>(Cursor::new("{\"id\":1}\n{\"id\":2}\n")) {
    println!("{}", row?.id);
}

stream::visit_array::<_, Row, _>(Cursor::new("[{\"id\":1},{\"id\":2}]"), |row| {
    println!("{}", row?.id);
    Ok(())
})?;
```

| 入口 | 输入与错误处理 |
| --- | --- |
| `Model::parse_reader` | 从 `Read` 读取单个 JSON 文档 |
| `stream::json_stream` | 空白分隔的文档序列;校验错误后继续,JSON 语法错误后终止 |
| `stream::ndjson` | 从 `BufRead` 逐行读取;空行跳过,语法/校验错误后继续下一行,附 `stream_line` |
| `stream::visit_array` | 逐元素回调;校验错误携带数组索引,回调返回错误即停止读取 |
| `stream::borrow_stream`/`visit_borrowed_array` | 从 `&str` 借用字段,返回值可在输入缓冲区存活期间保留 |
| `stream::documents` | 逐条返回 `Box<RawValue>`,可在循环中调用 `parse_json_async` |

这些入口使用同步 I/O。序列与数组访问器仅物化当前记录;内存随单条记录大小增长。读取器自身的缓冲策略由调用者决定。异步应用可在合适的 I/O 层接入,并逐条等待模型校验。同步 typed stream 遇到含异步规则的模型,会给出 `async_validation_required`。

运行示例:

```sh
cargo run -p rydantic --example borrowed
cargo run -p rydantic --example streaming
cargo run -p rydantic --example asynchronous
```

## 输入与输出 Schema

`SchemaMode::Input` 使用输入别名、输入必需字段与额外字段策略。`SchemaMode::Output` 使用输出别名、移除 exclude 字段,并将所有实际输出字段设为必需字段。递归模型通过 `$defs`/`$ref` 表达,定义名称包含完整 Rust 类型路径。

去空格、类型转换、自定义函数,以及 regex/email 的具体实现规则需要 Rydantic 执行。导出的 Schema 使用 `x-rydantic-local-rules` 标记这些行为;调用 `schema_report(mode)` 可以得到对应位置与规则。输入转换后的约束采用结构上的宽容描述,输出 Schema 则描述处理后的数据。字面量默认值仅在输入 Schema 中作为注释导出;生成 Schema 从不执行默认值工厂。

```rust
let report = User::schema_report(rydantic::SchemaMode::Input);
for local in &report.local_rules {
    println!("{}: {}", local.schema_path, local.rule);
}
```

Schema 的用途包括 API 文档与工具参数描述。实际执行入口仍应调用模型解析,确保本地规则得到执行。

## 运行时动态 Schema

可选 `dynamic` feature 使用 `jsonschema` 提供运行时 Schema 编译与复用。默认依赖关闭网络/文件解析功能,本地 `$ref` 可正常使用。

```toml
rydantic = { version = "0.3.0", features = ["dynamic"] }
```

```rust
use rydantic::{json, DynamicModel};

let model = DynamicModel::compile(json!({
    "type":"object",
    "properties":{"age":{"type":"integer","minimum":18}},
    "required":["age"],
    "additionalProperties":false
}))?;

assert!(model.is_valid(&json!({"age":25})));
assert!(model.validate(&json!({"age":17})).is_err());
```

`DynamicModel::compile` 会拒绝含 `x-rydantic-local-rules` 的 Schema。`compile_structural` 提供显式选择,仅执行标准 JSON Schema 断言。动态验证遵循后端默认的 format 注释语义,保留输入原样;默认值填充与转换由类型模型路径负责。

## 对象生命周期

Rust 中可直接构造或修改普通模型。调用 `validate()` 检查当前对象,调用 `dump()`/`dump_json()` 会先重新校验;这两个检查入口保持当前字段值。规范化发生在解析路径。

需要只读访问时使用 `User::parse_validated_json(...)` 或 `Validated::new(user)`,得到无 `DerefMut` 的 `Validated<User>`。`into_inner()` 可取回普通模型。自定义带内部可变性的 JsonType,需要自行维护修改规则。

## 原生 Cap’n Proto

启用 `capnp` feature 后,模型直接读取和构建[官方 Cap’n Proto](https://capnproto.org/) Schema 的字段。二进制布局与其他语言的官方实现互通,支持普通消息和 packed 消息。

### 从 Rust 模型自动生成协议

只需要一个依赖:

```toml
[dependencies]
rydantic = { version = "0.3.0", features = ["capnp"] }
```

首次配置开发环境时安装官方 `capnp` 编译器:macOS 使用 `brew install capnp`,Debian/Ubuntu 使用 `sudo apt-get install capnproto`。编译时属性宏自动调用该编译器,并使用官方 `capnpc` 生成绑定;支持通过 `RYDANTIC_CAPNP_COMPILER` 指定编译器可执行文件。

```rust
use rydantic::{CapnpModel, CapnpSchema, Model};

#[rydantic::capnp_models(namespace = "my_app.users")]
pub mod models {
    #[derive(Debug, PartialEq)]
    pub struct User {
        #[model(trim, min_length = 1)]
        pub name: String,
        #[model(ge = 18)]
        pub age: u8,
        pub role: Role,
        pub nickname: Option<String>,
        pub avatar: Vec<u8>,
    }

    #[derive(Debug, PartialEq)]
    pub enum Role { Guest, Admin }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    use models::User;
    let user = User::parse_json(
        r#"{"name":" Alice ","age":25,"role":"Admin","avatar":[0,255]}"#,
    )?;
    let bytes = user.dump_capnp()?;
    assert_eq!(User::parse_capnp(&bytes)?, user);
    let packed = user.dump_capnp_packed()?;
    assert_eq!(User::parse_capnp_packed(&packed)?, user);

    // 导出完整协议,供其他语言的官方生成器使用。
    User::write_capnp_schema("users.capnp")?;
    Ok(())
}
```

宏自动添加 `Model` 派生,可同时保留 `Debug`、`PartialEq` 等其他派生。模块包含:

- `models::wire`:官方生成的公开 Rust Reader/Builder/Enum。
- `models::CAPNP_SCHEMA`、`CAPNP_SCHEMA_ID`:完整协议文本和文件 ID。
- 每个模型的 `CapnpSchema`:`capnp_schema()`、`write_capnp_schema(path)` 和根类型名 `CAPNP_TYPE`。普通枚举也支持作为根消息进行二进制往返,协议中自动添加根包装类型。

默认映射自动覆盖嵌套及递归模型、`Box`、`Option`、`Vec`、固定数组、String 键的 `BTreeMap`/`HashMap`、普通枚举和带数据枚举。`Vec<u8>` 推导为 `Data`;完整范围 `i128`/`u128`、日期和 Decimal 推导为 `Text`,容器中的这些类型也适用。模块内的类型别名、带具体类型参数的容器别名、显式 `use` 别名和内联子模块可以一起使用。日期和 Decimal 分别需要启用 `chrono`/`decimal` feature。

模型需要使用具体且拥有所有权的类型,并放在属性覆盖的内联模块内。带数据枚举继续声明 JSON 表示规则:命名字段可使用 `#[model(tag = "kind")]`,元组变体使用 `#[model(untagged)]`。`capnp_name` 单独控制协议名字;`capnp_skip` 配合默认值保留本地字段;JSON 别名、flatten、extra、exclude 继续控制 JSON 表示。外部未知类型可使用默认值与 `capnp_skip`,或继续采用下面的已有 Schema 接入方式。

### 协议 ID 与兼容演进

默认文件 ID 从 Cargo 包名与当前模型模块名稳定计算;生产协议建议显式设置固定的 `namespace = "my_app.users"` 或 `id = 0x...`。同一包中多个同名模型模块应分配不同的 namespace;发布后保持 namespace/id、模型名和子模块路径稳定。

字段和枚举成员默认按声明顺序从 `@0` 编号。追加字段保留旧编号。需要调整声明顺序时,可在每个协议字段或枚举成员上固定 `#[model(capnp_ordinal = N)]`,编号须唯一、连续并从零开始。例如:

```rust
#[rydantic::capnp_models(namespace = "my_app.counters")]
mod models {
    pub struct Counter {
        #[model(capnp_ordinal = 1)]
        pub count: u64,
        #[model(capnp_ordinal = 0)]
        pub name: String,
    }
}
```

带数据枚举的命名/元组组字段共享协议编号空间;变体上的 `capnp_ordinal` 指定该变体的起始字段编号,字段本身也可固定编号。协议类型变更、删除字段、改名和修改 union 分组需要遵循[官方兼容规则](https://capnproto.org/language.html#evolving-your-protocol)。导出的 Schema 适合纳入代码审查;生成的 Rust 绑定会随模型自动更新。

完整自动生成示例和互操作测试见 [fixtures/auto-capnp](https://github.com/trotsky1997/Rydantic/tree/main/fixtures/auto-capnp):

```sh
cargo run -p rydantic-auto-capnp-fixture
cargo test -p rydantic-auto-capnp-fixture
```

### 接入已有 Schema

已有 `.capnp` 协议可继续使用显式绑定:

```toml
[dependencies]
rydantic = { version = "0.3.0", features = ["capnp"] }
capnp = { version = "0.27", features = ["unaligned"] }

[build-dependencies]
capnpc = "0.27"
```

用 `capnp id` 产生自己的文件 ID,然后编写 `schema/user.capnp`:

```capnp
@0xc886a6cb051d0cab;
struct User {
  name @0 :Text;
  age @1 :UInt16;
}
```

在 `build.rs` 调用官方代码生成器;本机需要安装 `capnp` 编译器:

```rust
fn main() {
    println!("cargo:rerun-if-changed=schema/user.capnp");
    capnpc::CompilerCommand::new()
        .src_prefix("schema")
        .file("schema/user.capnp")
        .run()
        .expect("generate Cap'n Proto bindings");
}
```

在 crate 根模块引入生成的绑定,再关联模型:

```rust
pub mod user_capnp {
    include!(concat!(env!("OUT_DIR"), "/user_capnp.rs"));
}
use rydantic::{CapnpModel, Model};

#[derive(Debug, Model)]
#[model(capnp = "user_capnp::user")]
struct User {
    #[model(trim, min_length = 2)]
    name: String,
    #[model(ge = 18, le = 120)]
    age: u8,
}

fn main() -> Result<(), rydantic::ValidationErrors> {
    let user = User::parse_json(r#"{"name":" Alice ","age":25}"#)?;
    let bytes = user.dump_capnp()?;
    assert_eq!(User::parse_capnp(&bytes)?.name, "Alice");
    let packed = user.dump_capnp_packed()?;
    assert_eq!(User::parse_capnp_packed(&packed)?.age, 25);
    Ok(())
}
```

| Rust 模型 | 原生 Schema 字段 |
| --- | --- |
| 布尔、整数、浮点数、String | Bool、对应数值类型、Text;整数宽度转换检查溢出 |
| 嵌套模型、Box | 关联模型的 Struct |
| Vec、固定数组 | List;固定数组检查长度,支持嵌套列表和结构列表 |
| Option | 含 `union { none @0 :Void; some @1 :T; }` 的包装 Struct |
| 字符串键映射 | `List(Entry)`,Entry 包含 `key :Text` 和 `value :T`;拒绝重复键 |
| `#[model(capnp_data)] Vec<u8>` | Data |
| `#[model(capnp_text)]` | Text;适用于日期、Decimal、完整范围 i128/u128 的标量表示 |
| `#[model(capnp_enum)]` 字段 | 原生 Enum;Rust 枚举声明 `#[model(capnp_enum = "wire_capnp::Role")]` |
| 数据枚举 | 原生 union,支持 unit、单字段及具名 group 分支;JSON 可使用 `untagged` 或受支持的 `tag` 形式 |
| Newtype、tuple、unit struct | 分别关联含 `value`、`item0`/`item1` 字段的 Struct,以及空 Struct |

默认使用 Rust 字段名寻找生成的访问器,`#[model(capnp_name = "fullName")]` 可单独指定协议字段;`#[model(capnp_skip, default = "local")]` 保存模型内部字段。JSON 的别名、flatten、extra 与 exclude 控制 JSON 表示;原生字段布局由 `.capnp` 文件决定。

`parse_capnp` 执行模型规范化与同步校验,`dump_capnp` 重新校验当前对象。异步模型使用 `parse_capnp_async`/`dump_capnp_async` 及 packed 对应入口。`parse_capnp_with_options` 接受官方 `ReaderOptions`,可调整遍历与嵌套限制;单消息入口拒绝尾随数据。`from_capnp_reader` 接受官方生成的 Reader,`to_capnp_message` 返回可供官方 API 使用的消息 Builder。`read_capnp`/`write_capnp` 是组合映射用的底层接口,完整校验应使用上述公开解析/输出入口。

原生关联要求具体的拥有所有权的模型。构造 Rust 模型会分配 String 和集合;需要直接访问消息内存时可保留官方 Reader。协议缺省值遵循 Cap’n Proto 语义;可选值用显式 union 表达,`capnp_skip` 字段使用模型默认值。JSON 的 `before` 回调仍接收该字段的 Value,日期与 Decimal 的 `capnp_text` 映射复用标量转换规则。

完整 Schema、官方生成的绑定和可执行示例见 [fixtures/capnp](https://github.com/trotsky1997/Rydantic/tree/main/fixtures/capnp)。运行示例和官方命令行互操作测试:

```sh
cargo run -p rydantic-capnp-fixture
cargo test -p rydantic-capnp-fixture --test native
# 需要系统 capnp 编译器:
cargo test -p rydantic-capnp-fixture --test native -- --include-ignored
# 修改示例 Schema 后重新生成并格式化绑定:
RYDANTIC_REGENERATE_CAPNP=1 cargo check -p rydantic-capnp-fixture
cargo fmt --all
```

## 实现范围与升级

0.2 已覆盖 0.1 README 列出的八类后续功能:借用字段、tuple struct、untagged union、flatten、内置日期/Decimal、异步 validator、额外字段保留与流式解析,同时扩展到 128 位整数。

拥有所有权的模型使用 `JsonType`/`Model`,借用模型使用 `BorrowType<'de>`/`BorrowModel<'de>`。两条 JSON 解析路径均从 `RawValue` 的原始字段切片直接构造模型和集合,省去完整输入的 Value 树;类型默认值也直接遍历校验。拥有所有权的模型按需构造 String,借用模型可保留输入中的字符串引用。Reader 入口缓存单个文档的原文,流式入口保留当前记录。

显式 `Value` 字段、`parse_value` 输入、`before` 回调,以及仅实现 `decode(Value, …)` 的自定义类型按各自接口使用 Value。原生标量复用轻量标量适配器;自定义类型可以覆盖 `JsonType::decode_input`,直接接收 `Input::Borrowed`。无需展开的任意 JSON 可保存为 `Box<RawValue>`,保留原文与数值写法。

0.1 的拥有所有权的模型入口继续可用。`StringType`/`NumericType`/`LengthType` 作为独立能力标记,泛型辅助函数需要显式声明 `JsonType` 或 `BorrowType` 约束。 自定义字段类型还需实现 `AsyncCheck` 并满足 `Sync`;原有纯同步类型可使用空的 `AsyncCheck` 实现。可通过 crates.io 的 `rydantic = "0.3.0"` 安装,也可使用 GitHub 的 v0.3.0 标签;工作区内部使用本地路径依赖。

## 验证

```sh
cargo test --workspace --all-features
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo fmt --all -- --check
cargo check -p rydantic --no-default-features --lib
cargo run -p rydantic-renamed-fixture
```

测试包含解析/序列化完整流程、递归/泛型/借用模型、缺失与 null、规范化、默认值、128 位精确整数边界、日期与 Decimal、异步与取消恢复、流式增量读取、错误路径、独立 JSON Schema 验证器对照,以及编译失败用例。边界矩阵覆盖 1,400 组输入;独立验证器还检查成功输出与输出 Schema 的一致性。

License: MIT.