vx_core/
url_builder.rs

1//! URL building utilities for different tools
2
3use crate::platform::Platform;
4
5/// Temporary UrlBuilder trait during migration
6pub trait UrlBuilder: Send + Sync {
7    fn download_url(&self, version: &str) -> Option<String>;
8    fn versions_url(&self) -> &str;
9}
10
11/// URL builder for Node.js downloads
12#[derive(Debug, Clone)]
13pub struct NodeUrlBuilder;
14
15impl Default for NodeUrlBuilder {
16    fn default() -> Self {
17        Self::new()
18    }
19}
20
21impl NodeUrlBuilder {
22    /// Create a new NodeUrlBuilder instance
23    pub fn new() -> Self {
24        Self
25    }
26    /// Generate Node.js download URL for a version
27    pub fn download_url(version: &str) -> Option<String> {
28        let platform = Platform::current();
29        let (os, arch) = platform.node_platform_string()?;
30        let ext = platform.archive_extension();
31
32        Some(format!(
33            "https://nodejs.org/dist/v{}/node-v{}-{}-{}.{}",
34            version, version, os, arch, ext
35        ))
36    }
37
38    /// Generate Node.js versions API URL
39    pub fn versions_url() -> &'static str {
40        "https://nodejs.org/dist/index.json"
41    }
42}
43
44impl UrlBuilder for NodeUrlBuilder {
45    fn download_url(&self, version: &str) -> Option<String> {
46        Self::download_url(version)
47    }
48
49    fn versions_url(&self) -> &str {
50        Self::versions_url()
51    }
52}
53
54/// URL builder for Go downloads
55#[derive(Debug, Clone)]
56pub struct GoUrlBuilder;
57
58impl Default for GoUrlBuilder {
59    fn default() -> Self {
60        Self::new()
61    }
62}
63
64impl GoUrlBuilder {
65    /// Create a new GoUrlBuilder instance
66    pub fn new() -> Self {
67        Self
68    }
69    /// Generate Go download URL for a version
70    pub fn download_url(version: &str) -> Option<String> {
71        let platform = Platform::current();
72        let (os, arch) = platform.go_platform_string()?;
73        let ext = platform.archive_extension();
74
75        Some(format!(
76            "https://go.dev/dl/go{}.{}-{}.{}",
77            version, os, arch, ext
78        ))
79    }
80
81    /// Generate Go versions API URL
82    pub fn versions_url() -> &'static str {
83        "https://go.dev/dl/?mode=json"
84    }
85}
86
87impl UrlBuilder for GoUrlBuilder {
88    fn download_url(&self, version: &str) -> Option<String> {
89        Self::download_url(version)
90    }
91
92    fn versions_url(&self) -> &str {
93        Self::versions_url()
94    }
95}
96
97/// URL builder for Rust downloads
98#[derive(Debug, Clone)]
99pub struct RustUrlBuilder;
100
101impl Default for RustUrlBuilder {
102    fn default() -> Self {
103        Self::new()
104    }
105}
106
107impl RustUrlBuilder {
108    /// Create a new RustUrlBuilder instance
109    pub fn new() -> Self {
110        Self
111    }
112    /// Generate Rust download URL (using rustup)
113    pub fn download_url(&self, _version: &str) -> Option<String> {
114        let platform = Platform::current();
115        match platform.os {
116            crate::platform::OperatingSystem::Windows => {
117                Some("https://win.rustup.rs/x86_64".to_string())
118            }
119            _ => Some("https://sh.rustup.rs".to_string()),
120        }
121    }
122
123    /// Generate Rust versions API URL (GitHub releases)
124    pub fn versions_url() -> &'static str {
125        "https://api.github.com/repos/rust-lang/rust/releases"
126    }
127}
128
129/// URL builder for Python downloads
130#[derive(Debug, Clone)]
131pub struct PythonUrlBuilder;
132
133impl Default for PythonUrlBuilder {
134    fn default() -> Self {
135        Self::new()
136    }
137}
138
139impl PythonUrlBuilder {
140    /// Create a new PythonUrlBuilder instance
141    pub fn new() -> Self {
142        Self
143    }
144    /// Generate Python download URL for a version
145    pub fn download_url(version: &str) -> Option<String> {
146        let platform = Platform::current();
147
148        let (os, arch) = match platform.os {
149            crate::platform::OperatingSystem::Windows => {
150                let arch = match platform.arch {
151                    crate::platform::Architecture::X86_64 => "amd64",
152                    crate::platform::Architecture::X86 => "win32",
153                    _ => return None,
154                };
155                ("windows", arch)
156            }
157            crate::platform::OperatingSystem::MacOS => {
158                let arch = match platform.arch {
159                    crate::platform::Architecture::X86_64 => "universal2",
160                    crate::platform::Architecture::Aarch64 => "universal2",
161                    _ => return None,
162                };
163                ("macos", arch)
164            }
165            crate::platform::OperatingSystem::Linux => {
166                let arch = match platform.arch {
167                    crate::platform::Architecture::X86_64 => "x86_64",
168                    crate::platform::Architecture::Aarch64 => "aarch64",
169                    _ => return None,
170                };
171                ("linux", arch)
172            }
173            _ => return None,
174        };
175
176        let ext = if matches!(platform.os, crate::platform::OperatingSystem::Windows) {
177            "exe"
178        } else {
179            "tgz"
180        };
181
182        Some(format!(
183            "https://www.python.org/ftp/python/{}/python-{}-{}-{}.{}",
184            version, version, os, arch, ext
185        ))
186    }
187
188    /// Generate Python versions API URL (GitHub releases)
189    pub fn versions_url() -> &'static str {
190        "https://api.github.com/repos/python/cpython/releases"
191    }
192}
193
194/// URL builder for UV downloads
195#[derive(Debug, Clone)]
196pub struct UvUrlBuilder;
197
198impl Default for UvUrlBuilder {
199    fn default() -> Self {
200        Self::new()
201    }
202}
203
204impl UvUrlBuilder {
205    /// Create a new UvUrlBuilder instance
206    pub fn new() -> Self {
207        Self
208    }
209
210    /// Generate UV download URL for a version
211    pub fn download_url(version: &str) -> Option<String> {
212        let platform = Platform::current();
213
214        let filename = match platform.os {
215            crate::platform::OperatingSystem::Windows => "uv-x86_64-pc-windows-msvc.zip",
216            crate::platform::OperatingSystem::MacOS => "uv-x86_64-apple-darwin.tar.gz",
217            _ => "uv-x86_64-unknown-linux-gnu.tar.gz",
218        };
219
220        // For latest, we need to use the actual latest tag, not "latest"
221        // This will be resolved by the downloader when fetching versions
222        let tag = if version == "latest" {
223            // Return None for latest, let the caller resolve the actual version first
224            return None;
225        } else {
226            // UV releases don't use 'v' prefix, just the version number
227            version.to_string()
228        };
229
230        Some(format!(
231            "https://github.com/astral-sh/uv/releases/download/{}/{}",
232            tag, filename
233        ))
234    }
235
236    /// Generate UV versions API URL (GitHub releases)
237    pub fn versions_url() -> &'static str {
238        "https://api.github.com/repos/astral-sh/uv/releases"
239    }
240}
241
242impl UrlBuilder for UvUrlBuilder {
243    fn download_url(&self, version: &str) -> Option<String> {
244        Self::download_url(version)
245    }
246
247    fn versions_url(&self) -> &str {
248        Self::versions_url()
249    }
250}
251
252/// Generic URL builder for tools that follow common patterns
253pub struct GenericUrlBuilder;
254
255impl GenericUrlBuilder {
256    /// Build GitHub releases URL
257    pub fn github_releases_url(owner: &str, repo: &str) -> String {
258        format!("https://api.github.com/repos/{}/{}/releases", owner, repo)
259    }
260
261    /// Build GitHub release download URL
262    pub fn github_release_download_url(
263        owner: &str,
264        repo: &str,
265        tag: &str,
266        filename: &str,
267    ) -> String {
268        format!(
269            "https://github.com/{}/{}/releases/download/{}/{}",
270            owner, repo, tag, filename
271        )
272    }
273}
274
275#[cfg(test)]
276mod tests {
277    use super::*;
278
279    #[test]
280    fn test_node_url_builder() {
281        let url = NodeUrlBuilder::download_url("18.0.0");
282        assert!(url.is_some());
283
284        let url = url.unwrap();
285        assert!(url.contains("18.0.0"));
286        assert!(url.contains("nodejs.org"));
287
288        let versions_url = NodeUrlBuilder::versions_url();
289        assert!(versions_url.contains("nodejs.org"));
290    }
291
292    #[test]
293    fn test_go_url_builder() {
294        let url = GoUrlBuilder::download_url("1.21.0");
295        assert!(url.is_some());
296
297        let url = url.unwrap();
298        assert!(url.contains("1.21.0"));
299        assert!(url.contains("go.dev"));
300
301        let versions_url = GoUrlBuilder::versions_url();
302        assert!(versions_url.contains("go.dev"));
303    }
304
305    #[test]
306    fn test_rust_url_builder() {
307        let builder = RustUrlBuilder::new();
308        let url = builder.download_url("1.70.0");
309        assert!(url.is_some());
310
311        let url = url.unwrap();
312        assert!(url.contains("rustup"));
313
314        let versions_url = RustUrlBuilder::versions_url();
315        assert!(versions_url.contains("github.com"));
316    }
317
318    #[test]
319    fn test_python_url_builder() {
320        let url = PythonUrlBuilder::download_url("3.12.0");
321        assert!(url.is_some());
322
323        let url = url.unwrap();
324        assert!(url.contains("3.12.0"));
325        assert!(url.contains("python.org"));
326
327        let versions_url = PythonUrlBuilder::versions_url();
328        assert!(versions_url.contains("github.com"));
329    }
330
331    #[test]
332    fn test_generic_url_builder() {
333        let releases_url = GenericUrlBuilder::github_releases_url("owner", "repo");
334        assert_eq!(
335            releases_url,
336            "https://api.github.com/repos/owner/repo/releases"
337        );
338
339        let download_url = GenericUrlBuilder::github_release_download_url(
340            "owner",
341            "repo",
342            "v1.0.0",
343            "file.tar.gz",
344        );
345        assert_eq!(
346            download_url,
347            "https://github.com/owner/repo/releases/download/v1.0.0/file.tar.gz"
348        );
349    }
350}