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