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 super::enums::{ProjectSide, ProjectType, ReleaseType, UpdateStrategy};
|
||||
use crate::error::{PakkerError, Result};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Project {
|
||||
|
|
@ -169,6 +170,80 @@ impl Project {
|
|||
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.
|
||||
/// Returns true if all provider files have the same version/file,
|
||||
/// or if there's only one provider.
|
||||
|
|
@ -760,4 +835,112 @@ mod tests {
|
|||
let url = file.get_site_url(&project);
|
||||
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