Skip to content

Commit 39dc03f

Browse files
author
developerworks
committed
添加配置 Schema 与 IDE 补全支持
- 生成 Draft 7 JSON Schema 并新增 config-schema 子命令 - 为 TOML/YAML 模板写入 schema 绑定指令 - 同步 examples、README 与中英文手册
1 parent 16e47b0 commit 39dc03f

24 files changed

Lines changed: 868 additions & 42 deletions

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ clap_complete = "4.6.3"
1717
confique = { version = "0.4.0", features = ["json5", "toml", "yaml"] }
1818
dotenvy = "0.15.7"
1919
figment = { version = "0.10.19", features = ["env", "json", "toml", "yaml"] }
20+
schemars = { version = "1", features = ["derive"] }
2021
serde_json = "1.0.149"
2122
tracing = "0.1.44"
2223

README.md

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ It handles:
1414
- loading a `confique` schema into a directly usable config object through
1515
Figment runtime providers
1616
- `config-template`, `completions`, and `install-completions` command handlers
17+
- Draft 7 JSON Schema generation for editor completion and validation
1718
- config template generation for YAML, TOML, JSON, and JSON5
19+
- schema directives for TOML and YAML templates without adding runtime fields
1820
- recursive include traversal
1921
- `.env` loading before environment values are merged
2022
- source tracking through Figment metadata
@@ -36,6 +38,7 @@ implementing `ConfigSchema` to expose the schema's include field.
3638
rust-config-tree = "0.1"
3739
confique = { version = "0.4", features = ["yaml", "toml", "json5"] }
3840
figment = { version = "0.10", features = ["yaml", "env"] }
41+
schemars = { version = "1", features = ["derive"] }
3942
serde = { version = "1", features = ["derive"] }
4043
clap = { version = "4", features = ["derive"] }
4144
```
@@ -198,6 +201,19 @@ output format is inferred from the output path:
198201
- `.json` and `.json5` generate JSON5-compatible templates
199202
- unknown or missing extensions generate YAML
200203

204+
Use `write_config_schema` to create one Draft 7 JSON Schema that can be shared
205+
by TOML, YAML, and JSON configuration files:
206+
207+
```rust
208+
use rust_config_tree::write_config_schema;
209+
210+
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
211+
write_config_schema::<AppConfig>("schemas/myapp.schema.json")?;
212+
213+
Ok(())
214+
}
215+
```
216+
201217
Use `write_config_templates` to create a root template and every template file
202218
reachable from its include tree:
203219

@@ -211,6 +227,28 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
211227
}
212228
```
213229

230+
Use `write_config_templates_with_schema` when generated TOML and YAML templates
231+
should bind that schema for IDE completion and validation:
232+
233+
```rust
234+
use rust_config_tree::write_config_templates_with_schema;
235+
236+
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
237+
write_config_templates_with_schema::<AppConfig>(
238+
"config.toml",
239+
"config.example.toml",
240+
"schemas/myapp.schema.json",
241+
)?;
242+
243+
Ok(())
244+
}
245+
```
246+
247+
TOML targets receive `#:schema ./schemas/myapp.schema.json`. YAML targets
248+
receive `# yaml-language-server: $schema=./schemas/myapp.schema.json`. JSON and
249+
JSON5 targets intentionally do not receive a `$schema` field; bind them with
250+
editor settings such as VS Code `json.schemas`.
251+
214252
Template generation chooses its source tree in this order:
215253

216254
- an existing config path
@@ -262,6 +300,7 @@ sections. Nested children are placed under their parent file stem, for example
262300
Flatten `ConfigCommand` into your existing clap command enum to add:
263301

264302
- `config-template`
303+
- `config-schema`
265304
- `completions`
266305
- `install-completions`
267306

