Skip to main content

minecraft_java_rs_core/utils/
paths.rs

1use crate::error::LaunchError;
2
3/// Resolved filesystem path and name for a Maven library coordinate.
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct LibraryPath {
6    /// Relative directory path, e.g. `"net/minecraftforge/forge/1.19-41.0.63"`.
7    pub path: String,
8    /// File name with extension, e.g. `"forge-1.19-41.0.63.jar"`.
9    pub name: String,
10    /// Raw version string from the coordinate.
11    pub version: String,
12}
13
14/// Maven mirrors tried in order when downloading Minecraft libraries.
15pub const MIRRORS: &[&str] = &[
16    "https://maven.minecraftforge.net",
17    "https://maven.neoforged.net/releases",
18    "https://maven.creeperhost.net",
19    "https://libraries.minecraft.net",
20    "https://repo1.maven.org/maven2",
21];
22
23/// Converts a Gradle/Maven coordinate into a relative filesystem path and filename.
24///
25/// Format: `groupId:artifactId:version` or `groupId:artifactId:version:classifier`
26/// Optional `@ext` suffix in version or classifier overrides the file extension.
27///
28/// # Examples
29/// ```
30/// use minecraft_java_rs_core::utils::paths::get_path_libraries;
31///
32/// let r = get_path_libraries("net.minecraftforge:forge:1.19-41.0.63", None, None).unwrap();
33/// assert_eq!(r.path, "net/minecraftforge/forge/1.19-41.0.63");
34/// assert_eq!(r.name, "forge-1.19-41.0.63.jar");
35///
36/// let r = get_path_libraries("net.java.dev.jna:jna:5.10.0", Some("-natives-linux"), None).unwrap();
37/// assert_eq!(r.name, "jna-5.10.0-natives-linux.jar");
38/// ```
39pub fn get_path_libraries(
40    main: &str,
41    native_string: Option<&str>,
42    force_ext: Option<&str>,
43) -> Result<LibraryPath, LaunchError> {
44    let parts: Vec<&str> = main.splitn(5, ':').collect();
45    if parts.len() < 3 {
46        return Err(LaunchError::InvalidData(format!(
47            "invalid library coordinate (need group:artifact:version): '{main}'"
48        )));
49    }
50
51    let group = parts[0];
52    let artifact = parts[1];
53    let version = parts[2];
54    let classifier = parts.get(3).copied();
55
56    // Base file name: "version" or "version-classifier"
57    let file_name = match classifier {
58        Some(c) => format!("{version}-{c}"),
59        None => version.to_string(),
60    };
61
62    // If the file_name contains '@', treat the part after '@' as the extension.
63    // Otherwise, append native_string (empty if None) and the extension (.jar by default).
64    let final_file_name = if let Some(at) = file_name.find('@') {
65        // "1.0@beta" → "1.0.beta"
66        let mut s = file_name.clone();
67        s.replace_range(at..=at, ".");
68        s
69    } else {
70        let native = native_string.unwrap_or("");
71        let ext = force_ext.unwrap_or(".jar");
72        format!("{file_name}{native}{ext}")
73    };
74
75    // Directory path: group dots → slashes, artifact, version (without @suffix)
76    let version_dir = version.split('@').next().unwrap_or(version);
77    let path_lib = format!("{}/{artifact}/{version_dir}", group.replace('.', "/"));
78
79    Ok(LibraryPath {
80        path: path_lib,
81        name: format!("{artifact}-{final_file_name}"),
82        version: version.to_string(),
83    })
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn basic_coordinate() {
92        let r = get_path_libraries("net.minecraftforge:forge:1.19-41.0.63", None, None).unwrap();
93        assert_eq!(r.path, "net/minecraftforge/forge/1.19-41.0.63");
94        assert_eq!(r.name, "forge-1.19-41.0.63.jar");
95        assert_eq!(r.version, "1.19-41.0.63");
96    }
97
98    #[test]
99    fn with_native_string() {
100        let r =
101            get_path_libraries("net.java.dev.jna:jna:5.10.0", Some("-natives-linux"), None)
102                .unwrap();
103        assert_eq!(r.path, "net/java/dev/jna/jna/5.10.0");
104        assert_eq!(r.name, "jna-5.10.0-natives-linux.jar");
105    }
106
107    #[test]
108    fn with_classifier() {
109        let r =
110            get_path_libraries("some.group:artifact:1.0:natives-win", None, None).unwrap();
111        assert_eq!(r.name, "artifact-1.0-natives-win.jar");
112    }
113
114    #[test]
115    fn with_at_extension() {
116        let r = get_path_libraries("com.example:lib:1.0@zip", None, None).unwrap();
117        assert_eq!(r.name, "lib-1.0.zip");
118        // Directory uses the version without the @suffix
119        assert_eq!(r.path, "com/example/lib/1.0");
120    }
121
122    #[test]
123    fn with_force_ext() {
124        let r = get_path_libraries("com.example:lib:2.0", None, Some(".zip")).unwrap();
125        assert_eq!(r.name, "lib-2.0.zip");
126    }
127
128    #[test]
129    fn malformed_coordinate_returns_error() {
130        assert!(get_path_libraries("no-colons-here", None, None).is_err());
131        assert!(get_path_libraries("only:one", None, None).is_err());
132    }
133}