[go: up one dir, main page]

uv_auth/
index.rs

1use std::fmt::{self, Display, Formatter};
2
3use rustc_hash::FxHashSet;
4use url::Url;
5use uv_redacted::DisplaySafeUrl;
6
7/// When to use authentication.
8#[derive(
9    Copy,
10    Clone,
11    Debug,
12    Default,
13    Hash,
14    Eq,
15    PartialEq,
16    Ord,
17    PartialOrd,
18    serde::Serialize,
19    serde::Deserialize,
20)]
21#[serde(rename_all = "kebab-case")]
22#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
23pub enum AuthPolicy {
24    /// Authenticate when necessary.
25    ///
26    /// If credentials are provided, they will be used. Otherwise, an unauthenticated request will
27    /// be attempted first. If the request fails, uv will search for credentials. If credentials are
28    /// found, an authenticated request will be attempted.
29    #[default]
30    Auto,
31    /// Always authenticate.
32    ///
33    /// If credentials are not provided, uv will eagerly search for credentials. If credentials
34    /// cannot be found, uv will error instead of attempting an unauthenticated request.
35    Always,
36    /// Never authenticate.
37    ///
38    /// If credentials are provided, uv will error. uv will not search for credentials.
39    Never,
40}
41
42impl Display for AuthPolicy {
43    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
44        match self {
45            Self::Auto => write!(f, "auto"),
46            Self::Always => write!(f, "always"),
47            Self::Never => write!(f, "never"),
48        }
49    }
50}
51
52// TODO(john): We are not using `uv_distribution_types::Index` directly
53// here because it would cause circular crate dependencies. However, this
54// could potentially make sense for a future refactor.
55#[derive(Debug, Clone, Hash, Eq, PartialEq)]
56pub struct Index {
57    pub url: DisplaySafeUrl,
58    /// The root endpoint where authentication is applied.
59    /// For PEP 503 endpoints, this excludes `/simple`.
60    pub root_url: DisplaySafeUrl,
61    pub auth_policy: AuthPolicy,
62}
63
64impl Index {
65    pub fn is_prefix_for(&self, url: &Url) -> bool {
66        if self.root_url.scheme() != url.scheme()
67            || self.root_url.host_str() != url.host_str()
68            || self.root_url.port_or_known_default() != url.port_or_known_default()
69        {
70            return false;
71        }
72
73        url.path().starts_with(self.root_url.path())
74    }
75}
76
77// TODO(john): Multiple methods in this struct need to iterate over
78// all the indexes in the set. There are probably not many URLs to
79// iterate through, but we could use a trie instead of a HashSet here
80// for more efficient search.
81#[derive(Debug, Default, Clone, Eq, PartialEq)]
82pub struct Indexes(FxHashSet<Index>);
83
84impl Indexes {
85    pub fn new() -> Self {
86        Self(FxHashSet::default())
87    }
88
89    /// Create a new [`Indexes`] instance from an iterator of [`Index`]s.
90    pub fn from_indexes(urls: impl IntoIterator<Item = Index>) -> Self {
91        let mut index_urls = Self::new();
92        for url in urls {
93            index_urls.0.insert(url);
94        }
95        index_urls
96    }
97
98    /// Get the index for a URL if one exists.
99    pub fn index_for(&self, url: &Url) -> Option<&Index> {
100        self.find_prefix_index(url)
101    }
102
103    /// Get the [`AuthPolicy`] for a URL.
104    pub fn auth_policy_for(&self, url: &Url) -> AuthPolicy {
105        self.find_prefix_index(url)
106            .map(|index| index.auth_policy)
107            .unwrap_or(AuthPolicy::Auto)
108    }
109
110    fn find_prefix_index(&self, url: &Url) -> Option<&Index> {
111        self.0.iter().find(|&index| index.is_prefix_for(url))
112    }
113}