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("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 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 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 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 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 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}