@@ -285,9 +324,10 @@ use std::path::PathBuf;
285324
286325
use clap::{Parser, Subcommand};
287326
use confique::Config;
327+
use schemars::JsonSchema;
288328
use rust_config_tree::{ConfigCommand, ConfigSchema, handle_config_command, load_config};
289329
290-
#[derive(Debug, Config)]
330+
#[derive(Debug, Config, JsonSchema)]
291331
struct AppConfig {
292332
#[config(default = [])]
293333
include: Vec<PathBuf>,
@@ -337,7 +377,11 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
337377

338378
`config-template --output <path>` writes templates to the selected path. If no
339379
output path is provided, it writes `config.example.yaml` in the current
340-
directory.
380+
directory. Add `--schema <path>` to bind TOML and YAML templates to a generated
381+
JSON Schema without adding a runtime `$schema` field.
382+
383+
`config-schema --output <path>` writes a Draft 7 JSON Schema. If no output path
384+
is provided, it writes `schemas/config.schema.json`.
341385

342386
`completions <shell>` prints completions to stdout.
343387

README.zh.md

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
- 通过 Figment runtime provider 将 `confique` schema 加载成可直接使用的
1414
config 对象
1515
- `config-template``completions``install-completions` 命令处理
16+
- 生成 Draft 7 JSON Schema,供编辑器补全和校验使用
1617
- YAML、TOML、JSON 和 JSON5 配置模板生成
18+
- 为 TOML 和 YAML 模板生成 schema directive,不写入运行时字段
1719
- 递归 include 遍历
1820
- 合并环境变量前加载 `.env`
1921
- 通过 Figment metadata 追踪配置来源
@@ -35,6 +37,7 @@
3537
rust-config-tree = "0.1"
3638
confique = { version = "0.4", features = ["yaml", "toml", "json5"] }
3739
figment = { version = "0.10", features = ["yaml", "env"] }
40+
schemars = { version = "1", features = ["derive"] }
3841
serde = { version = "1", features = ["derive"] }
3942
clap = { version = "4", features = ["derive"] }
4043
```
@@ -168,6 +171,19 @@ command-line overrides
168171
- `.json` 和 `.json5` 生成 JSON5-compatible 模板
169172
- 未知或缺失扩展名生成 YAML
170173

174+
使用 `write_config_schema` 生成一份 Draft 7 JSON Schema,TOML、YAML 和
175+
JSON 配置文件可以共用它:
176+
177+
```rust
178+
use rust_config_tree::write_config_schema;
179+
180+
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
181+
write_config_schema::<AppConfig>("schemas/myapp.schema.json")?;
182+
183+
Ok(())
184+
}
185+
```
186+
171187
使用 `write_config_templates` 创建 root 模板和 include tree 中的子模板:
172188

173189
```rust
@@ -180,6 +196,28 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
180196
}
181197
```
182198

199+
如果生成的 TOML 和 YAML 模板需要绑定 schema,用
200+
`write_config_templates_with_schema`
201+
202+
```rust
203+
use rust_config_tree::write_config_templates_with_schema;
204+
205+
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
206+
write_config_templates_with_schema::<AppConfig>(
207+
"config.toml",
208+
"config.example.toml",
209+
"schemas/myapp.schema.json",
210+
)?;
211+
212+
Ok(())
213+
}
214+
```
215+
216+
TOML 目标会写入 `#:schema ./schemas/myapp.schema.json`。YAML 目标会写入
217+
`# yaml-language-server: $schema=./schemas/myapp.schema.json`。JSON 和 JSON5
218+
目标不会写 `$schema` 字段;这类文件应通过 VS Code `json.schemas` 等编辑器
219+
设置绑定。
220+
183221
模板生成按这个顺序选择 source tree:
184222

185223
- 已存在的 config path
@@ -192,6 +230,7 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
192230
`rust-config-tree` 只提供可复用的 `ConfigCommand` 子命令:
193231

194232
- `config-template`
233+
- `config-schema`
195234
- `completions`
196235
- `install-completions`
197236

