Versioned
Schema migrations for persisted files.
Public types
| Kind | Name |
|---|---|
trait | Versioned — A persisted struct whose schema is versioned |
enum | MigrationError — Errors surfaced by Migrator::run |
struct | Migrator — Schema migration pipeline for a Versioned type |
Public functions
Versioned
| Returns | Function |
|---|---|
| Methods | |
u32 | version() |
set_version(v: u32) | |
| Constants and types | |
u32 | CURRENT_VERSION |
Migrator
| Returns | Function |
|---|---|
| Constructors | |
Self | new() |
| Builder methods | |
Self | step<F>(from: u32, func: F) |
| Methods | |
Result<T, MigrationError> | run(mut raw: toml::Value) |
Detailed description
Every persisted struct carries a version: u32 (via Versioned).
Migrator<T> holds an ordered set of from_version → from_version + 1
transformations expressed on raw toml::Value — pre-deserialization,
so a v1 file that no longer matches the v2 type can still be upgraded.
use teksilo_settings::{Versioned, Migrator};
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
struct Recents {
version: u32,
items: Vec<Entry>,
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Entry { path: String, pinned: bool }
impl Versioned for Recents {
const CURRENT_VERSION: u32 = 2;
fn version(&self) -> u32 { self.version }
fn set_version(&mut self, v: u32) { self.version = v; }
}
// v1 didn't have `pinned`; supply false.
let migrator: Migrator<Recents> = Migrator::new()
.step(1, |mut v| {
if let Some(items) = v.get_mut("items").and_then(|i| i.as_array_mut()) {
for item in items {
if let Some(t) = item.as_table_mut() {
t.insert("pinned".into(), toml::Value::Boolean(false));
}
}
}
Ok(v)
});
API reference
📖 Full rustdoc API for this module
pub trait Versioned
Import: use teksilo::settings::Versioned;
A persisted struct whose schema is versioned.
CURRENT_VERSION is the version this build of the code reads and
writes. Files on disk may be older — Migrator walks them up.
#![allow(unused)] fn main() { pub trait Versioned { /* associated items below */ } }
Associated items
const CURRENT_VERSION: u32;
The version this build understands. Bump when the schema changes in a way that requires migration.
fn version(&self) -> u32;
The version embedded in this instance.
fn set_version(&mut self, v: u32);
Write a new version into this instance. Used by the migrator after a successful chain of steps.
pub enum MigrationError
Import: use teksilo::settings::MigrationError;
Errors surfaced by Migrator::run.
#![allow(unused)] fn main() { pub enum MigrationError { /* variants */ } }
Variants
NewerThanCurrent{ on_disk: u32, current: u32 }— The on-disk version number exceedsT::CURRENT_VERSION; the file was written by a newer build and cannot be read safely.NoStepFor(u32)— The chain is missing a step for the encountered version, making it impossible to reachT::CURRENT_VERSION.Step{ from: u32, message: String }— A migration step closure returnedErr(message).Deserialize(toml::de::Error)— The migratedtoml::Valuedid not deserialize asT.
pub struct Migrator
Import: use teksilo::settings::Migrator;
Schema migration pipeline for a Versioned type.
Add from → from + 1 steps with Migrator::step; the order in which
they're added does not matter — Migrator::run walks them in
version order.
Migrator<T> is cheaply Clone (each step's closure lives behind an
Arc, so cloning is a handful of refcount bumps, not a deep copy) and
Send + Sync whenever T is — which is what lets a Patch
(crate::flush::Patch) closure retain its own copy of the migrator and re-run it against the
document read fresh on the shared I/O worker thread, instead of the
stale, possibly-out-of-date value this handle loaded at construction.
#![allow(unused)] fn main() { pub struct Migrator<T: Versioned + DeserializeOwned> { /* fields */ } }
Methods
pub fn new() -> Self
Create an empty migrator with no steps registered.
If T::CURRENT_VERSION is 1 (the initial schema) or the file
is already at the current version, no steps are needed and
run will succeed immediately.
pub fn step<F>(mut self, from: u32, func: F) -> Self where F: Fn(toml::Value) -> Result<toml::Value, String> + Send + Sync + 'static,
Register a step that promotes a value from from to from + 1.
Steps may be registered in any order; run finds
the right one for the current version on demand.
pub fn run(&self, mut raw: toml::Value) -> Result<T, MigrationError>
Migrate raw from its on-disk version up to
T::CURRENT_VERSION, then deserialize.
Reads the version directly from the version field of the raw
toml::Value — never deserializes-then-checks, because a v1
payload typically fails to deserialize as the v2 type.
Files missing the version field are treated as v1 (legacy).