WIP json merging

This commit is contained in:
Warren Hood 2024-10-09 00:12:06 +02:00
parent 2189ce9bce
commit 3e56632ef0
6 changed files with 168 additions and 6 deletions

5
Cargo.lock generated
View file

@ -2074,6 +2074,7 @@ dependencies = [
"reqwest",
"semver",
"serde",
"serde_json",
"sha1",
"sha2",
"tempfile",
@ -3194,9 +3195,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.125"
version = "1.0.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed"
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
dependencies = [
"itoa",
"memchr",

View file

@ -13,6 +13,7 @@ pathdiff = "0.2.1"
reqwest = { version = "0.12.5", features = ["json"] }
semver = { version = "1.0.23", features = ["serde"] }
serde = { version = "1.0.207", features = ["derive"] }
serde_json = "1.0.128"
sha1 = "0.10.6"
sha2 = "0.10.8"
tempfile = "3.12.0"

150
mcmpmgr/src/file_merge.rs Normal file
View file

@ -0,0 +1,150 @@
use std::{any::Any, default, str::FromStr};
#[derive(Debug, Clone, Copy)]
pub enum FileType {
Json,
Yaml,
Toml,
}
impl FromStr for FileType {
type Err = anyhow::Error;
fn from_str(s: &str) -> anyhow::Result<Self> {
Ok(if s.contains("json") {
FileType::Json
} else if s.contains("toml") {
FileType::Toml
} else if s.contains("yaml") || s.contains("yml") {
FileType::Yaml
} else {
anyhow::bail!("Unmergable file type: {s}")
})
}
}
fn merge_json(
src: &serde_json::Value,
dst: &mut serde_json::Value,
overwrite_existing: bool,
) -> anyhow::Result<()> {
if src.is_object() && dst.is_object() {
let src = src.as_object().unwrap();
let dst = dst.as_object_mut().unwrap();
for (k, v) in src.iter() {
if v.is_object() {
let dst_v = dst.entry(k).or_insert(serde_json::json!({}));
merge_json(v, dst_v, overwrite_existing)?;
} else {
if overwrite_existing || !dst.contains_key(k) {
dst.insert(k.to_string(), v.clone());
}
}
}
} else {
// TODO: Keep track of path for better errors
anyhow::bail!("Cannot merge non-objects: {src:#?} and {dst:#?}")
}
Ok(())
}
#[test]
fn test_merge_json() {
let src = serde_json::json!({
"a": 3,
"b": {
"x": {
},
"y": {
"test": "thing"
}
},
"c": {}
});
let dst = serde_json::json!({
"b": {
"y": {
"test": "something"
}
},
"c": {
"foo": "bar"
}
});
let mut merged_overwrite = dst.clone();
let mut merged_retained = dst.clone();
merge_json(&src, &mut merged_overwrite, true).unwrap();
merge_json(&src, &mut merged_retained, false).unwrap();
assert!(
merged_overwrite["b"]["y"]["test"] == "thing",
"//b/y/test wasn't overwritten with \"thing\". src={}, dst={}",
src,
merged_overwrite
);
assert!(
merged_overwrite["a"] == 3,
"//a was not set to 3. src={}, dst={}",
src,
merged_overwrite
);
assert!(
merged_overwrite["b"]["x"].is_object(),
"//b/x is not an object. src={}, dst={}",
src,
merged_overwrite
);
assert!(
merged_overwrite["c"]["foo"] == "bar",
"//c/foo != bar. src={}, dst={}",
src,
merged_overwrite
);
assert!(
merged_retained["b"]["y"]["test"] == "something",
"//b/y/test was overwritten. src={}, dst={}",
src,
merged_retained
);
assert!(
merged_retained["a"] == 3,
"//a was not set to 3. src={}, dst={}",
src,
merged_retained
);
assert!(
merged_retained["b"]["x"].is_object(),
"//b/x is not an object. src={}, dst={}",
src,
merged_retained
);
assert!(
merged_retained["c"]["foo"] == "bar",
"//c/foo != bar. src={}, dst={}",
src,
merged_retained
);
}
/// Merge `src` into `dst` if it is a supported file type
fn merge_files(
src: &str,
dst: &str,
overwrite_existing: bool,
file_type: FileType,
) -> anyhow::Result<String> {
Ok(match file_type {
FileType::Json => {
let src_val = serde_json::from_str(src)?;
let mut dst_val = serde_json::from_str(dst)?;
merge_json(&src_val, &mut dst_val, overwrite_existing)?;
dst_val.to_string()
}
FileType::Yaml => todo!(),
FileType::Toml => todo!(),
})
}

View file

@ -1,6 +1,6 @@
use crate::providers::DownloadSide;
use serde::{Deserialize, Serialize};
use std::{fmt::Display, path::{Path, PathBuf}, str::FromStr};
use std::{fmt::Display, path::Path, str::FromStr};
#[derive(Debug, Clone, Serialize, Deserialize, Hash)]
pub struct FileMeta {
@ -9,7 +9,7 @@ pub struct FileMeta {
/// Which side the files should be applied on
pub side: DownloadSide,
/// When to apply the files to the instance
pub apply_policy: FileApplyPolicy
pub apply_policy: FileApplyPolicy,
}
#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)]
@ -17,7 +17,11 @@ pub enum FileApplyPolicy {
/// Always ensure the file or folder exactly matches that defined in the pack
Always,
/// Only apply the file or folder if it doesn't already exist in the pack
Once
Once,
/// Merge into folders and files, retaining existing values in files when a file already exists
MergeRetain,
/// Merge into folders and files, overwriting existing values in files when a file already exists
MergeOverwrite,
}
impl FromStr for FileApplyPolicy {
@ -27,6 +31,8 @@ impl FromStr for FileApplyPolicy {
match s.to_ascii_lowercase().as_str() {
"always" => Ok(Self::Always),
"once" => Ok(Self::Once),
"mergeretain" => Ok(Self::MergeRetain),
"mergeoverwrite" => Ok(Self::MergeOverwrite),
_ => anyhow::bail!("Invalid apply policy {}. Expected one of: always, once", s),
}
}
@ -37,6 +43,8 @@ impl Display for FileApplyPolicy {
match self {
Self::Always => write!(f, "Always"),
Self::Once => write!(f, "Once"),
Self::MergeRetain => write!(f, "MergeRetain"),
Self::MergeOverwrite => write!(f, "MergeOverwrite"),
}
}
}

View file

@ -1,5 +1,6 @@
pub mod mod_meta;
pub mod file_merge;
pub mod file_meta;
pub mod mod_meta;
pub mod modpack;
pub mod profiles;
pub mod providers;

View file

@ -1,3 +1,4 @@
mod file_merge;
mod file_meta;
mod mod_meta;
mod modpack;