Skip to content

Commit 9848d15

Browse files
author
developerworks
committed
Add CLI generate-template command with factory-key schema enrichment
- Add `generate-template` subcommand to supervisor CLI - Implement `supervisor_schema_targets_with_factory_registry` for split-section schema generation - Wire factory registry into schema generation for dynamic factory-key completions - Add factory-key documentation pages for en/zh manuals - Register new pages in en/zh SUMMARY.md
1 parent 380792f commit 9848d15

8 files changed

Lines changed: 631 additions & 23 deletions

File tree

manual/en/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- [Getting Started](getting-started.md)
66
- [Configuration and Schema](configuration.md)
77
- [Split Configuration and Transparent Array Sections](split-config.md)
8+
- [factory_key Configuration](factory-key.md)
89
- [Supervisor Tree](supervisor-tree.md)
910
- [Task Model](task-model.md)
1011
- [ChildSpec and ChildDeclaration](child-spec.md)

manual/en/factory-key.md

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# factory_key Configuration
2+
3+
Language: [中文](../zh/factory-key.html)
4+
5+
## 1. Summary
6+
7+
`factory_key` is a task factory key in YAML configuration. Its value is the name shared by the configuration file and Rust code, such as `api_server`. It connects a declarative worker child to a `TaskFactory` registered in Rust code.
8+
9+
The configuration file stores only declarations. It does not store executable closures. The real task startup logic must be supplied by Rust code.
10+
11+
## 2. Problem
12+
13+
A Supervisor task tree can declare children through configuration files. However, `async_worker` and `blocking_worker` children need an executable `TaskFactory` when they actually start. A `TaskFactory` contains Rust code and usually a closure, so it cannot be safely stored directly in YAML.
14+
15+
`factory_key` defines the boundary. The configuration file writes an agreed key, and Rust code registers a task factory under the same key. Before startup, the system binds the declaration to the executable factory.
16+
17+
## 3. Configuration
18+
19+
`children.yaml` can declare workers like this:
20+
21+
```yaml
22+
- name: api
23+
kind: async_worker
24+
factory_key: api_server
25+
26+
- name: exporter
27+
kind: blocking_worker
28+
factory_key: report_exporter
29+
```
30+
31+
`api_server` and `report_exporter` are not function names. They are configuration-level task factory keys. Rust code must register matching `TaskFactory` values.
32+
33+
## 4. Rust Registration
34+
35+
Rust code uses `TaskFactoryRegistry` to map keys to `TaskFactory` values.
36+
37+
```rust
38+
use rust_supervisor::spec::child::TaskKind;
39+
use rust_supervisor::task::factory::{TaskResult, service_fn};
40+
use rust_supervisor::task::factory_registry::{
41+
TaskFactoryDescriptor, TaskFactoryRegistry,
42+
};
43+
use std::sync::Arc;
44+
45+
let mut registry = TaskFactoryRegistry::new();
46+
47+
registry.register(TaskFactoryDescriptor::new(
48+
"api_server",
49+
"API Server",
50+
"Runs the API service.",
51+
[TaskKind::AsyncWorker],
52+
Arc::new(service_fn(|_ctx| async { TaskResult::Succeeded })),
53+
))?;
54+
55+
registry.register(TaskFactoryDescriptor::new(
56+
"report_exporter",
57+
"Report Exporter",
58+
"Runs blocking export work.",
59+
[TaskKind::BlockingWorker],
60+
Arc::new(service_fn(|_ctx| async { TaskResult::Succeeded })),
61+
))?;
62+
```
63+
64+
`TaskFactoryDescriptor` stores 3 kinds of data:
65+
66+
- `key`: The task factory key used by configuration files.
67+
- `title` and `description`: Metadata shown by schema-backed editor completion.
68+
- `allowed_kinds`: The task kinds that may use this factory, such as `TaskKind::AsyncWorker` or `TaskKind::BlockingWorker`.
69+
70+
## 5. Startup Binding
71+
72+
After configuration loading, `factory_key` is still only a string. Before startup, the string must be resolved to a real `TaskFactory`.
73+
74+
The current binding path is:
75+
76+
1. `ConfigState` reads child declarations from YAML.
77+
2. `to_supervisor_spec_with_factories` uses `TaskFactoryRegistry` to bind workers.
78+
3. `bind_task_factories` checks every worker's `factory_key`.
79+
4. The registry resolves the matching `TaskFactory` and writes it into `ChildSpec`.
80+
5. `Supervisor` starts with already-bound executable task factories.
81+
82+
Binding rules:
83+
84+
- Worker children must declare `factory_key`.
85+
- Supervisor child nodes must not declare `factory_key`.
86+
- An unknown `factory_key` causes a configuration error.
87+
- A factory that does not support the current `TaskKind` causes a configuration error.
88+
89+
## 6. Completion Generation
90+
91+
Editor completion depends on JSON Schema. The current implementation does not rewrite the rust-config-tree schema generator. Instead, it post-processes the base schema generated by rust-config-tree.
92+
93+
The flow is:
94+
95+
1. `generate-template` or `generate-schema` asks rust-config-tree to generate the base schema.
96+
2. `supervisor_schema_targets_with_factory_registry` receives the root schema and split-section schemas.
97+
3. Each schema is parsed into `serde_json::Value`.
98+
4. `inject_factory_key_completions_if_present` finds the `factory_key` field.
99+
5. The system writes keys from `TaskFactoryRegistry` into `oneOf`.
100+
6. The schema is serialized again and written to the target file.
101+
102+
After generation, `children.schema.json` contains a `factory_key` field like this:
103+
104+
```json
105+
{
106+
"factory_key": {
107+
"description": "TaskFactory registry key used to bind worker children before startup.",
108+
"oneOf": [
109+
{
110+
"const": "api_server",
111+
"description": "Runs the API service.",
112+
"title": "API Server"
113+
},
114+
{
115+
"const": "report_exporter",
116+
"description": "Runs blocking export work.",
117+
"title": "Report Exporter"
118+
}
119+
],
120+
"type": [
121+
"string",
122+
"null"
123+
]
124+
}
125+
}
126+
```
127+
128+
When an editor reads the yaml-language-server schema directive at the top of `children.yaml`, it can offer `factory_key` candidates.
129+
130+
## 7. Commands
131+
132+
Generate templates:
133+
134+
```bash
135+
target/debug/rust-tokio-supervisor generate-template
136+
```
137+
138+
This command writes configuration templates and schemas with completion metadata.
139+
140+
Generate schemas only:
141+
142+
```bash
143+
target/debug/rust-tokio-supervisor generate-schema
144+
```
145+
146+
This command writes schemas only, and the generated schema also contains `factory_key` candidates in `oneOf`.
147+
148+
## 8. Current Boundaries
149+
150+
- `factory_key` is a configuration declaration, not executable code.
151+
- Completion candidates come from the `TaskFactoryRegistry` used by the command.
152+
- If Rust code does not register a key, a configuration file using that key cannot start.
153+
- Schema-backed completion helps editors suggest valid candidates, but it does not replace startup binding validation.
154+
- Runtime child addition goes through the same kind of binding validation, so dynamic additions cannot bypass the registry.

