Skip to main content

libwally/package_source/
registry.rs

1use 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    /// Create a `Registry` from a registry spec, which usually comes from the
30    /// `registry` field of a package manifest.
31    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}