Skip to main content

zoi_resolver/
mini_resolve.rs

1//! Optimized dependency resolution for Zoi Mini.
2//!
3//! This module provides a fast, lightweight resolution path that uses a
4//! pre-compiled JSON index instead of full registry checkouts.
5
6use std::collections::HashMap;
7
8use anyhow::{Result, anyhow};
9use colored::Colorize;
10use serde::{Deserialize, Serialize};
11pub use zoi_core::types::MiniVulnerability;
12
13/// Returns the default package revision ("1").
14fn default_revision() -> String {
15    "1".to_string()
16}
17
18/// A simplified version of package metadata used in Zoi Mini's remote index.
19///
20/// This index allows Zoi Mini to perform fast lookups and vulnerability checks
21/// without downloading individual `.pkg.lua` files or cloning entire
22/// registries.
23#[derive(Debug, Serialize, Deserialize, Clone)]
24pub struct MiniPackageIndex {
25    /// The repository where the package is located.
26    pub repo: String,
27    /// The type of repository (e.g. official, unofficial).
28    pub repo_type: String,
29    /// The current version of the package.
30    pub version: String,
31    /// The revision of the package version.
32    #[serde(default = "default_revision")]
33    pub revision: String,
34    /// A short description of the package.
35    pub description: String,
36    /// A list of sub-packages provided by this package.
37    #[serde(default, deserialize_with = "deserialize_sub_packages")]
38    pub sub_packages: Option<Vec<String>>,
39    /// Known vulnerabilities for this package.
40    pub vuln: Option<Vec<MiniVulnerability>>
41}
42
43/// Custom deserializer for sub-packages to handle potential null or non-array
44/// values gracefully.
45fn deserialize_sub_packages<'de, D>(
46    deserializer: D
47) -> Result<Option<Vec<String>>, D::Error>
48where
49    D: serde::Deserializer<'de>
50{
51    let v: serde_json::Value = serde::Deserialize::deserialize(deserializer)?;
52    if v.is_array() {
53        Ok(serde_json::from_value(v).ok())
54    } else {
55        Ok(None)
56    }
57}
58
59/// A mapping of package names to their metadata in the Mini index.
60#[derive(Debug, Serialize, Deserialize, Clone)]
61pub struct MiniRegistryIndex {
62    /// The collection of packages in the index.
63    pub packages: HashMap<String, MiniPackageIndex>
64}
65
66/// Fetches the optimized JSON index from the official Zoidberg registry.
67///
68/// This index is the backbone of Zoi Mini, providing a pre-resolved mapping
69/// of package names to their current versions and metadata.
70///
71/// # Errors
72///
73/// Returns an error if the index cannot be fetched or parsed.
74pub fn fetch_registry_index() -> Result<MiniRegistryIndex> {
75    let url = "https://gitlab.com/zillowe/zillwen/zusty/zoidberg/-/raw/main/packages.json";
76    let client = zoi_core::utils::get_http_client()?;
77    let response = client.get(url).send()?;
78    if !response.status().is_success() {
79        return Err(anyhow!(
80            "Failed to fetch packages.json from Zoidberg registry: {}",
81            response.status()
82        ));
83    }
84    let index: MiniRegistryIndex = response.json()?;
85    Ok(index)
86}
87
88/// Fetches the repository configuration from the official Zoidberg registry.
89///
90/// # Errors
91///
92/// Returns an error if the configuration cannot be fetched or parsed.
93pub fn fetch_registry_config() -> Result<zoi_core::types::RepoConfig> {
94    let url = "https://gitlab.com/zillowe/zillwen/zusty/zoidberg/-/raw/main/repo.yaml";
95    let client = zoi_core::utils::get_http_client()?;
96    let response = client.get(url).send()?;
97    if !response.status().is_success() {
98        return Err(anyhow!(
99            "Failed to fetch repo.yaml from Zoidberg registry: {}",
100            response.status()
101        ));
102    }
103    let content = response.text()?;
104    let config: zoi_core::types::RepoConfig = serde_yaml::from_str(&content)?;
105    Ok(config)
106}
107
108/// Returns the URL to download a package's `.pkg.lua` file from the official
109/// registry.
110pub fn get_package_lua_url(repo: &str, name: &str) -> String {
111    format!(
112        "https://gitlab.com/zillowe/zillwen/zusty/zoidberg/-/raw/main/{repo}/{name}/{name}.pkg.lua"
113    )
114}
115
116/// Scans the package metadata for known security advisories.
117///
118/// Returns `true` if the package is safe to install, or if the user
119/// explicitly chooses to bypass a security warning.
120///
121/// # Errors
122///
123/// Returns an error if the version cannot be parsed or if the user confirmation
124/// fails.
125pub fn check_vulnerabilities(
126    pkg_name: &str,
127    pkg_index: &MiniPackageIndex,
128    version: &str
129) -> Result<bool> {
130    let Some(vulns) = &pkg_index.vuln else {
131        return Ok(true);
132    };
133
134    let target_version =
135        semver::Version::parse(version.trim_start_matches('v'))
136            .map_err(|e| anyhow!("Failed to parse version {version}: {e}"))?;
137
138    let mut affected = Vec::new();
139
140    for vuln in vulns {
141        if let Ok(req) = semver::VersionReq::parse(&vuln.affected_range)
142            && req.matches(&target_version)
143        {
144            affected.push(vuln);
145        }
146    }
147
148    if affected.is_empty() {
149        return Ok(true);
150    }
151
152    println!("\n{}", "SECURITY WARNING".red().bold());
153    for vuln in affected {
154        println!(
155            "Package {} v{} is known to be vulnerable:",
156            pkg_name.cyan().bold(),
157            version.red()
158        );
159        println!(
160            "[{}] {} (Severity: {})",
161            vuln.id.dimmed(),
162            vuln.summary,
163            vuln.severity.to_uppercase()
164        );
165        if let Some(fixed) = &vuln.fixed_in {
166            println!("Fixed in version: {}", fixed.green());
167        }
168        println!();
169    }
170
171    Ok(zoi_core::utils::ask_for_confirmation(
172        "Do you want to continue with the installation anyway?",
173        false
174    ))
175}