springtime_migrate_refinery/
config.rs

1//! Migration configuration is based on injecting an [MigrationConfigProvider], which can later be
2//! used to retrieve [MigrationConfig].
3//!
4//! By default, the config is created with opinionated default values, which can then be overwritten
5//! by values from `springtime.json` file under the `migration` key.
6
7use config::{Config, File};
8use serde::Deserialize;
9use springtime::config::CONFIG_FILE;
10use springtime::future::{BoxFuture, FutureExt};
11use springtime_di::component_registry::conditional::unregistered_component;
12use springtime_di::instance_provider::ErrorPtr;
13use springtime_di::{component_alias, injectable, Component};
14use std::sync::Arc;
15
16/// A [Deserialize] version of [Target](refinery_core::Target).
17#[derive(Clone, Copy, Debug, Deserialize)]
18pub enum Target {
19    /// Latest version.
20    Latest,
21    /// User-provided version.
22    Version(u32),
23    /// Don't run migrations, just update the migration table to latest version.
24    Fake,
25    /// Don't run migrations, just update the migration table to user-provided version.
26    FakeVersion(u32),
27}
28
29impl From<Target> for refinery_core::Target {
30    fn from(value: Target) -> Self {
31        match value {
32            Target::Latest => refinery_core::Target::Latest,
33            Target::Version(version) => refinery_core::Target::Version(version),
34            Target::Fake => refinery_core::Target::Fake,
35            Target::FakeVersion(version) => refinery_core::Target::FakeVersion(version),
36        }
37    }
38}
39
40/// Migration configuration.
41#[non_exhaustive]
42#[derive(Clone, Debug, Deserialize)]
43#[serde(default)]
44pub struct MigrationConfig {
45    /// Should migrations run on application start.
46    pub run_migrations_on_start: bool,
47    /// The target version up to which migrate.
48    pub target: Target,
49    /// Group migrations in a single transaction.
50    pub grouped: bool,
51    /// Should abort migration process if divergent migrations are found i.e. applied migrations
52    /// with the same version but different name or checksum from the ones on the filesystem.
53    pub abort_divergent: bool,
54    /// Should abort if missing migrations are found i.e. applied migrations that are not found on
55    /// the filesystem, or migrations found on filesystem with a version inferior to the last one
56    /// applied but not applied
57    pub abort_missing: bool,
58    /// Table name for migration data.
59    pub migration_table_name: String,
60}
61
62impl Default for MigrationConfig {
63    fn default() -> Self {
64        Self {
65            run_migrations_on_start: true,
66            target: Target::Latest,
67            grouped: false,
68            abort_divergent: true,
69            abort_missing: true,
70            migration_table_name: "refinery_schema_history".to_string(),
71        }
72    }
73}
74
75impl MigrationConfig {
76    fn init_from_config() -> Result<Self, ErrorPtr> {
77        Config::builder()
78            .add_source(File::with_name(CONFIG_FILE).required(false))
79            .build()
80            .and_then(|config| config.try_deserialize::<MigrationConfigWrapper>())
81            .map(|config| config.migration)
82            .map_err(|error| Arc::new(error) as ErrorPtr)
83    }
84}
85
86/// Provider for [MigrationConfig]. The primary instance of the provider will be used to retrieve
87/// migration configuration.
88#[injectable]
89pub trait MigrationConfigProvider {
90    /// Provide current config.
91    fn config(&self) -> BoxFuture<'_, Result<&MigrationConfig, ErrorPtr>>;
92}
93
94#[derive(Component)]
95#[component(priority = -128, condition = "unregistered_component::<dyn MigrationConfigProvider + Send + Sync>", constructor = "DefaultMigrationConfigProvider::new")]
96struct DefaultMigrationConfigProvider {
97    // cached init result
98    #[component(ignore)]
99    config: Result<MigrationConfig, ErrorPtr>,
100}
101
102#[component_alias]
103impl MigrationConfigProvider for DefaultMigrationConfigProvider {
104    fn config(&self) -> BoxFuture<'_, Result<&MigrationConfig, ErrorPtr>> {
105        async {
106            match &self.config {
107                Ok(config) => Ok(config),
108                Err(error) => Err(error.clone()),
109            }
110        }
111        .boxed()
112    }
113}
114
115impl DefaultMigrationConfigProvider {
116    fn new() -> BoxFuture<'static, Result<Self, ErrorPtr>> {
117        async {
118            Ok(Self {
119                config: MigrationConfig::init_from_config(),
120            })
121        }
122        .boxed()
123    }
124}
125
126#[derive(Deserialize, Default)]
127#[serde(default)]
128struct MigrationConfigWrapper {
129    migration: MigrationConfig,
130}