lux_lib/remote_package_db/
mod.rs1use 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(transparent)]
37 Mlua(#[from] mlua::Error),
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: {0}")]
43 Manifest(#[from] ManifestError),
44}
45
46#[derive(Error, Debug)]
47pub enum RemotePackageDbIntegrityError {
48 #[error(transparent)]
49 Lockfile(#[from] LockfileIntegrityError),
50}
51
52impl RemotePackageDB {
53 pub async fn from_config(
54 config: &Config,
55 progress: &Progress<ProgressBar>,
56 ) -> Result<Self, RemotePackageDBError> {
57 let mut manifests = Vec::new();
58 for server in config.enabled_dev_servers()? {
59 let manifest = Manifest::from_config(server, config, progress).await?;
60 manifests.push(manifest);
61 }
62 for server in config.extra_servers() {
63 let manifest = Manifest::from_config(server.clone(), config, progress).await?;
64 manifests.push(manifest);
65 }
66 manifests.push(Manifest::from_config(config.server().clone(), config, progress).await?);
67 Ok(Self(Impl::LuarocksManifests(manifests)))
68 }
69
70 pub(crate) fn find(
72 &self,
73 package_req: &PackageReq,
74 filter: Option<RemotePackageTypeFilterSpec>,
75 progress: &Progress<ProgressBar>,
76 ) -> Result<RemotePackage, SearchError> {
77 match &self.0 {
78 Impl::LuarocksManifests(manifests) => match manifests.iter().find_map(|manifest| {
79 progress.map(|p| p.set_message(format!("🔎 Searching {}", &manifest.server_url())));
80 manifest.find(package_req, filter.clone())
81 }) {
82 Some(package) => Ok(package),
83 None => Err(SearchError::RockNotFound(package_req.clone())),
84 },
85 Impl::Lock(lockfile) => {
86 match lockfile.has_rock(package_req, filter).map(|local_package| {
87 RemotePackage::new(
88 PackageSpec::new(local_package.spec.name, local_package.spec.version),
89 local_package.source,
90 local_package.source_url,
91 )
92 }) {
93 Some(package) => Ok(package),
94 None => Err(SearchError::RockNotFoundInLockfile(package_req.clone())),
95 }
96 }
97 }
98 }
99
100 pub fn search(&self, package_req: &PackageReq) -> Vec<(&PackageName, Vec<&PackageVersion>)> {
102 match &self.0 {
103 Impl::LuarocksManifests(manifests) => manifests
104 .iter()
105 .flat_map(|manifest| {
106 manifest
107 .metadata()
108 .repository
109 .iter()
110 .filter_map(|(name, elements)| {
111 if name.to_string().contains(&package_req.name().to_string()) {
112 Some((
113 name,
114 elements
115 .keys()
116 .filter(|version| {
117 package_req.version_req().matches(version)
118 })
119 .sorted_by(|a, b| Ord::cmp(b, a))
120 .collect_vec(),
121 ))
122 } else {
123 None
124 }
125 })
126 })
127 .collect(),
128 Impl::Lock(lockfile) => lockfile
129 .rocks()
130 .iter()
131 .filter_map(|(_, package)| {
132 let name = package.name();
135 if name.to_string().contains(&package_req.name().to_string()) {
136 Some((name, vec![package.version()]))
137 } else {
138 None
139 }
140 })
141 .collect_vec(),
142 }
143 }
144
145 pub(crate) fn latest_version(&self, rock_name: &PackageName) -> Option<PackageVersion> {
147 self.latest_match(&rock_name.clone().into(), None)
148 .map(|result| result.version().clone())
149 }
150
151 pub fn latest_match(
153 &self,
154 package_req: &PackageReq,
155 filter: Option<RemotePackageTypeFilterSpec>,
156 ) -> Option<PackageSpec> {
157 match self.find(package_req, filter, &Progress::NoProgress) {
158 Ok(result) => Some(result.package),
159 Err(_) => None,
160 }
161 }
162}
163
164impl UserData for RemotePackageDB {
165 fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
166 methods.add_method("search", |_, this, package_req: PackageReq| {
167 Ok(this
168 .search(&package_req)
169 .into_iter()
170 .map(|(package_name, versions)| {
171 (
172 package_name.clone(),
173 versions.into_iter().cloned().collect_vec(),
174 )
175 })
176 .collect::<HashMap<_, _>>())
177 });
178 methods.add_method("latest_match", |_, this, package_req| {
179 Ok(this.latest_match(&package_req, None))
180 });
181 }
182}
183
184impl From<Manifest> for RemotePackageDB {
185 fn from(manifest: Manifest) -> Self {
186 Self(Impl::LuarocksManifests(vec![manifest]))
187 }
188}
189
190impl From<LocalPackageLock> for RemotePackageDB {
191 fn from(lock: LocalPackageLock) -> Self {
192 Self(Impl::Lock(lock))
193 }
194}