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)]
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(config: &Config) -> 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).await?;
58            manifests.push(manifest);
59        }
60        for server in config.extra_servers() {
61            let manifest = Manifest::from_config(server.clone(), config).await?;
62            manifests.push(manifest);
63        }
64        manifests.push(Manifest::from_config(config.server().clone(), config).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    ) -> Result<RemotePackage, SearchError> {
74        let span = span!(
75            tracing::Level::INFO,
76            "🔎 Searching",
77            package = package_req.to_string(),
78        );
79        let _enter = span.enter();
80        match &self.0 {
81            Impl::LuarocksManifests(manifests) => {
82                match manifests
83                    .iter()
84                    .find_map(|manifest| manifest.find(package_req, filter.clone()))
85                {
86                    Some(package) => Ok(package),
87                    None => Err(SearchError::RockNotFound(package_req.clone())),
88                }
89            }
90            Impl::Lock(lockfile) => {
91                match lockfile.has_rock(package_req, filter).map(|local_package| {
92                    RemotePackage::new(
93                        PackageSpec::new(local_package.spec.name, local_package.spec.version),
94                        local_package.source,
95                        local_package.source_url,
96                    )
97                }) {
98                    Some(package) => Ok(package),
99                    None => Err(SearchError::RockNotFoundInLockfile(package_req.clone())),
100                }
101            }
102        }
103    }
104
105    /// Search for all packages that match the requirement.
106    pub fn search(&self, package_req: &PackageReq) -> Vec<(&PackageName, Vec<&PackageVersion>)> {
107        match &self.0 {
108            Impl::LuarocksManifests(manifests) => manifests
109                .iter()
110                .flat_map(|manifest| {
111                    manifest
112                        .metadata()
113                        .repository
114                        .iter()
115                        .filter_map(|(name, elements)| {
116                            if name.to_string().contains(&package_req.name().to_string()) {
117                                Some((
118                                    name,
119                                    elements
120                                        .keys()
121                                        .filter(|version| {
122                                            package_req.version_req().matches(version)
123                                        })
124                                        .sorted_by(|a, b| Ord::cmp(b, a))
125                                        .collect_vec(),
126                                ))
127                            } else {
128                                None
129                            }
130                        })
131                })
132                .collect(),
133            Impl::Lock(lockfile) => lockfile
134                .rocks()
135                .values()
136                .filter_map(|package| {
137                    // NOTE: This doesn't group packages by name, but we don't care for now,
138                    // as we shouldn't need to use this function with a lockfile.
139                    let name = package.name();
140                    if name.to_string().contains(&package_req.name().to_string()) {
141                        Some((name, vec![package.version()]))
142                    } else {
143                        None
144                    }
145                })
146                .collect_vec(),
147        }
148    }
149
150    /// Find the latest version for a package by name.
151    pub(crate) fn latest_version(&self, rock_name: &PackageName) -> Option<PackageVersion> {
152        self.latest_match(&rock_name.clone().into(), None)
153            .map(|result| result.version().clone())
154    }
155
156    /// Find the latest package that matches the requirement.
157    pub fn latest_match(
158        &self,
159        package_req: &PackageReq,
160        filter: Option<RemotePackageTypeFilterSpec>,
161    ) -> Option<PackageSpec> {
162        match self.find(package_req, filter) {
163            Ok(result) => Some(result.package),
164            Err(_) => None,
165        }
166    }
167}
168
169impl From<Manifest> for RemotePackageDB {
170    fn from(manifest: Manifest) -> Self {
171        Self(Impl::LuarocksManifests(vec![manifest]))
172    }
173}
174
175impl From<LocalPackageLock> for RemotePackageDB {
176    fn from(lock: LocalPackageLock) -> Self {
177        Self(Impl::Lock(lock))
178    }
179}