model/project: add Project::merged for pure combining
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Idf955432e57d87352dffa961e145fcb76a6a6964
This commit is contained in:
parent
5772200da9
commit
c0c9d741c1
1 changed files with 183 additions and 0 deletions
|
|
@ -3,6 +3,7 @@ use std::collections::{HashMap, HashSet};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::enums::{ProjectSide, ProjectType, ReleaseType, UpdateStrategy};
|
use super::enums::{ProjectSide, ProjectType, ReleaseType, UpdateStrategy};
|
||||||
|
use crate::error::{PakkerError, Result};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Project {
|
pub struct Project {
|
||||||
|
|
@ -169,6 +170,80 @@ impl Project {
|
||||||
self.aliases.extend(other.aliases);
|
self.aliases.extend(other.aliases);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Merge this project with another, returning a new combined project.
|
||||||
|
/// Like Pakku's `Project.plus()`, this is a pure operation that doesn't
|
||||||
|
/// modify either project.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns `PakkerError::InvalidProject` if the projects have different types
|
||||||
|
/// or conflicting pakku_links.
|
||||||
|
pub fn merged(&self, other: Self) -> Result<Self> {
|
||||||
|
if self.r#type != other.r#type {
|
||||||
|
return Err(PakkerError::InvalidProject(format!(
|
||||||
|
"Cannot merge projects of different types: {:?} vs {:?}",
|
||||||
|
self.r#type, other.r#type
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !other.pakku_links.is_empty() && self.pakku_links != other.pakku_links {
|
||||||
|
return Err(PakkerError::InvalidProject(
|
||||||
|
"Cannot merge projects with conflicting pakku_links".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefer non-default side
|
||||||
|
let side = if self.side != ProjectSide::Both {
|
||||||
|
self.side
|
||||||
|
} else {
|
||||||
|
other.side
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut id = self.id.clone();
|
||||||
|
for (platform, other_id) in other.id {
|
||||||
|
id.entry(platform).or_insert(other_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut slug = self.slug.clone();
|
||||||
|
for (platform, other_slug) in other.slug {
|
||||||
|
slug.entry(platform).or_insert(other_slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut name = self.name.clone();
|
||||||
|
for (platform, other_name) in other.name {
|
||||||
|
name.entry(platform).or_insert(other_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut files = self.files.clone();
|
||||||
|
for file in other.files {
|
||||||
|
if !files.iter().any(|f| f.id == file.id) {
|
||||||
|
files.push(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut aliases = self.aliases.clone();
|
||||||
|
aliases.extend(other.aliases);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
pakku_id: self.pakku_id.clone(),
|
||||||
|
pakku_links: self.pakku_links.clone(),
|
||||||
|
r#type: self.r#type,
|
||||||
|
side,
|
||||||
|
slug,
|
||||||
|
name,
|
||||||
|
id,
|
||||||
|
update_strategy: self.update_strategy,
|
||||||
|
redistributable: self.redistributable && other.redistributable,
|
||||||
|
subpath: self.subpath.clone().or(other.subpath.clone()),
|
||||||
|
aliases,
|
||||||
|
export: if self.export {
|
||||||
|
self.export
|
||||||
|
} else {
|
||||||
|
other.export
|
||||||
|
},
|
||||||
|
files,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if versions match across all providers.
|
/// Check if versions match across all providers.
|
||||||
/// Returns true if all provider files have the same version/file,
|
/// Returns true if all provider files have the same version/file,
|
||||||
/// or if there's only one provider.
|
/// or if there's only one provider.
|
||||||
|
|
@ -760,4 +835,112 @@ mod tests {
|
||||||
let url = file.get_site_url(&project);
|
let url = file.get_site_url(&project);
|
||||||
assert!(url.is_none());
|
assert!(url.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_merged_different_types_returns_error() {
|
||||||
|
let mut p1 =
|
||||||
|
Project::new("id1".to_string(), ProjectType::Mod, ProjectSide::Both);
|
||||||
|
p1.name.insert("modrinth".to_string(), "Mod1".to_string());
|
||||||
|
|
||||||
|
let mut p2 = Project::new(
|
||||||
|
"id2".to_string(),
|
||||||
|
ProjectType::ResourcePack,
|
||||||
|
ProjectSide::Both,
|
||||||
|
);
|
||||||
|
p2.name.insert("modrinth".to_string(), "RP1".to_string());
|
||||||
|
|
||||||
|
assert!(p1.merged(p2).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_merged_combines_ids_and_slugs() {
|
||||||
|
let mut p1 =
|
||||||
|
Project::new("id1".to_string(), ProjectType::Mod, ProjectSide::Both);
|
||||||
|
p1.add_platform(
|
||||||
|
"modrinth".to_string(),
|
||||||
|
"mr1".to_string(),
|
||||||
|
"mod1".to_string(),
|
||||||
|
"Mod 1".to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut p2 =
|
||||||
|
Project::new("id2".to_string(), ProjectType::Mod, ProjectSide::Both);
|
||||||
|
p2.add_platform(
|
||||||
|
"curseforge".to_string(),
|
||||||
|
"cf1".to_string(),
|
||||||
|
"mod1".to_string(),
|
||||||
|
"Mod 1".to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let merged = p1.merged(p2).unwrap();
|
||||||
|
assert_eq!(merged.id.get("modrinth"), Some(&"mr1".to_string()));
|
||||||
|
assert_eq!(merged.id.get("curseforge"), Some(&"cf1".to_string()));
|
||||||
|
assert_eq!(merged.slug.get("modrinth"), Some(&"mod1".to_string()));
|
||||||
|
assert_eq!(merged.slug.get("curseforge"), Some(&"mod1".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_merged_prefers_non_both_side() {
|
||||||
|
let p1 =
|
||||||
|
Project::new("id1".to_string(), ProjectType::Mod, ProjectSide::Client);
|
||||||
|
let p2 =
|
||||||
|
Project::new("id2".to_string(), ProjectType::Mod, ProjectSide::Both);
|
||||||
|
|
||||||
|
let merged = p1.merged(p2.clone()).unwrap();
|
||||||
|
assert_eq!(merged.side, ProjectSide::Client);
|
||||||
|
|
||||||
|
let merged2 = p2.merged(p1).unwrap();
|
||||||
|
assert_eq!(merged2.side, ProjectSide::Client);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_merged_preserves_pakku_id() {
|
||||||
|
let p1 =
|
||||||
|
Project::new("id1".to_string(), ProjectType::Mod, ProjectSide::Both);
|
||||||
|
let p2 =
|
||||||
|
Project::new("id2".to_string(), ProjectType::Mod, ProjectSide::Both);
|
||||||
|
|
||||||
|
let merged = p1.merged(p2).unwrap();
|
||||||
|
assert_eq!(merged.pakku_id, Some("id1".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_merged_deduplicates_files() {
|
||||||
|
let mut p1 =
|
||||||
|
Project::new("id1".to_string(), ProjectType::Mod, ProjectSide::Both);
|
||||||
|
p1.files.push(ProjectFile {
|
||||||
|
file_type: "modrinth".to_string(),
|
||||||
|
file_name: "mod-1.0.0.jar".to_string(),
|
||||||
|
mc_versions: vec!["1.20.1".to_string()],
|
||||||
|
loaders: vec!["fabric".to_string()],
|
||||||
|
release_type: ReleaseType::Release,
|
||||||
|
url: "https://example.com/mod.jar".to_string(),
|
||||||
|
id: "file1".to_string(),
|
||||||
|
parent_id: "mod123".to_string(),
|
||||||
|
hashes: HashMap::new(),
|
||||||
|
required_dependencies: vec![],
|
||||||
|
size: 1024,
|
||||||
|
date_published: "2024-01-01T00:00:00Z".to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut p2 =
|
||||||
|
Project::new("id2".to_string(), ProjectType::Mod, ProjectSide::Both);
|
||||||
|
p2.files.push(ProjectFile {
|
||||||
|
file_type: "modrinth".to_string(),
|
||||||
|
file_name: "mod-1.0.0.jar".to_string(),
|
||||||
|
mc_versions: vec!["1.20.1".to_string()],
|
||||||
|
loaders: vec!["fabric".to_string()],
|
||||||
|
release_type: ReleaseType::Release,
|
||||||
|
url: "https://example.com/mod.jar".to_string(),
|
||||||
|
id: "file1".to_string(),
|
||||||
|
parent_id: "mod123".to_string(),
|
||||||
|
hashes: HashMap::new(),
|
||||||
|
required_dependencies: vec![],
|
||||||
|
size: 1024,
|
||||||
|
date_published: "2024-01-01T00:00:00Z".to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let merged = p1.merged(p2).unwrap();
|
||||||
|
assert_eq!(merged.files.len(), 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue