From 02191f0eec9d154433af50cc85f7b6810cfa9e31 Mon Sep 17 00:00:00 2001 From: Tomas Ostasevicius Date: Wed, 21 Sep 2022 15:17:18 +0200 Subject: [PATCH] Add ability to serialize additional fields to the MigrationMetadata --- diesel/src/migration/mod.rs | 8 ++ .../migrations_internals/Cargo.toml | 1 + .../migrations_internals/src/lib.rs | 76 ++++++++++++++++++- .../migrations_macros/src/embed_migrations.rs | 6 +- .../src/file_based_migrations.rs | 11 ++- 5 files changed, 95 insertions(+), 7 deletions(-) diff --git a/diesel/src/migration/mod.rs b/diesel/src/migration/mod.rs index afd631c2786d..4d91062b0634 100644 --- a/diesel/src/migration/mod.rs +++ b/diesel/src/migration/mod.rs @@ -130,6 +130,14 @@ pub trait MigrationMetadata { fn run_in_transaction(&self) -> bool { true } + + /// Any additional fields found in the migration metadata, that are not + /// explicitly used by diesel. + /// + /// The values are JSON-serialized strings + fn additional_fields(&self) -> std::collections::HashMap { + std::collections::HashMap::new() + } } /// A migration source is an entity that can be used diff --git a/diesel_migrations/migrations_internals/Cargo.toml b/diesel_migrations/migrations_internals/Cargo.toml index 2f1c67458cfe..5b9d73bb56fe 100644 --- a/diesel_migrations/migrations_internals/Cargo.toml +++ b/diesel_migrations/migrations_internals/Cargo.toml @@ -9,4 +9,5 @@ rust-version = "1.56.0" [dependencies] serde = {version = "1", features = ["derive"]} toml = "0.5" +serde_json = "1" diff --git a/diesel_migrations/migrations_internals/src/lib.rs b/diesel_migrations/migrations_internals/src/lib.rs index 6ab5e98cb108..2bb6eb53c4fd 100644 --- a/diesel_migrations/migrations_internals/src/lib.rs +++ b/diesel_migrations/migrations_internals/src/lib.rs @@ -20,38 +20,61 @@ missing_copy_implementations )] +use std::collections::HashMap; use std::ffi::OsString; use std::fs::{DirEntry, File}; use std::io::Read; use std::path::{Path, PathBuf}; #[doc(hidden)] -#[derive(Debug, serde::Deserialize)] +#[derive(Debug, serde::Deserialize, serde::Serialize)] #[allow(missing_copy_implementations)] pub struct TomlMetadata { #[serde(default)] pub run_in_transaction: bool, + + #[serde(flatten)] + additional_fields: HashMap, } impl Default for TomlMetadata { fn default() -> Self { Self { run_in_transaction: true, + additional_fields: HashMap::default(), } } } impl TomlMetadata { - pub const fn new(run_in_transaction: bool) -> Self { - Self { run_in_transaction } + pub fn new(run_in_transaction: bool) -> Self { + Self { + run_in_transaction, + additional_fields: HashMap::new(), + } } pub fn read_from_file(path: &Path) -> Result> { let mut toml = String::new(); let mut file = File::open(path)?; file.read_to_string(&mut toml)?; + Self::from_toml_str(&toml) + } + + pub fn from_toml_str(toml: &str) -> Result> { + Ok(toml::from_str(toml)?) + } - Ok(toml::from_str(&toml)?) + pub fn to_toml_string(&self) -> Result> { + Ok(toml::to_string(&self)?) + } + + pub fn serialized_additional_fields(&self) -> HashMap { + let mut map = HashMap::with_capacity(self.additional_fields.len()); + for (k, v) in self.additional_fields.iter() { + map.insert(k.clone(), serde_json::to_string(v).unwrap_or_default()); + } + map } } @@ -101,3 +124,48 @@ pub fn migrations_directories( .transpose() })) } + +#[cfg(test)] +mod tests { + use super::*; + + static RAW_TOML: &str = r#" +run_in_transaction = false +boolean_field = false +string_field = "Something" + +[foo] +name = "Bar" +"#; + + #[test] + fn extracts_additional_fields() { + let md = TomlMetadata::from_toml_str(RAW_TOML).unwrap(); + dbg!(&md); + assert!(!md.run_in_transaction); + assert!(md.additional_fields.contains_key("boolean_field")); + assert!(md.additional_fields.contains_key("string_field")); + assert!(md.additional_fields.contains_key("foo")); + assert!(!md.additional_fields.contains_key("name")); + } + + #[test] + fn round_trip() { + let md = TomlMetadata::from_toml_str(RAW_TOML).unwrap(); + let toml = md.to_toml_string().unwrap(); + let new = TomlMetadata::from_toml_str(&toml).unwrap(); + assert_eq!(md.run_in_transaction, new.run_in_transaction); + for (k, v) in md.additional_fields.iter() { + assert_eq!(v, new.additional_fields.get(k).unwrap()); + } + } + + #[test] + fn additional_fields_serialization() { + let md = TomlMetadata::from_toml_str(RAW_TOML).unwrap(); + let fields = md.serialized_additional_fields(); + assert_eq!("\"Something\"", fields.get("string_field").unwrap()); + assert_eq!("false", fields.get("boolean_field").unwrap()); + assert_eq!(r#"{"name":"Bar"}"#, fields.get("foo").unwrap()); + } +} diff --git a/diesel_migrations/migrations_macros/src/embed_migrations.rs b/diesel_migrations/migrations_macros/src/embed_migrations.rs index 5a887ae2c68b..ac2de32d2b23 100644 --- a/diesel_migrations/migrations_macros/src/embed_migrations.rs +++ b/diesel_migrations/migrations_macros/src/embed_migrations.rs @@ -55,7 +55,9 @@ fn migration_literal_from_path(path: &Path) -> proc_macro2::TokenStream { let up_sql_path = up_sql_path.to_str(); let down_sql_path = path.join("down.sql"); let metadata = TomlMetadata::read_from_file(&path.join("metadata.toml")).unwrap_or_default(); - let run_in_transaction = metadata.run_in_transaction; + let metadata_str = metadata + .to_toml_string() + .unwrap_or_else(|_| panic!("Could not serialize the migration metadata as string")); let down_sql = match down_sql_path.metadata() { Err(e) if e.kind() == std::io::ErrorKind::NotFound => quote! { None }, @@ -69,6 +71,6 @@ fn migration_literal_from_path(path: &Path) -> proc_macro2::TokenStream { include_str!(#up_sql_path), #down_sql, diesel_migrations::EmbeddedName::new(#name), - diesel_migrations::TomlMetadataWrapper::new(#run_in_transaction) + diesel_migrations::TomlMetadataWrapper::from_toml_str_or_default(#metadata_str) )) } diff --git a/diesel_migrations/src/file_based_migrations.rs b/diesel_migrations/src/file_based_migrations.rs index ae21c82cb385..a121741f7221 100644 --- a/diesel_migrations/src/file_based_migrations.rs +++ b/diesel_migrations/src/file_based_migrations.rs @@ -254,15 +254,24 @@ pub struct TomlMetadataWrapper(TomlMetadata); impl TomlMetadataWrapper { #[doc(hidden)] - pub const fn new(run_in_transaction: bool) -> Self { + pub fn new(run_in_transaction: bool) -> Self { Self(TomlMetadata::new(run_in_transaction)) } + + #[doc(hidden)] + pub fn from_toml_str_or_default(toml: &'static str) -> Self { + Self(TomlMetadata::from_toml_str(toml).unwrap_or_default()) + } } impl MigrationMetadata for TomlMetadataWrapper { fn run_in_transaction(&self) -> bool { self.0.run_in_transaction } + + fn additional_fields(&self) -> std::collections::HashMap { + self.0.serialized_additional_fields() + } } fn run_sql_from_file(