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