1use crate::package;
2use std::{collections::HashMap, sync::Arc};
3
4use crate::{
5 config::{self, Config},
6 lockfile::{IntegrityError, PackageLock},
7 lua_rockspec::RemoteLuaRockspec,
8 manifest::{self, Manifest},
9 manifest_cache::{Closure, ClosureSpec, ManifestCache},
10 operations::PackageInstallSpec,
11 package::{RemotePackage, RemotePackageType, RemotePackageTypeFilterSpec},
12 pipeline::discover::{self, FoundPackage},
13 remote_package_source::RemotePackageSource,
14 workspace::{self, Workspace},
15};
16use itertools::Itertools;
17use tokio::sync::{Mutex, OnceCell};
18use url::Url;
19
20use miette::Diagnostic;
21use thiserror::Error;
22
23#[derive(Clone, Debug)]
24pub struct PackageDB {
25 providers: Vec<PackageProvider>,
26}
27
28#[derive(Error, Debug, Diagnostic)]
29pub enum Error {
30 #[error(transparent)]
31 #[diagnostic(transparent)]
32 Manifest(#[from] manifest::Error),
33 #[error(transparent)]
34 #[diagnostic(transparent)]
35 Config(#[from] config::Error),
36 #[error(transparent)]
37 #[diagnostic(transparent)]
38 Workspace(Box<workspace::Error>),
39}
40
41impl From<workspace::Error> for Error {
42 fn from(err: workspace::Error) -> Self {
43 Self::Workspace(Box::new(err))
44 }
45}
46
47#[derive(Error, Debug, Diagnostic)]
48#[non_exhaustive]
49pub enum SearchError {
50 #[error("no rock that matches '{0}' found in the lockfile or in the manifests")]
51 RockNotFound(package::Req),
52 #[error("error when pulling manifest")]
53 #[diagnostic(forward(0))]
54 Manifest(#[from] manifest::Error),
55}
56
57#[derive(Error, Debug, Diagnostic)]
58#[non_exhaustive]
59pub enum RemotePackageDbIntegrityError {
60 #[error(transparent)]
61 #[diagnostic(transparent)]
62 Lockfile(#[from] IntegrityError),
63}
64
65#[derive(Clone, Debug)]
67pub(crate) enum PackageProvider {
68 Local(Vec<FoundPackage>),
70 Lockfile(Vec<PackageLock>),
72 Manifest(ManifestProvider),
74 Closure(ClosureProvider),
76}
77
78#[derive(Clone, Debug)]
80pub(crate) struct ManifestProvider {
81 server_url: Url,
82 config: Box<Config>,
83 manifest: Arc<OnceCell<Manifest>>,
84}
85
86impl ManifestProvider {
87 fn new(server_url: Url, config: &Config) -> Self {
88 Self {
89 server_url,
90 config: Box::new(config.clone()),
91 manifest: Arc::new(OnceCell::new()),
92 }
93 }
94
95 #[cfg(test)]
96 fn from_manifest(manifest: Manifest, config: &Config) -> Self {
97 Self {
98 server_url: manifest.server_url().clone(),
99 config: Box::new(config.clone()),
100 manifest: Arc::new(OnceCell::from(manifest)),
101 }
102 }
103
104 async fn get(&self) -> Result<&Manifest, manifest::Error> {
105 let manifest = self
106 .manifest
107 .get_or_try_init(|| Manifest::from_config(self.server_url.clone(), &self.config));
108 Box::pin(manifest).await
109 }
110}
111
112impl PackageProvider {
113 async fn find(
114 &self,
115 package_req: &package::Req,
116 filter: Option<RemotePackageTypeFilterSpec>,
117 ) -> Result<Option<RemotePackage>, SearchError> {
118 match self {
119 Self::Local(packages) => Ok(packages
120 .iter()
121 .find(|found| found.remote.spec.name() == package_req.name())
122 .map(|found| found.remote.clone())),
123 Self::Lockfile(locks) => Ok(locks
124 .iter()
125 .filter_map(|lock| lock.has_rock(package_req, filter.clone()))
126 .map(|locked_package| {
127 RemotePackage::new(
128 locked_package.to_package(),
129 locked_package.source().clone(),
130 locked_package.source_url().cloned(),
131 )
132 })
133 .next()),
134 Self::Manifest(manifest) => Ok(manifest.get().await?.find(package_req, filter)),
135 Self::Closure(closure) => Ok(closure.find(package_req, filter).await),
136 }
137 }
138
139 async fn resolve(
140 &self,
141 spec: &PackageInstallSpec,
142 config: &Config,
143 ) -> Result<Option<FoundPackage>, discover::Error> {
144 match self {
145 Self::Local(packages) => Ok(packages
146 .iter()
147 .find(|package| package.remote.spec.name() == spec.package.name())
148 .cloned()),
149 Self::Closure(closure) => closure.resolve(spec, config).await,
150 Self::Lockfile(_) | Self::Manifest(_) => Ok(None),
151 }
152 }
153
154 async fn search(
155 &self,
156 package_req: &package::Req,
157 ) -> Result<Vec<(package::Name, Vec<package::Version>)>, SearchError> {
158 match self {
159 Self::Local(packages) => Ok(packages
160 .iter()
161 .filter_map(|package| {
162 let name = package.remote.spec.name();
163 if name.to_string().contains(&package_req.name().to_string())
164 && package_req
165 .version_req()
166 .matches(package.remote.spec.version())
167 {
168 Some((name.clone(), vec![package.remote.spec.version().clone()]))
169 } else {
170 None
171 }
172 })
173 .collect()),
174 Self::Lockfile(locks) => Ok(locks
177 .iter()
178 .flat_map(|lock| lock.rocks().values())
179 .filter_map(|package| {
180 let name = package.name();
181 if name.to_string().contains(&package_req.name().to_string()) {
182 Some((name.clone(), vec![package.version().clone()]))
183 } else {
184 None
185 }
186 })
187 .collect()),
188 Self::Manifest(manifest) => Ok(manifest
189 .get()
190 .await?
191 .metadata()
192 .repository
193 .iter()
194 .filter_map(|(name, elements)| {
195 if name.to_string().contains(&package_req.name().to_string()) {
196 Some((
197 name.clone(),
198 elements
199 .keys()
200 .filter(|version| package_req.version_req().matches(version))
201 .sorted_by(|a, b| Ord::cmp(b, a))
202 .cloned()
203 .collect_vec(),
204 ))
205 } else {
206 None
207 }
208 })
209 .collect()),
210 Self::Closure(_) => Ok(Vec::new()),
212 }
213 }
214}
215
216#[derive(Clone, Debug)]
217pub(crate) struct ClosureProvider {
218 cache: ManifestCache,
219 rock_server: Url,
220 loaded: Arc<Mutex<HashMap<package::Name, Arc<Closure>>>>,
221}
222
223impl ClosureProvider {
224 fn new(cache: ManifestCache, config: &Config) -> Self {
225 Self {
226 cache,
227 rock_server: config.server().clone(),
228 loaded: Arc::new(Mutex::new(HashMap::new())),
229 }
230 }
231
232 async fn load_closure_for(&self, req: &package::Req) -> Option<Arc<Closure>> {
233 if let Some(closure) = self.loaded.lock().await.get(req.name()) {
234 return Some(closure.clone());
235 }
236
237 let versions = self.cache.versions(req.name()).await.ok()?;
238 let chosen = versions
239 .iter()
240 .find(|version| req.version_req().matches(version))?;
241
242 let is_latest = versions.first().is_some_and(|latest| latest == chosen);
243 let closure = if is_latest {
244 self.cache.latest_closure(req.name()).await.ok()?
245 } else {
246 self.cache.closure(req.name(), chosen).await.ok()?
247 };
248 let closure = Arc::new(closure);
249
250 self.loaded
251 .lock()
252 .await
253 .insert(req.name().clone(), closure.clone());
254 Some(closure)
255 }
256
257 fn remote_package(&self, spec: &ClosureSpec, package_type: RemotePackageType) -> RemotePackage {
258 let source = match package_type {
259 RemotePackageType::Rockspec => {
260 RemotePackageSource::LuarocksRockspec(self.rock_server.clone())
261 }
262 RemotePackageType::Src => {
263 RemotePackageSource::LuarocksSrcRock(self.rock_server.clone())
264 }
265 RemotePackageType::Binary => {
266 RemotePackageSource::LuarocksBinaryRock(self.rock_server.clone())
267 }
268 };
269 RemotePackage::new(
270 package::Spec::new(spec.package().clone(), spec.version().clone()),
271 source,
272 None,
273 )
274 }
275
276 async fn find(
277 &self,
278 req: &package::Req,
279 filter: Option<RemotePackageTypeFilterSpec>,
280 ) -> Option<RemotePackage> {
281 let closure = self.load_closure_for(req).await?;
282 let (spec, package_type) = closure.spec(req, filter.as_ref())?;
283 Some(self.remote_package(spec, package_type))
284 }
285
286 async fn resolve(
287 &self,
288 spec: &PackageInstallSpec,
289 _config: &Config,
290 ) -> Result<Option<FoundPackage>, discover::Error> {
291 let Some(closure) = self.load_closure_for(&spec.package).await else {
292 return Ok(None);
293 };
294 let Some((closure_spec, package_type)) = closure.spec(&spec.package, None) else {
295 return Ok(None);
296 };
297 if package_type != RemotePackageType::Rockspec {
301 return Ok(None);
302 }
303 let rockspec = RemoteLuaRockspec::new(closure_spec.rockspec_raw()).map_err(|err| {
304 discover::Error::Download(
305 spec.package.clone(),
306 Box::new(crate::operations::SearchAndDownloadError::Rockspec(
307 Box::new(err),
308 )),
309 )
310 })?;
311 let remote = self.remote_package(closure_spec, package_type);
312 Ok(Some(FoundPackage {
313 remote,
314 rockspec,
315 artifact: None,
316 }))
317 }
318}
319
320impl PackageDB {
321 #[tracing::instrument(level = "trace", skip_all)]
322 pub async fn from_config(config: &Config) -> Result<Self, Error> {
323 let mut providers = Vec::new();
324 if let Some(cache) = ManifestCache::from_config(config) {
325 providers.push(PackageProvider::Closure(ClosureProvider::new(
326 cache, config,
327 )));
328 }
329 for server in config.enabled_dev_servers()? {
330 providers.push(PackageProvider::Manifest(ManifestProvider::new(
331 server, config,
332 )));
333 }
334 for server in config.extra_servers() {
335 providers.push(PackageProvider::Manifest(ManifestProvider::new(
336 server.clone(),
337 config,
338 )));
339 }
340 providers.push(PackageProvider::Manifest(ManifestProvider::new(
341 config.server().clone(),
342 config,
343 )));
344 Ok(Self { providers })
345 }
346
347 #[cfg(test)]
349 pub(crate) fn from_manifest(manifest: Manifest, config: &Config) -> Self {
350 Self {
351 providers: vec![PackageProvider::Manifest(ManifestProvider::from_manifest(
352 manifest, config,
353 ))],
354 }
355 }
356
357 pub(crate) async fn open(config: &Config, workspace: &Workspace) -> Result<Self, Error> {
360 Ok(Self::from_config(config)
361 .await?
362 .with_locks(workspace.lockfile()?.local_pkg_locks()))
363 }
364
365 #[cfg(test)]
366 pub(crate) fn from_local(packages: Vec<FoundPackage>) -> Self {
367 Self {
368 providers: vec![PackageProvider::Local(packages)],
369 }
370 }
371
372 pub(crate) fn with_local(mut self, packages: Vec<FoundPackage>) -> Self {
374 self.providers
375 .retain(|provider| !matches!(provider, PackageProvider::Local(_)));
376 self.providers.insert(0, PackageProvider::Local(packages));
377 self
378 }
379
380 pub(crate) fn with_locks(mut self, locks: Vec<PackageLock>) -> Self {
382 self.providers
383 .retain(|provider| !matches!(provider, PackageProvider::Lockfile(_)));
384 let index = self
385 .providers
386 .iter()
387 .position(|provider| {
388 matches!(
389 provider,
390 PackageProvider::Closure(_) | PackageProvider::Manifest(_)
391 )
392 })
393 .unwrap_or(self.providers.len());
394 self.providers
395 .insert(index, PackageProvider::Lockfile(locks));
396 self
397 }
398
399 pub(crate) fn has_remote_sources(&self) -> bool {
400 self.providers.iter().any(|provider| {
401 matches!(
402 provider,
403 PackageProvider::Lockfile(_)
404 | PackageProvider::Manifest(_)
405 | PackageProvider::Closure(_)
406 )
407 })
408 }
409
410 #[tracing::instrument(
412 name = "Searching",
413 level = "info",
414 skip_all,
415 fields(package = package_req.to_string()),
416 )]
417 pub(crate) async fn find(
418 &self,
419 package_req: &package::Req,
420 filter: Option<RemotePackageTypeFilterSpec>,
421 ) -> Result<RemotePackage, SearchError> {
422 for provider in &self.providers {
423 if let Some(package) = provider.find(package_req, filter.clone()).await? {
424 return Ok(package);
425 }
426 }
427
428 Err(SearchError::RockNotFound(package_req.clone()))
429 }
430
431 pub(crate) async fn resolve(
433 &self,
434 spec: &PackageInstallSpec,
435 config: &Config,
436 ) -> Result<Option<FoundPackage>, discover::Error> {
437 for provider in &self.providers {
438 if let Some(found) = provider.resolve(spec, config).await? {
441 return Ok(Some(found));
442 }
443
444 if provider.find(&spec.package, None).await?.is_some() {
447 break;
448 }
449 }
450 Ok(None)
451 }
452
453 pub async fn search(
455 &self,
456 package_req: &package::Req,
457 ) -> Result<Vec<(package::Name, Vec<package::Version>)>, SearchError> {
458 let mut results = Vec::new();
459 for provider in &self.providers {
460 results.extend(provider.search(package_req).await?);
461 }
462 Ok(results)
463 }
464
465 pub(crate) async fn latest_version(
467 &self,
468 rock_name: &package::Name,
469 ) -> Option<package::Version> {
470 self.latest_match(&rock_name.clone().into(), None)
471 .await
472 .map(|result| result.version().clone())
473 }
474
475 pub async fn latest_match(
477 &self,
478 package_req: &package::Req,
479 filter: Option<RemotePackageTypeFilterSpec>,
480 ) -> Option<package::Spec> {
481 match self.find(package_req, filter).await {
482 Ok(result) => Some(result.spec),
483 Err(_) => None,
484 }
485 }
486}
487
488impl From<Vec<PackageLock>> for PackageDB {
489 fn from(locks: Vec<PackageLock>) -> Self {
490 Self {
491 providers: vec![PackageProvider::Lockfile(locks)],
492 }
493 }
494}