Skip to main content

rust_ef/migration/
snapshot.rs

1//! Snapshot helpers for the automated upgrade workflow.
2//!
3//! `rust-ef migration add` needs a *current* model snapshot file to diff
4//! against the baseline stored in the migrations directory. Because Rust has no
5//! runtime reflection, the CLI cannot read the application's registered
6//! entities; the application must render the snapshot itself (this module) and
7//! then invoke the CLI.
8
9use super::dialect::MigrationDialect;
10use super::engine::MigrationEngine;
11use super::types::ModelSnapshot;
12use crate::error::{EFError, EFResult};
13use crate::metadata_cache::MetadataCache;
14use crate::model_builder::ModelBuilder;
15use std::path::Path;
16
17/// Builds a [`ModelSnapshot`] from all inventory-registered entities and
18/// `#[entity(T)]` configurations for `context_key`.
19///
20/// Call this from a small binary/test in the application (e.g. a `snapshot`
21/// subcommand), write it with [`write_snapshot`], then run
22/// `rust-ef migration add --snapshot <file> --dir Migrations`.
23pub fn build_registered_snapshot(
24    context_key: Option<&str>,
25    dialect: MigrationDialect,
26) -> EFResult<ModelSnapshot> {
27    let built = MetadataCache::build(context_key);
28    let builder = ModelBuilder::from_built(&built);
29    let metas = builder.build();
30    if metas.is_empty() {
31        return Err(EFError::configuration(
32            "No entity types registered. Derive EntityType on the application entities \
33             (and apply #[entity(T)] configurations) before building a snapshot.",
34        ));
35    }
36    Ok(MigrationEngine::new(dialect).create_snapshot("__snapshot__", &metas))
37}
38
39/// Writes a snapshot to an explicit path (pretty JSON).
40pub fn write_snapshot(path: impl AsRef<Path>, snapshot: &ModelSnapshot) -> EFResult<()> {
41    let json = super::snapshot_io::snapshot_to_json(snapshot);
42    if let Some(parent) = path.as_ref().parent() {
43        if !parent.as_os_str().is_empty() {
44            std::fs::create_dir_all(parent).map_err(|e| {
45                EFError::migration(format!("Cannot create {}: {e}", parent.display()))
46            })?;
47        }
48    }
49    std::fs::write(path.as_ref(), json)
50        .map_err(|e| EFError::migration(format!("Cannot write snapshot: {e}")))
51}
52
53/// Builds the registered snapshot and writes it to `path`.
54pub fn save_registered_snapshot(
55    path: impl AsRef<Path>,
56    context_key: Option<&str>,
57    dialect: MigrationDialect,
58) -> EFResult<ModelSnapshot> {
59    let snapshot = build_registered_snapshot(context_key, dialect)?;
60    write_snapshot(path, &snapshot)?;
61    Ok(snapshot)
62}