Skip to main content

lux_lib/remote_package_db/
mod.rs

1use crate::{
2    config::{Config, ConfigError},
3    lockfile::{LocalPackageLock, LockfileIntegrityError},
4    manifest::{Manifest, ManifestError},
5    package::{
6        PackageName, PackageReq, PackageSpec, PackageVersion, RemotePackage,
7        RemotePackageTypeFilterSpec,
8    },
9    progress::{Progress, ProgressBar},
10};
11use itertools::Itertools;
12
13use thiserror::Error;
14
15#[derive(Clone, Debug)]
16pub struct RemotePackageDB(Impl);
17
18#[derive(Clone, Debug)]
19enum Impl {
20    LuarocksManifests(Vec<Manifest>),
21    Lock(LocalPackageLock),
22}
23
24#[derive(Error, Debug)]
25pub enum RemotePackageDBError {
26    #[error(transparent)]
27    ManifestError(#[from] ManifestError),
28    #[error(transparent)]
29    ConfigError(#[from] ConfigError),
30}
31
32#[derive(Error, Debug)]
33pub enum SearchError {
34    #[error("no rock that matches '{0}' found")]
35    RockNotFound(PackageReq),
36    #[error("no rock that matches '{0}' found in the lockfile.")]
37    RockNotFoundInLockfile(PackageReq),
38    #[error("error when pulling manifest:\n{0}")]
39    Manifest(#[from] ManifestError),
40}
41
42#[derive(Error, Debug)]
43pub enum RemotePackageDbIntegrityError {
44    #[error(transparent)]
45    Lockfile(#[from] LockfileIntegrityError),
46}
47
48impl RemotePackageDB {
49    pub async fn from_config(
50        config: &Config,
51        progress: &Progress<ProgressBar>,
52    ) -> Result<Self, RemotePackageDBError> {
53        let mut manifests = Vec::new();
54        for server in config.enabled_dev_servers()? {
55            let manifest = Manifest::from_config(server, config, progress).await?;
56            manifests.push(manifest);
57        }
58        for server in config.extra_servers() {
59            let manifest = Manifest::from_config(server.clone(), config, progress).await?;
60            manifests.push(manifest);
61        }
62        manifests.push(Manifest::from_config(config.server().clone(), config, progress).await?);
63        Ok(Self(Impl::LuarocksManifests(manifests)))
64    }
65
66    /// Find a remote package that matches the requirement, returning the latest match.
67    pub(crate) fn find(
68        &self,
69        package_req: &PackageReq,
70        filter: Option<RemotePackageTypeFilterSpec>,
71        progress: &Progress<ProgressBar>,
72    ) -> Result<RemotePackage, SearchError> {
73        match &self.0 {
74            Impl::LuarocksManifests(manifests) => match manifests.iter().find_map(|manifest| {
75                progress.map(|p| p.set_message(format!("🔎 Searching {}", &manifest.server_url())));
76                manifest.find(package_req, filter.clone())
77            }) {
78                Some(package) => Ok(package),
79                None => Err(SearchError::RockNotFound(package_req.clone())),
80            },
81            Impl::Lock(lockfile) => {
82                match lockfile.has_rock(package_req, filter).map(|local_package| {
83                    RemotePackage::new(
84                        PackageSpec::new(local_package.spec.name, local_package.spec.version),
85                        local_package.source,
86                        local_package.source_url,
87                    )
88                }) {
89                    Some(package) => Ok(package),
90                    None => Err(SearchError::RockNotFoundInLockfile(package_req.clone())),
91                }
92            }
93        }
94    }
95
96    /// Search for all packages that match the requirement.
97    pub fn search(&self, package_req: &PackageReq) -> Vec<(&PackageName, Vec<&PackageVersion>)> {
98        match &self.0 {
99            Impl::LuarocksManifests(manifests) => manifests
100                .iter()
101                .flat_map(|manifest| {
102                    manifest
103                        .metadata()
104                        .repository
105                        .iter()
106                        .filter_map(|(name, elements)| {
107                            if name.to_string().contains(&package_req.name().to_string()) {
108                                Some((
109                                    name,
110                                    elements
111                                        .keys()
112                                        .filter(|version| {
113                                            package_req.version_req().matches(version)
114                                        })
115                                        .sorted_by(|a, b| Ord::cmp(b, a))
116                                        .collect_vec(),
117                                ))
118                            } else {
119                                None
120                            }
121                        })
122                })
123                .collect(),
124            Impl::Lock(lockfile) => lockfile
125                .rocks()
126                .iter()
127                .filter_map(|(_, package)| {
128                    // NOTE: This doesn't group packages by name, but we don't care for now,
129                    // as we shouldn't need to use this function with a lockfile.
130                    let name = package.name();
131                    if name.to_string().contains(&package_req.name().to_string()) {
132                        Some((name, vec![package.version()]))
133                    } else {
134                        None
135                    }
136                })
137                .collect_vec(),
138        }
139    }
140
141    /// Find the latest version for a package by name.
142    pub(crate) fn latest_version(&self, rock_name: &PackageName) -> Option<PackageVersion> {
143        self.latest_match(&rock_name.clone().into(), None)
144            .map(|result| result.version().clone())
145    }
146
147    /// Find the latest package that matches the requirement.
148    pub fn latest_match(
149        &self,
150        package_req: &PackageReq,
151        filter: Option<RemotePackageTypeFilterSpec>,
152    ) -> Option<PackageSpec> {
153        match self.find(package_req, filter, &Progress::no_progress()) {
154            Ok(result) => Some(result.package),
155            Err(_) => None,
156        }
157    }
158}
159
160impl From<Manifest> for RemotePackageDB {
161    fn from(manifest: Manifest) -> Self {
162        Self(Impl::LuarocksManifests(vec![manifest]))
163    }
164}
165
166impl From<LocalPackageLock> for RemotePackageDB {
167    fn from(lock: LocalPackageLock) -> Self {
168        Self(Impl::Lock(lock))
169    }
170}