Skip to main content

wows_data_mgr/
manifest.rs

1use std::collections::BTreeMap;
2use std::path::Path;
3
4use rootcause::prelude::*;
5use serde::Deserialize;
6use serde::Serialize;
7
8#[derive(Debug, Serialize, Deserialize)]
9pub struct GameVersionManifest {
10    #[serde(default)]
11    pub versions: BTreeMap<String, GameVersionEntry>,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct GameVersionEntry {
16    pub version: String,
17    pub depot_id: u32,
18    pub manifest_id: String,
19}
20
21impl GameVersionManifest {
22    /// Returns the highest build number in the manifest.
23    pub fn latest_build(&self) -> Option<u32> {
24        self.versions.keys().filter_map(|k| k.parse::<u32>().ok()).max()
25    }
26
27    /// Look up a build number by version string (supports shorthand like "15.1").
28    /// When multiple builds match, returns the highest.
29    pub fn find_by_version(&self, query: &str) -> Option<u32> {
30        let mut matched: Vec<u32> = self
31            .versions
32            .iter()
33            .filter(|(_, entry)| version_matches(&entry.version, query))
34            .filter_map(|(k, _)| k.parse::<u32>().ok())
35            .collect();
36        matched.sort();
37        matched.last().copied()
38    }
39
40    /// Get a manifest entry by build number.
41    pub fn get(&self, build: u32) -> Option<&GameVersionEntry> {
42        self.versions.get(&build.to_string())
43    }
44}
45
46/// Check if a full version string matches a possibly-shorthand query.
47/// "15.1.0" matches "15.1", "15.1.0", and "15".
48pub fn version_matches(full: &str, query: &str) -> bool {
49    let full_parts: Vec<&str> = full.split('.').collect();
50    let query_parts: Vec<&str> = query.split('.').collect();
51
52    if query_parts.len() > full_parts.len() {
53        return false;
54    }
55
56    full_parts.iter().zip(query_parts.iter()).all(|(f, q)| f == q)
57}
58
59pub fn load_manifest(path: &Path) -> Result<GameVersionManifest, Report> {
60    if !path.exists() {
61        return Ok(GameVersionManifest { versions: BTreeMap::new() });
62    }
63    let content = std::fs::read_to_string(path).attach_with(|| format!("Failed to read {}", path.display()))?;
64    let manifest: GameVersionManifest =
65        toml::from_str(&content).map_err(|e| rootcause::report!("Failed to parse {}: {e}", path.display()))?;
66    Ok(manifest)
67}
68
69#[cfg(test)]
70mod test {
71    use super::*;
72
73    #[test]
74    fn version_matches_exact() {
75        assert!(version_matches("15.1.0", "15.1.0"));
76    }
77
78    #[test]
79    fn version_matches_shorthand_two() {
80        assert!(version_matches("15.1.0", "15.1"));
81    }
82
83    #[test]
84    fn version_matches_shorthand_one() {
85        assert!(version_matches("15.1.0", "15"));
86    }
87
88    #[test]
89    fn version_no_match() {
90        assert!(!version_matches("15.1.0", "14.1"));
91    }
92
93    #[test]
94    fn version_query_longer() {
95        assert!(!version_matches("15.1", "15.1.0"));
96    }
97
98    #[test]
99    fn find_by_version_picks_highest() {
100        let mut versions = BTreeMap::new();
101        versions.insert(
102            "11791718".to_string(),
103            GameVersionEntry { version: "15.0.0".to_string(), depot_id: 552991, manifest_id: "aaa".to_string() },
104        );
105        versions.insert(
106            "11965230".to_string(),
107            GameVersionEntry { version: "15.1.0".to_string(), depot_id: 552991, manifest_id: "bbb".to_string() },
108        );
109        let manifest = GameVersionManifest { versions };
110
111        assert_eq!(manifest.find_by_version("15"), Some(11965230));
112        assert_eq!(manifest.find_by_version("15.1"), Some(11965230));
113        assert_eq!(manifest.find_by_version("15.0"), Some(11791718));
114        assert_eq!(manifest.find_by_version("14"), None);
115    }
116}