1use path_slash::PathExt;
2use thiserror::Error;
3use url::Url;
4
5pub use crate::manifest::metadata::*;
6use crate::manifest::metadata_from_server::*;
7use crate::manifest::metadata_from_vendor_dir::manifest_from_vendor_dir;
8use crate::package::{RemotePackageType, RemotePackageTypeFilterSpec};
9use crate::progress::{Progress, ProgressBar};
10use crate::{
11 config::Config,
12 package::{PackageReq, RemotePackage},
13 remote_package_source::RemotePackageSource,
14};
15
16pub mod metadata;
17mod metadata_from_server;
18mod metadata_from_vendor_dir;
19
20#[derive(Error, Debug)]
21pub enum ManifestError {
22 #[error(transparent)]
23 Lua(#[from] ManifestLuaError),
24 #[error("failed to fetch manifest from server:\n{0}")]
25 Server(#[from] ManifestFromServerError),
26 #[error("error parsing URL from `vendor-dir`: {0}:")]
27 Vendor(String),
28}
29
30#[derive(Clone, Debug)]
31pub(crate) struct Manifest {
32 server_url: Url,
33 metadata: ManifestMetadata,
34}
35
36impl Manifest {
37 pub fn new(server_url: Url, metadata: ManifestMetadata) -> Self {
38 Self {
39 server_url,
40 metadata,
41 }
42 }
43
44 pub async fn from_config(
45 server_url: Url,
46 config: &Config,
47 progress: &Progress<ProgressBar>,
48 ) -> Result<Self, ManifestError> {
49 if let Some(vendor_dir) = config.vendor_dir() {
50 let server_url: Url = Url::from_file_path(vendor_dir)
51 .map_err(|_err| ManifestError::Vendor(vendor_dir.to_slash_lossy().to_string()))?;
52 return Ok(Self::new(server_url, manifest_from_vendor_dir(vendor_dir)));
53 }
54 let content = manifest_from_cache_or_server(&server_url, config, progress).await?;
55 match ManifestMetadata::new(&content) {
56 Ok(metadata) => Ok(Self::new(server_url, metadata)),
57 Err(_) => {
58 let manifest = manifest_from_server_only(&server_url, config, progress).await?;
59 Ok(Self::new(server_url, ManifestMetadata::new(&manifest)?))
60 }
61 }
62 }
63
64 pub fn server_url(&self) -> &Url {
65 &self.server_url
66 }
67
68 pub fn metadata(&self) -> &ManifestMetadata {
69 &self.metadata
70 }
71
72 pub fn find(
74 &self,
75 package_req: &PackageReq,
76 filter: Option<RemotePackageTypeFilterSpec>,
77 ) -> Option<RemotePackage> {
78 match self.metadata().latest_match(package_req, filter) {
79 None => None,
80 Some((package, package_type)) => {
81 let remote_source = match package_type {
82 RemotePackageType::Rockspec => {
83 RemotePackageSource::LuarocksRockspec(self.server_url().clone())
84 }
85 RemotePackageType::Src => {
86 RemotePackageSource::LuarocksSrcRock(self.server_url().clone())
87 }
88 RemotePackageType::Binary => {
89 RemotePackageSource::LuarocksBinaryRock(self.server_url().clone())
90 }
91 };
92 Some(RemotePackage::new(package, remote_source, None))
93 }
94 }
95 }
96}