manual/zh/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- [快速开始](getting-started.md)
66
- [配置模型和结构模式](configuration.md)
77
- [拆分配置与透明数组 Section](split-config.md)
8+
- [factory_key 配置说明](factory-key.md)
89
- [监督树](supervisor-tree.md)
910
- [任务模型](task-model.md)
1011
- [ChildSpec 与 ChildDeclaration](child-spec.md)

manual/zh/factory-key.md

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# factory_key 配置说明
2+
3+
语言: [English](../en/factory-key.html)
4+
5+
## 1. 结论
6+
7+
`factory_key` 是 YAML(配置文件格式) 里的任务工厂 key(键). 它的值是配置文件和 Rust(系统编程语言) 代码约定的名字, 例如 `api_server`. 它把配置文件中的 worker(工作任务) 声明连接到 Rust(系统编程语言) 代码里注册的 `TaskFactory`(任务工厂).
8+
9+
配置文件只保存声明, 不保存可执行 closure(闭包). 真正的任务启动逻辑必须由 Rust(系统编程语言) 代码提供.
10+
11+
## 2. 解决的问题
12+
13+
Supervisor(监督器) 任务树可以通过配置文件声明子任务. 但是 `async_worker`(异步工作任务) 和 `blocking_worker`(阻塞工作任务) 在真正启动时需要可执行的 `TaskFactory`(任务工厂). `TaskFactory`(任务工厂) 本质上包含 Rust(系统编程语言) 代码和 closure(闭包), 不能安全地直接写入 YAML(配置文件格式).
14+
15+
`factory_key` 解决这个边界问题. 配置文件写一个约定好的 key(键), Rust(系统编程语言) 代码用同一个 key(键) 注册任务工厂. 启动前, 系统把二者绑定起来.
16+
17+
## 3. 配置写法
18+
19+
`children.yaml` 可以这样声明 worker(工作任务):
20+
21+
```yaml
22+
- name: api
23+
kind: async_worker
24+
factory_key: api_server
25+
26+
- name: exporter
27+
kind: blocking_worker
28+
factory_key: report_exporter
29+
```
30+
31+
这里的 `api_server` 和 `report_exporter` 不是函数名. 它们是配置层任务工厂 key(键). Rust(系统编程语言) 代码必须注册同名 `TaskFactory`(任务工厂).
32+
33+
## 4. Rust 代码侧注册流程
34+
35+
Rust(系统编程语言) 代码通过 `TaskFactoryRegistry`(任务工厂注册表) 建立 key(键) 到 `TaskFactory`(任务工厂) 的映射.
36+
37+
```rust
38+
use rust_supervisor::spec::child::TaskKind;
39+
use rust_supervisor::task::factory::{TaskResult, service_fn};
40+
use rust_supervisor::task::factory_registry::{
41+
TaskFactoryDescriptor, TaskFactoryRegistry,
42+
};
43+
use std::sync::Arc;
44+
45+
let mut registry = TaskFactoryRegistry::new();
46+
47+
registry.register(TaskFactoryDescriptor::new(
48+
"api_server",
49+
"API Server",
50+
"Runs the API service.",
51+
[TaskKind::AsyncWorker],
52+
Arc::new(service_fn(|_ctx| async { TaskResult::Succeeded })),
53+
))?;
54+
55+
registry.register(TaskFactoryDescriptor::new(
56+
"report_exporter",
57+
"Report Exporter",
58+
"Runs blocking export work.",
59+
[TaskKind::BlockingWorker],
60+
Arc::new(service_fn(|_ctx| async { TaskResult::Succeeded })),
61+
))?;
62+
```
63+
64+
`TaskFactoryDescriptor`(任务工厂描述符) 同时保存 3 类信息:
65+
66+
- `key`: 配置文件使用的任务工厂 key(键).
67+
- `title` 和 `description`: Schema(结构定义) 自动补全时展示给编辑器的说明.
68+
- `allowed_kinds`: 允许使用这个工厂的任务类型, 例如 `TaskKind::AsyncWorker` 或 `TaskKind::BlockingWorker`.
69+
70+
## 5. 启动时绑定流程
71+
72+
配置加载后, `factory_key` 仍然只是字符串. 启动前需要把字符串解析为真正的 `TaskFactory`(任务工厂).
73+
74+
当前绑定路径是:
75+
76+
1. `ConfigState`(配置状态) 读取 YAML(配置文件格式) 子任务声明.
77+
2. `to_supervisor_spec_with_factories` 使用 `TaskFactoryRegistry`(任务工厂注册表) 绑定 worker(工作任务).
78+
3. `bind_task_factories` 检查每个 worker(工作任务) 的 `factory_key`.
79+
4. 注册表找到对应 `TaskFactory`(任务工厂) 后, 把它写入 `ChildSpec`(子任务规格).
80+
5. `Supervisor`(监督器) 启动时只看到已经绑定好的可执行任务工厂.
81+
82+
绑定规则是:
83+
84+
- worker(工作任务) 必须声明 `factory_key`.
85+
- Supervisor(监督器) 子节点不能声明 `factory_key`.
86+
- 未注册的 `factory_key` 会导致配置错误.
87+
- 工厂不支持当前 `TaskKind`(任务类型) 时会导致配置错误.
88+
89+
## 6. 自动补全生成流程
90+
91+
自动补全依赖 JSON Schema(JSON 结构定义). 当前实现不是重写 rust-config-tree(配置树库) 的 Schema(结构定义) 生成逻辑, 而是在基础 Schema(结构定义) 生成之后做后处理.
92+
93+
流程是:
94+
95+
1. `generate-template` 或 `generate-schema` 调用 rust-config-tree(配置树库) 生成基础 Schema(结构定义).
96+
2. `supervisor_schema_targets_with_factory_registry` 取得 root schema(根结构定义) 和 split-section schema(拆分配置段结构定义).
97+
3. 每个 Schema(结构定义) 先解析成 `serde_json::Value`(JSON 值).
98+
4. `inject_factory_key_completions_if_present` 查找 `factory_key` 字段.
99+
5. 系统把 `TaskFactoryRegistry`(任务工厂注册表) 中的 key(键) 写入 `oneOf`(枚举补全候选).
100+
6. Schema(结构定义) 被重新序列化并写入目标文件.
101+
102+
生成后, `children.schema.json` 的 `factory_key` 字段会包含类似内容:
103+
104+
```json
105+
{
106+
"factory_key": {
107+
"description": "TaskFactory registry key used to bind worker children before startup.",
108+
"oneOf": [
109+
{
110+
"const": "api_server",
111+
"description": "Runs the API service.",
112+
"title": "API Server"
113+
},
114+
{
115+
"const": "report_exporter",
116+
"description": "Runs blocking export work.",
117+
"title": "Report Exporter"
118+
}
119+
],
120+
"type": [
121+
"string",
122+
"null"
123+
]
124+
}
125+
}
126+
```
127+
128+
编辑器读取 `children.yaml` 顶部的 yaml-language-server(YAML 语言服务器) Schema(结构定义) 指令后, 可以对 `factory_key` 给出候选值.
129+
130+
## 7. 命令行为
131+
132+
执行模板生成:
133+
134+
```bash
135+
target/debug/rust-tokio-supervisor generate-template
136+
```
137+
138+
这个命令会生成配置模板, 同时生成带补全信息的 Schema(结构定义).
139+
140+
执行 Schema(结构定义) 生成:
141+
142+
```bash
143+
target/debug/rust-tokio-supervisor generate-schema
144+
```
145+
146+
这个命令只生成 Schema(结构定义), 也会包含 `factory_key` 的 `oneOf`(枚举补全候选).
147+
148+
## 8. 当前边界
149+
150+
- `factory_key` 只是配置层声明, 不是可执行代码.
151+
- 自动补全候选来自当前命令使用的 `TaskFactoryRegistry`(任务工厂注册表).
152+
- 如果 Rust(系统编程语言) 代码没有注册某个 key(键), 配置文件写这个 key(键) 也无法启动.
153+
- Schema(结构定义) 自动补全只能帮助编辑器提示合法候选, 不能替代启动前绑定校验.
154+
- 运行时添加子任务也会经过同一类绑定校验, 避免配置绕过注册表.

0 commit comments

Comments
 (0)