|
| 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. |
0 commit comments