@@ -213,9 +252,10 @@ use std::path::PathBuf;
213252
214253
use clap::{Parser, Subcommand};
215254
use confique::Config;
255+
use schemars::JsonSchema;
216256
use rust_config_tree::{ConfigCommand, ConfigSchema, handle_config_command, load_config};
217257
218-
#[derive(Debug, Config)]
258+
#[derive(Debug, Config, JsonSchema)]
219259
struct AppConfig {
220260
#[config(default = [])]
221261
include: Vec<PathBuf>,
@@ -264,7 +304,11 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
264304
```
265305

266306
`config-template --output <path>` 将模板写入指定路径。未提供 output path 时,
267-
写入当前目录下的 `config.example.yaml`。
307+
写入当前目录下的 `config.example.yaml`。添加 `--schema <path>` 后,TOML 和
308+
YAML 模板会绑定生成的 JSON Schema,但不会加入运行时 `$schema` 字段。
309+
310+
`config-schema --output <path>` 写入 Draft 7 JSON Schema。未提供 output path
311+
时,写入 `schemas/config.schema.json`。
268312

269313
`completions <shell>` 将 completions 输出到 stdout。
270314

examples/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Run them from the repository root:
99
cargo run --example basic_loading
1010
cargo run --example cli_overrides -- --server-port 9000
1111
cargo run --example config_commands -- config-template --output /tmp/config.example.yaml
12+
cargo run --example config_commands -- config-schema --output /tmp/myapp.schema.json
1213
cargo run --example generate_templates
1314
cargo run --example tree_api
1415
```
@@ -19,5 +20,6 @@ The examples cover:
1920
- `cli_overrides.rs`: merge application CLI flags as the highest-priority
2021
Figment provider.
2122
- `config_commands.rs`: flatten `ConfigCommand` into an application clap CLI.
22-
- `generate_templates.rs`: write split config templates from a schema.
23+
- `generate_templates.rs`: write one JSON Schema and schema-bound TOML/YAML
24+
templates from a schema.
2325
- `tree_api.rs`: use the lower-level, format-agnostic include tree API.

examples/config_commands.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::{
77
use clap::{Parser, Subcommand};
88
use confique::Config;
99
use rust_config_tree::{ConfigCommand, ConfigSchema, handle_config_command, load_config};
10+
use schemars::JsonSchema;
1011

1112
#[derive(Debug, Parser)]
1213
#[command(name = "config-commands")]
@@ -26,7 +27,7 @@ enum Command {
2627
Config(ConfigCommand),
2728
}
2829

29-
#[derive(Debug, Config)]
30+
#[derive(Debug, Config, JsonSchema)]
3031
struct AppConfig {
3132
#[config(default = [])]
3233
include: Vec<PathBuf>,
@@ -38,7 +39,7 @@ struct AppConfig {
3839
server: ServerConfig,
3940
}
4041

41-
#[derive(Debug, Config)]
42+
#[derive(Debug, Config, JsonSchema)]
4243
struct ServerConfig {
4344
#[config(default = "127.0.0.1")]
4445
bind: String,

examples/generate_templates.rs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ use std::{
55
};
66

77
use confique::Config;
8-
use rust_config_tree::{ConfigSchema, write_config_templates};
8+
use rust_config_tree::{ConfigSchema, write_config_schema, write_config_templates_with_schema};
9+
use schemars::JsonSchema;
910

10-
#[derive(Debug, Config)]
11+
#[derive(Debug, Config, JsonSchema)]
1112
#[allow(dead_code)]
1213
struct AppConfig {
1314
#[config(default = [])]
@@ -23,7 +24,7 @@ struct AppConfig {
2324
log: LogConfig,
2425
}
2526

26-
#[derive(Debug, Config)]
27+
#[derive(Debug, Config, JsonSchema)]
2728
#[allow(dead_code)]
2829
struct ServerConfig {
2930
/// HTTP bind address.
@@ -37,7 +38,7 @@ struct ServerConfig {
3738
port: u16,
3839
}
3940

40-
#[derive(Debug, Config)]
41+
#[derive(Debug, Config, JsonSchema)]
4142
#[allow(dead_code)]
4243
struct DatabaseConfig {
4344
/// Database URL.
@@ -50,7 +51,7 @@ struct DatabaseConfig {
5051
pool_size: u32,
5152
}
5253

53-
#[derive(Debug, Config)]
54+
#[derive(Debug, Config, JsonSchema)]
5455
#[allow(dead_code)]
5556
struct LogConfig {
5657
/// Log level.
@@ -68,11 +69,22 @@ impl ConfigSchema for AppConfig {
6869
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
6970
let dir = temp_example_dir("generate-templates")?;
7071
let config_path = dir.join("config.yaml");
71-
let output_path = dir.join("config.example.yaml");
72-
73-
write_config_templates::<AppConfig>(&config_path, &output_path)?;
72+
let schema_path = dir.join("schemas").join("myapp.schema.json");
73+
74+
write_config_schema::<AppConfig>(&schema_path)?;
75+
for file_name in [
76+
"config.example.toml",
77+
"config.example.yaml",
78+
"config.example.json",
79+
] {
80+
write_config_templates_with_schema::<AppConfig>(
81+
&config_path,
82+
dir.join(file_name),
83+
&schema_path,
84+
)?;
85+
}
7486

75-
println!("template root: {}", output_path.display());
87+
println!("schema: {}", schema_path.display());
7688
for path in generated_files(&dir)? {
7789
println!("{}", path.display());
7890
}

manual/en/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- [Environment Variables](environment.md)
99
- [Source Tracking](source-tracking.md)
1010
- [Template Generation](templates.md)
11+
- [IDE Completions](ide-completions.md)
1112
- [CLI Integration](cli.md)
1213
- [Tree API](tree-api.md)
1314
- [GitHub Pages](github-pages.md)

manual/en/cli.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
`ConfigCommand` provides reusable clap subcommands:
66

77
- `config-template`
8+
- `config-schema`
89
- `completions`
910
- `install-completions`
1011

@@ -31,9 +32,10 @@ use std::path::PathBuf;
3132

3233
use clap::{Parser, Subcommand};
3334
use confique::Config;
35+
use schemars::JsonSchema;
3436
use rust_config_tree::{ConfigCommand, ConfigSchema, handle_config_command, load_config};
3537

36-
#[derive(Debug, Config)]
38+
#[derive(Debug, Config, JsonSchema)]
3739
struct AppConfig {
3840
#[config(default = [])]
3941
include: Vec<PathBuf>,
@@ -87,7 +89,18 @@ demo config-template --output config.example.yaml
8789
```
8890

8991
If no output path is provided, the command writes `config.example.yaml` in the
90-
current directory.
92+
current directory. Add `--schema schemas/myapp.schema.json` to bind generated
93+
TOML and YAML templates to the JSON Schema.
94+
95+
```bash
96+
demo config-template --output config.example.toml --schema schemas/myapp.schema.json
97+
```
98+
99+
Generate the shared JSON Schema:
100+
101+
```bash
102+
demo config-schema --output schemas/myapp.schema.json
103+
```
91104

92105
## Shell Completions
93106

0 commit comments

Comments
 (0)