club-kdl
English | 日本語
Rust 構造体に derive マクロを付けるだけで KDL の読み書きができるライブラリ。
[]
= "0.5"
なぜ club-kdl?
KDL の公式 Rust 実装 kdl-rs は AST レベル の操作に集中したライブラリで、 Rust 構造体との往復は手書きが必要。 club-kdl は kdl-rs の上に 属性ベースの derive 層 を載せて、 #[derive(KdlDeserialize, KdlSerialize)] だけで struct ↔ KDL の往復を完結させる。
| ライブラリ | 立ち位置 | 適性 |
|---|---|---|
kdl |
KDL parser / AST | 動的に KDL を構築・編集 / spec 準拠の low-level 操作 |
knuffel / knus |
derive ベースの parser | spec 準拠重視 / parse 側に偏る |
club-kdl |
derive ベースの ser/de | struct ↔ KDL 双方向 / 親子ノード名自動解決 / enum data variants |
club-kdl は内部で kdl crate (v6) の AST を使うので、 spec 準拠は kdl-rs に委譲されている。
derive を付けると何が起こるか
flowchart LR
A["Rust 構造体\n+ #[derive(KdlDeserialize)]"] --> B["from_str()"]
C["KDL テキスト"] --> B
B --> D["Rust 構造体の値"]
D --> E["to_string_pretty()"]
E --> F["KDL テキスト"]
構造体のフィールドと KDL のノード構造が #[kdl(...)] 属性で対応付けられる。
use ;
この構造体で以下の KDL を読み書きできる:
service "api" image="myapp" {
port host=8080 container=80
port host=8443 container=443
}
// デシリアライズ(KDL → Rust)
let service: Service = from_str.unwrap;
// シリアライズ(Rust → KDL)
let kdl_text = to_string_pretty.unwrap;
属性リファレンス
構造体属性
| 属性 | 説明 |
|---|---|
#[kdl(name = "...")] |
KDL ノード名(省略時は構造体名の snake_case) |
#[kdl(alias = "...")] |
ノード名の別名(複数指定可、デシリアライズ時に受け入れる) |
#[kdl(document)] |
KDL ドキュメント全体(複数トップレベルノード)として扱う |
フィールド属性
| 属性 | 説明 |
|---|---|
#[kdl(argument)] |
位置引数にマッピング(自動インデックス) |
#[kdl(argument(index = N))] |
特定インデックスの引数にマッピング |
#[kdl(arguments)] |
全引数を Vec<T> に収集 |
#[kdl(property)] |
名前付きプロパティ(key=value) |
#[kdl(property(rename = "...")] |
別名のプロパティにマッピング |
#[kdl(child)] |
単一の子ノード(子型の #[kdl(name)] を自動参照) |
#[kdl(child(name = "...")] |
明示名で子ノードを検索 |
#[kdl(child, unwrap_arg)] |
子ノードの第1引数を値として取得 |
#[kdl(child, unwrap_args)] |
子ノードの全引数を Vec<T> として取得 |
#[kdl(children)] |
子ノードを Vec<T> に収集(子型の #[kdl(name)] を自動参照) |
#[kdl(children(name = "...")] |
明示名で子ノードをフィルタして収集 |
#[kdl(child_map)] |
子ノードを HashMap<String, String> に収集 |
#[kdl(child_map(name = "...")] |
ラッパーノード内の子を HashMap に収集 |
#[kdl(flatten)] |
子構造体のフィールドを親ノードに展開 |
#[kdl(default)] |
欠落時に Default::default() を使用 |
#[kdl(skip)] |
シリアライズ / デシリアライズをスキップ |
Enum 属性
| 属性 | 用途 | 説明 |
|---|---|---|
#[kdl(rename = "...")] |
スカラー / データ | バリアント名の KDL 表現(省略時は snake_case) |
Enum サポート
スカラー Enum(プロパティ / 引数の値として使う)
全バリアントが unit(データなし)の enum は、文字列として KDL の引数やプロパティにマッピングされる。
channel "events" from="server"
データ Enum(ノード名でバリアントを判別)
struct / newtype / unit バリアントを含む enum は、KDL ノード名でバリアントを判別する。
move x=10.0 y=20.0
configure key="debug" value="true"
quit
Vec で子ノードを収集
データ enum は #[kdl(children)] と組み合わせて、異なるノード名の子を一括収集できる。
pipeline "deploy" {
move x=1.0 y=2.0
configure key="env" value="prod"
quit
}
子ノードの名前自動解決
#[kdl(child)] / #[kdl(children)] は、子構造体の #[kdl(name = "...")] を自動参照する。
フィールド名と KDL ノード名が異なる場合でも、明示指定なしで正しくマッピングされる。
post-setup "bun install"
子構造体に #[kdl(name)] がない場合はフィールド名にフォールバックする。
エイリアス
構造体に #[kdl(alias = "...")] を付けると、デシリアライズ時に別名も受け入れる。
database "pg://..." でも db "pg://..." でもデシリアライズ可能。
kdl_node_name() は常に primary name("database")を返す。
使い方の例
ドキュメント全体をパースする
KDL ファイルにトップレベルノードが複数ある場合は #[kdl(document)] を使う:
let config: Config = from_str.unwrap;
全引数を収集する
depends_on "db" "redis" "cache"
子ノードマップ
service "api" {
env {
DATABASE_URL "postgres://localhost/db"
API_KEY "secret"
}
}
unwrap_arg / unwrap_args
子ノードの引数だけを値として取得する:
app {
name "my-app"
tags "web" "api"
}
flatten
子構造体のフィールドを親ノードに展開する:
service "api" interval=30 timeout=5
サポートする型
- 整数:
i32,i64,i128,u16,u32,u64,usize - 浮動小数点:
f64 - 真偽値:
bool - 文字列:
String,&str(ゼロコピー) - パス:
PathBuf - コレクション:
Vec<T>,HashMap<String, String> - オプショナル:
Option<T> - カスタム型:
FromKdlValue/ToKdlValueを実装
ガイド
より詳しい使い方は docs/guide/ を参照:
- カスタム型ガイド — 独自型 (chrono 型・newtype など) を KDL 値にマッピング
- KDL 設計ベストプラクティス — argument / property / children の使い分けとアンチパターン
- トラブルシュート — よくあるエラーの原因と対処
ベンチマーク
benches/kdl_vs_json.rs に同等の docker-compose 風データを KDL と JSON で読み書きするマイクロベンチがあります。
実測値 (Apple Silicon, Rust 1.95, criterion 中央値):
| operation | KDL (club-kdl) | JSON (serde_json) | 倍率 |
|---|---|---|---|
| read | 486 µs | 4.2 µs | KDL は ~115x 遅い |
| write | 8.8 µs | 1.7 µs | KDL は ~5x 遅い |
実行:
結果は HTML レポート (target/criterion/report/index.html) で詳細を確認できます。
用途のガイド: KDL は人間可読性に最適化されたフォーマットで、 read は JSON より明確に重いです。 hot-path での頻繁な再パースには JSON / binary フォーマット (rkyv 等) を選び、 設定ファイル / 宣言的 schema / human-edited DSL に club-kdl を使ってください。
MSRV (Minimum Supported Rust Version)
現在の MSRV は Rust 1.94 です。 Cargo.toml の rust-version フィールドで管理し、 CI で継続的に検証しています。
MSRV の引き上げは patch リリースで行うことがあります (semver の慣習に準拠)。
Contributing
CONTRIBUTING.md を参照してください。 セキュリティ問題は SECURITY.md の手順で報告をお願いします。
License
このプロジェクトはあなたの選択により以下のいずれかでライセンスされています:
- Apache License, Version 2.0, (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT)
Contribution
特に明示的に別段の定めがない限り、 あなたが意図的に提出した、 Apache-2.0 ライセンスで定義されている、本作品に含めるためのコントリビューションは、 追加の条項なしに上記のようにデュアルライセンスされるものとします。