Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to serialize additional fields to the MigrationMetadata #3332

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions diesel/src/migration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String> {
std::collections::HashMap::new()
}
}

/// A migration source is an entity that can be used
Expand Down
1 change: 1 addition & 0 deletions diesel_migrations/migrations_internals/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ rust-version = "1.56.0"
[dependencies]
serde = {version = "1", features = ["derive"]}
toml = "0.5"
serde_json = "1"

76 changes: 72 additions & 4 deletions diesel_migrations/migrations_internals/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, serde_json::Value>,
}

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<Self, Box<dyn std::error::Error>> {
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<Self, Box<dyn std::error::Error>> {
Ok(toml::from_str(toml)?)
}

Ok(toml::from_str(&toml)?)
pub fn to_toml_string(&self) -> Result<String, Box<dyn std::error::Error>> {
Ok(toml::to_string(&self)?)
}

pub fn serialized_additional_fields(&self) -> HashMap<String, String> {
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
}
}

Expand Down Expand Up @@ -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());
}
}
6 changes: 4 additions & 2 deletions diesel_migrations/migrations_macros/src/embed_migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand All @@ -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)
))
}
11 changes: 10 additions & 1 deletion diesel_migrations/src/file_based_migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String> {
self.0.serialized_additional_fields()
}
}

fn run_sql_from_file<DB: Backend>(
Expand Down