libwally/package_source/
registry.rs1use std::io::Read;
2use std::sync::Arc;
3
4use anyhow::bail;
5use once_cell::sync::OnceCell;
6use reqwest::{blocking::Client, header::AUTHORIZATION};
7use url::Url;
8
9use crate::auth::AuthStore;
10use crate::manifest::Manifest;
11use crate::package_id::PackageId;
12use crate::package_index::PackageIndex;
13use crate::package_req::PackageReq;
14use crate::package_source::PackageContents;
15
16use super::{PackageSourceId, PackageSourceProvider};
17
18const VERSION: &str = env!("CARGO_PKG_VERSION");
19
20#[derive(Clone)]
21pub struct Registry {
22 index_url: Url,
23 auth_token: OnceCell<Option<Arc<str>>>,
24 index: OnceCell<Arc<PackageIndex>>,
25 client: Client,
26}
27
28impl Registry {
29 pub fn from_registry_spec(spec: &str) -> anyhow::Result<Self> {
32 let index_url = Url::parse(spec)?;
33
34 Ok(Self {
35 index_url,
36 auth_token: OnceCell::new(),
37 index: OnceCell::new(),
38 client: Client::new(),
39 })
40 }
41
42 fn auth_token(&self) -> anyhow::Result<Option<Arc<str>>> {
43 self.auth_token
44 .get_or_try_init(|| {
45 let store = AuthStore::load()?;
46 let token = store.tokens.get(self.api_url()?.as_str());
47 match token {
48 Some(token) => Ok(Some(Arc::from(token.as_str()))),
49 None => Ok(None),
50 }
51 })
52 .map(|token| token.clone())
53 }
54
55 fn index(&self) -> anyhow::Result<&Arc<PackageIndex>> {
56 self.index
57 .get_or_try_init(|| Ok(Arc::new(PackageIndex::new(&self.index_url, None)?)))
58 }
59
60 fn api_url(&self) -> anyhow::Result<Url> {
61 let config = self.index()?.config()?;
62 Ok(config.api)
63 }
64}
65
66impl PackageSourceProvider for Registry {
67 fn update(&self) -> anyhow::Result<()> {
68 self.index()?.update()
69 }
70
71 fn query(&self, package_req: &PackageReq) -> anyhow::Result<Vec<Manifest>> {
72 let metadata = self.index()?.get_package_metadata(package_req.name())?;
73 let versions: Vec<_> = metadata
74 .versions
75 .iter()
76 .filter(|manifest| {
77 package_req.matches(&manifest.package.name, &manifest.package.version)
78 })
79 .cloned()
80 .collect();
81
82 Ok(versions)
83 }
84
85 fn download_package(&self, package_id: &PackageId) -> anyhow::Result<PackageContents> {
86 let path = format!(
87 "/v1/package-contents/{}/{}/{}",
88 package_id.name().scope(),
89 package_id.name().name(),
90 package_id.version()
91 );
92
93 let url = self.api_url()?.join(&path)?;
94
95 let mut request = self.client.get(url).header("Wally-Version", VERSION);
96
97 if let Some(token) = self.auth_token()? {
98 request = request.header(AUTHORIZATION, format!("Bearer {}", token));
99 }
100 let mut response = request.send()?;
101
102 if !response.status().is_success() {
103 bail!(
104 "Failed to download package {} from registry: {}\n{} {}",
105 package_id,
106 self.api_url()?,
107 response.status(),
108 response.text()?
109 );
110 }
111
112 let mut data = Vec::new();
113 response.read_to_end(&mut data)?;
114
115 Ok(PackageContents::from_buffer(data))
116 }
117
118 fn fallback_sources(&self) -> anyhow::Result<Vec<PackageSourceId>> {
119 let fallback_registries = self.index()?.config()?.fallback_registries;
120
121 let sources = fallback_registries
122 .into_iter()
123 .map(PackageSourceId::Git)
124 .collect();
125
126 Ok(sources)
127 }
128}