Skip to main content

lux_lib/remote_package_db/
mod.rs

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