1use miette::Diagnostic;
2use path_slash::PathExt;
3use thiserror::Error;
4use url::Url;
5
6pub use crate::manifest::metadata::*;
7use crate::manifest::metadata_from_server::*;
8use crate::manifest::metadata_from_vendor_dir::manifest_from_vendor_dir;
9use crate::package::{RemotePackageType, RemotePackageTypeFilterSpec};
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, Diagnostic)]
21#[non_exhaustive]
22pub enum ManifestError {
23 #[error(transparent)]
24 #[diagnostic(transparent)]
25 Lua(#[from] ManifestLuaError),
26 #[error("failed to fetch manifest from {url}:\n{source}")]
27 #[diagnostic(forward(source))]
28 Server {
29 url: String,
30 source: Box<ManifestFromServerError>,
31 },
32 #[error("error parsing URL from `vendor-dir`: {0}:")]
33 Vendor(String),
34}
35
36#[derive(Clone, Debug)]
37pub(crate) struct Manifest {
38 server_url: Url,
39 metadata: ManifestMetadata,
40 default_filter: RemotePackageTypeFilterSpec,
41}
42
43impl Manifest {
44 pub fn new(
45 server_url: Url,
46 metadata: ManifestMetadata,
47 default_filter: RemotePackageTypeFilterSpec,
48 ) -> Self {
49 Self {
50 server_url,
51 metadata,
52 default_filter,
53 }
54 }
55
56 #[tracing::instrument(level = "trace", skip(config))]
57 pub async fn from_config(server_url: Url, config: &Config) -> Result<Self, ManifestError> {
58 if let Some(vendor_dir) = config.vendor_dir() {
59 let server_url: Url = Url::from_file_path(vendor_dir)
60 .map_err(|_err| ManifestError::Vendor(vendor_dir.to_slash_lossy().to_string()))?;
61 return Ok(Self::new(
62 server_url,
63 manifest_from_vendor_dir(vendor_dir),
64 config.package_types().clone(),
65 ));
66 }
67 let content = manifest_from_cache_or_server(&server_url, config)
68 .await
69 .map_err(|source| ManifestError::Server {
70 url: server_url.to_string(),
71 source: Box::new(source),
72 })?;
73 match ManifestMetadata::new(&content) {
74 Ok(metadata) => Ok(Self::new(
75 server_url,
76 metadata,
77 config.package_types().clone(),
78 )),
79 Err(_) => {
80 let manifest = manifest_from_server_only(&server_url, config)
81 .await
82 .map_err(|source| ManifestError::Server {
83 url: server_url.to_string(),
84 source: Box::new(source),
85 })?;
86 Ok(Self::new(
87 server_url,
88 ManifestMetadata::new(&manifest)?,
89 config.package_types().clone(),
90 ))
91 }
92 }
93 }
94
95 pub fn server_url(&self) -> &Url {
96 &self.server_url
97 }
98
99 pub fn metadata(&self) -> &ManifestMetadata {
100 &self.metadata
101 }
102
103 pub fn find(
105 &self,
106 package_req: &PackageReq,
107 filter: Option<RemotePackageTypeFilterSpec>,
108 ) -> Option<RemotePackage> {
109 let filter = filter.as_ref().unwrap_or(&self.default_filter);
110 match self.metadata().latest_match(package_req, filter) {
111 None => None,
112 Some((package, package_type)) => {
113 let remote_source = match package_type {
114 RemotePackageType::Rockspec => {
115 RemotePackageSource::LuarocksRockspec(self.server_url().clone())
116 }
117 RemotePackageType::Src => {
118 RemotePackageSource::LuarocksSrcRock(self.server_url().clone())
119 }
120 RemotePackageType::Binary => {
121 RemotePackageSource::LuarocksBinaryRock(self.server_url().clone())
122 }
123 };
124 Some(RemotePackage::new(package, remote_source, None))
125 }
126 }
127 }
128}