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