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