Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ChildSpecBuilder

Language: 中文

One-sentence summary

ChildSpecBuilder is the fluent API for constructing ChildSpec values in Rust code. Configuration and RPC should still use ChildDeclaration. The build exit is build() -> Result<ChildSpec, SupervisorError>, which calls ChildSpec::validate() internally.

Relationship to child-spec.md: that page explains how declarations and specs divide responsibility. This page focuses on builder entry points, setters, and common usage patterns.

Module path

#![allow(unused)]
fn main() {
use rust_supervisor::spec::child_builder::ChildSpecBuilder;
}

The module is defined in src/spec/child_builder.rs. Per project module-boundary rules, there is no pub use re-export.

When to use the builder

ScenarioRecommended approach
YAML config, add_child RPC payloadsChildDeclaration + TryFrom
Tests, examples, hand-built runtime specs in codeChildSpecBuilder
Worker default bundle only, no fluent chainChildSpec::worker(...)? (delegates to the builder internally)

Legacy code may still mutate fields after construction. New code should prefer the builder.

Entry methods

MethodPurposeDefault highlights
worker(id, name, kind, factory)Async or blocking workerMatches ChildSpec::worker: Transient restart, Critical criticality, TaskRole::Worker, and so on
service(id, name, kind, factory)Long-running serviceBased on worker defaults: TaskRole::Service, Critical criticality
job(id, name, kind, factory)Finite jobBased on worker defaults: TaskRole::Job, Optional criticality
sidecar(id, name, kind, factory, sidecar_config)Sidecar attached to a primary childBased on worker defaults: TaskRole::Sidecar, writes sidecar_config, and automatically adds the primary child dependency
supervisor(id, name)Nested supervisorkind = Supervisor, factory = None, task_role = Supervisor, criticality = Critical
new(id, name)Minimal skeletonSets only id / name plus baseline policies; caller must add kind and, for workers, factory

Build exit

MethodBehavior
build()Takes the inner ChildSpec, calls validate(), returns Ok(spec) or SupervisorError

All entry methods and setters return ChildSpecBuilder, which means construction is still in progress. Only build() consumes the builder and returns the final ChildSpec.

There is no build_validated(). Validation is always performed inside build().

ChildSpec::worker(...) also returns Result<ChildSpec, SupervisorError> via ChildSpecBuilder::worker(...).build().

Basic usage

#![allow(unused)]
fn main() {
use rust_supervisor::error::types::SupervisorError;
use rust_supervisor::id::types::ChildId;
use rust_supervisor::policy::task_role_defaults::TaskRole;
use rust_supervisor::spec::child::TaskKind;
use rust_supervisor::spec::child_builder::ChildSpecBuilder;
use rust_supervisor::task::factory::{TaskResult, service_fn};
use std::sync::Arc;

fn build_worker() -> Result<ChildSpec, SupervisorError> {
    let factory = Arc::new(service_fn(|_ctx| async { TaskResult::Succeeded }));
    ChildSpecBuilder::worker(
        ChildId::new("invoice-worker"),
        "Invoice Worker",
        TaskKind::AsyncWorker,
        factory,
    )
    .task_role(TaskRole::Worker)
    .tag("invoice")
    .build()
}
}

Propagate errors with ?, or use build().expect("...") in tests.

Fluent setter coverage

Each setter consumes self and returns Self. You can chain them in any order that remains semantically valid.

Policy fields: isolation, restart_policy, shutdown_policy, health_policy, readiness_policy, backoff_policy

Topology and classification: dependencies, dependency, tags, tag, criticality, task_role, without_task_role, sidecar_config, without_sidecar_config, severity, without_severity, group, without_group

Config blocks: health_check, without_health_check, readiness, without_readiness, resource_limits, without_resource_limits, command_permissions, environment, env_var, secrets, secret, cleanup_paths, cleanup_path

Runtime: kind, factory, without_factory (for new() or supervisor paths)

Naming convention: plural fields use dependencies(...), tags(...); singular helpers use dependency(...), tag(...). The same pattern applies to environment / env_var, secrets / secret, and cleanup_paths / cleanup_path.

Common combinations

Service

Long-running services should prefer service(...); callers do not need to set TaskRole::Service by hand:

#![allow(unused)]
fn main() {
ChildSpecBuilder::service(id, "API Service", TaskKind::AsyncWorker, factory)
    .tag("service")
    .build()?;
}

Job

Finite work should prefer job(...). You can still override restart_policy for one-shot behavior:

#![allow(unused)]
fn main() {
ChildSpecBuilder::job(id, "Nightly Export", TaskKind::AsyncWorker, factory)
    .restart_policy(RestartPolicy::Temporary)
    .build()?;
}

Sidecar

Sidecars attached to a primary child should prefer sidecar(...). This entry writes sidecar_config and automatically adds the primary child dependency:

#![allow(unused)]
fn main() {
use rust_supervisor::policy::task_role_defaults::SidecarConfig;

ChildSpecBuilder::sidecar(
    id,
    "Metrics Sidecar",
    TaskKind::AsyncWorker,
    factory,
    SidecarConfig::new(primary_id.clone(), false),
)
.build()?;
}

If you still configure task_role = Sidecar manually with setters, you must also set sidecar_config, or build() validation fails.

Worker from new()

#![allow(unused)]
fn main() {
ChildSpecBuilder::new(ChildId::new("custom"), "custom")
    .kind(TaskKind::AsyncWorker)
    .factory(factory)
    .build()?;
}

Data flow (short)

ChildSpecBuilder::worker / service / job / sidecar / supervisor / new
        |
        v
   fluent setters (policy, role, deps, env, ...)
        |
        v
   build()  -->  ChildSpec::validate()
        |
        +-- Ok(ChildSpec)  -->  Supervisor::start / register topology
        +-- Err(SupervisorError)

Example program

Runnable demo:

cargo run --example child_spec_builder

Source: examples/child_spec_builder.rs. Covers worker, service, job, sidecar, supervisor, the new() path, and an intentionally invalid sidecar combination.

Tests and regression

External tests: src/spec/tests/child_builder_test.rs

TestWhat it verifies
worker_builder_matches_child_spec_worker_defaultsBuilder output matches ChildSpec::worker field-for-field
supervisor_builder_produces_valid_supervisor_childSupervisor entry has no factory and validates
service_builder_sets_service_roleService entry sets TaskRole::Service and Critical criticality
job_builder_sets_job_role_and_optional_criticalityJob entry sets TaskRole::Job and Optional criticality
sidecar_builder_sets_sidecar_role_binding_and_dependencySidecar entry sets the binding and automatically adds the primary child dependency
builder_setters_apply_expected_fieldsSidecar, dependency, tag, and related setters
build_rejects_invalid_sidecar_combinationMissing sidecar_config makes build() fail
new_builder_can_build_valid_worker_with_factorynew() path works after required fields are set

Run:

cargo test --test child_builder_test

Known boundaries

  • Default policy bundles for TryFrom<ChildDeclaration> are not fully shared with the builder yet. The two paths may evolve independently; review both when changing defaults.
  • The builder does not handle serde. Dynamic child adds still flow through ChildDeclaration.
  • Legacy examples and tests were not bulk-migrated from ChildSpec::worker. Both styles are runtime-equivalent when callers handle Result.

Further reading

  • child-spec.md — how ChildDeclaration and ChildSpec relate, plus a short builder introduction
  • docs/architecture.md — module boundaries and the no re-export rule