ferrous_forge/rust_version/
mod.rs

1//! Rust version management and checking
2//!
3//! This module provides functionality to detect installed Rust versions,
4//! check for updates from GitHub releases, and provide recommendations.
5
6use crate::Result;
7use semver::Version;
8use serde::{Deserialize, Serialize};
9use std::sync::Arc;
10use std::time::Duration;
11use tokio::sync::RwLock;
12
13pub mod cache;
14pub mod detector;
15pub mod github;
16
17pub use detector::RustVersion;
18pub use github::{GitHubClient, GitHubRelease};
19
20/// Rust release channel
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22pub enum Channel {
23    /// Stable releases
24    Stable,
25    /// Beta releases
26    Beta,
27    /// Nightly builds
28    Nightly,
29    /// Custom or unknown channel
30    Custom(String),
31}
32
33impl std::fmt::Display for Channel {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        match self {
36            Self::Stable => write!(f, "stable"),
37            Self::Beta => write!(f, "beta"),
38            Self::Nightly => write!(f, "nightly"),
39            Self::Custom(s) => write!(f, "{}", s),
40        }
41    }
42}
43
44/// Version update recommendation
45#[derive(Debug, Clone)]
46pub enum UpdateRecommendation {
47    /// Already on latest version
48    UpToDate,
49    /// Minor update available
50    MinorUpdate {
51        /// Current Rust version
52        current: Version,
53        /// Latest available version
54        latest: Version,
55        /// URL to the release page
56        release_url: String,
57    },
58    /// Major update available
59    MajorUpdate {
60        /// Current Rust version
61        current: Version,
62        /// Latest available version
63        latest: Version,
64        /// URL to the release page
65        release_url: String,
66    },
67    /// Security update available
68    SecurityUpdate {
69        /// Current Rust version
70        current: Version,
71        /// Latest available version
72        latest: Version,
73        /// URL to the release page
74        release_url: String,
75        /// Security update details
76        details: String,
77    },
78}
79
80/// Version manager for checking and recommending updates
81pub struct VersionManager {
82    github_client: GitHubClient,
83    cache: Arc<RwLock<cache::Cache<String, Vec<u8>>>>,
84}
85
86impl VersionManager {
87    /// Create a new version manager
88    pub fn new() -> Result<Self> {
89        let github_client = GitHubClient::new(None)?;
90        let cache = Arc::new(RwLock::new(cache::Cache::new(Duration::from_secs(3600))));
91        
92        Ok(Self {
93            github_client,
94            cache,
95        })
96    }
97    
98    /// Check current Rust installation
99    pub async fn check_current(&self) -> Result<RustVersion> {
100        detector::detect_rust_version()
101    }
102    
103    /// Get latest stable release
104    pub async fn get_latest_stable(&self) -> Result<GitHubRelease> {
105        // Check cache first
106        let cache_key = "latest_stable";
107        
108        {
109            let cache = self.cache.read().await;
110            if let Some(cached_bytes) = cache.get(&cache_key.to_string()) {
111                if let Ok(release) = serde_json::from_slice::<GitHubRelease>(&cached_bytes) {
112                    return Ok(release);
113                }
114            }
115        }
116        
117        // Fetch from GitHub
118        let release = self.github_client.get_latest_release().await?;
119        
120        // Cache the result
121        if let Ok(bytes) = serde_json::to_vec(&release) {
122            let mut cache = self.cache.write().await;
123            cache.insert(cache_key.to_string(), bytes);
124        }
125        
126        Ok(release)
127    }
128    
129    /// Get update recommendation
130    pub async fn get_recommendation(&self) -> Result<UpdateRecommendation> {
131        let current = self.check_current().await?;
132        let latest = self.get_latest_stable().await?;
133        
134        // Compare versions
135        if latest.version <= current.version {
136            return Ok(UpdateRecommendation::UpToDate);
137        }
138        
139        // Check if it's a security update
140        let is_security = latest.body.to_lowercase().contains("security") ||
141                         latest.name.to_lowercase().contains("security");
142        
143        if is_security {
144            return Ok(UpdateRecommendation::SecurityUpdate {
145                current: current.version.clone(),
146                latest: latest.version.clone(),
147                release_url: latest.html_url.clone(),
148                details: self.extract_security_details(&latest.body),
149            });
150        }
151        
152        // Check if it's a major update
153        if latest.version.major > current.version.major {
154            return Ok(UpdateRecommendation::MajorUpdate {
155                current: current.version.clone(),
156                latest: latest.version.clone(),
157                release_url: latest.html_url.clone(),
158            });
159        }
160        
161        // It's a minor/patch update
162        Ok(UpdateRecommendation::MinorUpdate {
163            current: current.version.clone(),
164            latest: latest.version.clone(),
165            release_url: latest.html_url.clone(),
166        })
167    }
168    
169    /// Get multiple recent releases
170    pub async fn get_recent_releases(&self, count: usize) -> Result<Vec<GitHubRelease>> {
171        self.github_client.get_releases(count).await
172    }
173    
174    fn extract_security_details(&self, body: &str) -> String {
175        // Extract security-related information from release notes
176        body.lines()
177            .filter(|line| {
178                let lower = line.to_lowercase();
179                lower.contains("security") || 
180                lower.contains("vulnerability") ||
181                lower.contains("cve-")
182            })
183            .take(3)
184            .collect::<Vec<_>>()
185            .join("\n")
186    }
187}
188
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193    
194    #[test]
195    fn test_channel_display() {
196        assert_eq!(Channel::Stable.to_string(), "stable");
197        assert_eq!(Channel::Beta.to_string(), "beta");
198        assert_eq!(Channel::Nightly.to_string(), "nightly");
199        assert_eq!(Channel::Custom("custom".to_string()).to_string(), "custom");
200    }
201}