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