xianyun_core/config/versions.rs
1//! Port of `internal/config/versions`: the contract for versioned Caproni
2//! configurations that can upgrade themselves toward the latest schema
3//! version.
4
5use thiserror::Error;
6
7#[derive(Debug, Error)]
8pub enum UpgraderError {
9 #[error("upgrade step failed: {0}")]
10 Step(String),
11}
12
13/// A versioned config that upgrades itself one step toward the latest schema
14/// version. The latest version's `upgrade` is a no-op that returns the
15/// receiver unchanged, which is how the upgrade walker detects completion.
16///
17/// Unlike the Go `Upgrade(log *slog.Logger) (Upgrader, error)`, which can
18/// return the exact same pointer to signal termination, Rust's ownership
19/// rules make returning "the same object" from `&self` awkward with a
20/// `Box<dyn Upgrader>` return type. The real implementation will need to
21/// settle this (e.g. via an enum of known versions, or an `is_terminal()`
22/// query) rather than pointer identity; see `PORT_NOTES.md`.
23pub trait Upgrader {
24 fn upgrade(&self) -> Result<Box<dyn Upgrader>, UpgraderError>;
25
26 /// Reports whether this value is already at the latest version, i.e.
27 /// whether `upgrade` would be a no-op. This is the termination signal
28 /// `config::registry::walk` uses instead of the Go original's
29 /// pointer-identity check (`next == cfg`): Rust's `Box<dyn Upgrader>`
30 /// return type has no equivalent "return the same object" signal, so the
31 /// walker asks each value directly rather than comparing addresses.
32 /// Defaults to `false`; only the latest version's `Upgrader` impl
33 /// overrides it.
34 fn is_terminal(&self) -> bool {
35 false
36 }
37
38 /// Exposes `self` as `&dyn Any`, so a caller holding a `Box<dyn
39 /// Upgrader>` at the end of an upgrade walk can downcast back to the
40 /// concrete type it expects — the Rust equivalent of the Go original's
41 /// `upgraded.(*latest.CaproniConfig)` type assertion, since Rust trait
42 /// objects need an explicit escape hatch (there is no built-in upcast
43 /// from `dyn Upgrader` to `dyn Any` without this).
44 fn as_any(&self) -> &dyn std::any::Any;
45}