cargo_util_schemas/core/
source_kind.rs

1use std::cmp::Ordering;
2
3/// The possible kinds of code source.
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub enum SourceKind {
6    /// A git repository.
7    Git(GitReference),
8    /// A local path.
9    Path,
10    /// A remote registry.
11    Registry,
12    /// A sparse registry.
13    SparseRegistry,
14    /// A local filesystem-based registry.
15    LocalRegistry,
16    /// A directory-based registry.
17    Directory,
18}
19
20// The hash here is important for what folder packages get downloaded into.
21// Changes trigger all users to download another copy of their crates.
22// So the `stable_hash` test checks that we only change it intentionally.
23// We implement hash manually to callout the stability impact.
24impl std::hash::Hash for SourceKind {
25    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
26        core::mem::discriminant(self).hash(state);
27        if let SourceKind::Git(git) = self {
28            git.hash(state);
29        }
30    }
31}
32
33impl SourceKind {
34    pub fn protocol(&self) -> Option<&str> {
35        match self {
36            SourceKind::Path => Some("path"),
37            SourceKind::Git(_) => Some("git"),
38            SourceKind::Registry => Some("registry"),
39            // Sparse registry URL already includes the `sparse+` prefix, see `SourceId::new`
40            SourceKind::SparseRegistry => None,
41            SourceKind::LocalRegistry => Some("local-registry"),
42            SourceKind::Directory => Some("directory"),
43        }
44    }
45}
46
47// The ordering here is important for how packages are serialized into lock files.
48// We implement it manually to callout the stability guarantee.
49// See https://github.com/rust-lang/cargo/pull/9397 for the history.
50impl Ord for SourceKind {
51    fn cmp(&self, other: &SourceKind) -> Ordering {
52        match (self, other) {
53            (SourceKind::Path, SourceKind::Path) => Ordering::Equal,
54            (SourceKind::Path, _) => Ordering::Less,
55            (_, SourceKind::Path) => Ordering::Greater,
56
57            (SourceKind::Registry, SourceKind::Registry) => Ordering::Equal,
58            (SourceKind::Registry, _) => Ordering::Less,
59            (_, SourceKind::Registry) => Ordering::Greater,
60
61            (SourceKind::SparseRegistry, SourceKind::SparseRegistry) => Ordering::Equal,
62            (SourceKind::SparseRegistry, _) => Ordering::Less,
63            (_, SourceKind::SparseRegistry) => Ordering::Greater,
64
65            (SourceKind::LocalRegistry, SourceKind::LocalRegistry) => Ordering::Equal,
66            (SourceKind::LocalRegistry, _) => Ordering::Less,
67            (_, SourceKind::LocalRegistry) => Ordering::Greater,
68
69            (SourceKind::Directory, SourceKind::Directory) => Ordering::Equal,
70            (SourceKind::Directory, _) => Ordering::Less,
71            (_, SourceKind::Directory) => Ordering::Greater,
72
73            (SourceKind::Git(a), SourceKind::Git(b)) => a.cmp(b),
74        }
75    }
76}
77
78/// Forwards to `Ord`
79impl PartialOrd for SourceKind {
80    fn partial_cmp(&self, other: &SourceKind) -> Option<Ordering> {
81        Some(self.cmp(other))
82    }
83}
84
85/// Information to find a specific commit in a Git repository.
86#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
87pub enum GitReference {
88    /// From a tag.
89    Tag(String),
90    /// From a branch.
91    Branch(String),
92    /// From a specific revision. Can be a commit hash (either short or full),
93    /// or a named reference like `refs/pull/493/head`.
94    Rev(String),
95    /// The default branch of the repository, the reference named `HEAD`.
96    DefaultBranch,
97}
98
99impl GitReference {
100    pub fn from_query(
101        query_pairs: impl Iterator<Item = (impl AsRef<str>, impl AsRef<str>)>,
102    ) -> Self {
103        let mut reference = GitReference::DefaultBranch;
104        for (k, v) in query_pairs {
105            let v = v.as_ref();
106            match k.as_ref() {
107                // Map older 'ref' to branch.
108                "branch" | "ref" => reference = GitReference::Branch(v.to_owned()),
109
110                "rev" => reference = GitReference::Rev(v.to_owned()),
111                "tag" => reference = GitReference::Tag(v.to_owned()),
112                _ => {}
113            }
114        }
115        reference
116    }
117
118    /// Returns a `Display`able view of this git reference, or None if using
119    /// the head of the default branch
120    pub fn pretty_ref(&self, url_encoded: bool) -> Option<PrettyRef<'_>> {
121        match self {
122            GitReference::DefaultBranch => None,
123            _ => Some(PrettyRef {
124                inner: self,
125                url_encoded,
126            }),
127        }
128    }
129}
130
131/// A git reference that can be `Display`ed
132pub struct PrettyRef<'a> {
133    inner: &'a GitReference,
134    url_encoded: bool,
135}
136
137impl<'a> std::fmt::Display for PrettyRef<'a> {
138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139        let value: &str;
140        match self.inner {
141            GitReference::Branch(s) => {
142                write!(f, "branch=")?;
143                value = s;
144            }
145            GitReference::Tag(s) => {
146                write!(f, "tag=")?;
147                value = s;
148            }
149            GitReference::Rev(s) => {
150                write!(f, "rev=")?;
151                value = s;
152            }
153            GitReference::DefaultBranch => unreachable!(),
154        }
155        if self.url_encoded {
156            for value in url::form_urlencoded::byte_serialize(value.as_bytes()) {
157                write!(f, "{value}")?;
158            }
159        } else {
160            write!(f, "{value}")?;
161        }
162        Ok(())
163    }
164}