uv_pypi_types/
direct_url.rs1use std::collections::BTreeMap;
2use std::path::Path;
3
4use serde::{Deserialize, Serialize};
5use uv_redacted::{DisplaySafeUrl, DisplaySafeUrlError};
6
7#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case", untagged)]
12pub enum DirectUrl {
13 LocalDirectory {
18 url: String,
19 dir_info: DirInfo,
20 #[serde(skip_serializing_if = "Option::is_none")]
21 subdirectory: Option<Box<Path>>,
22 },
23 ArchiveUrl {
28 url: String,
33 archive_info: ArchiveInfo,
34 #[serde(skip_serializing_if = "Option::is_none")]
35 subdirectory: Option<Box<Path>>,
36 },
37 VcsUrl {
42 url: String,
43 vcs_info: VcsInfo,
44 #[serde(skip_serializing_if = "Option::is_none")]
45 subdirectory: Option<Box<Path>>,
46 },
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
50#[serde(rename_all = "snake_case")]
51pub struct DirInfo {
52 #[serde(skip_serializing_if = "Option::is_none")]
53 pub editable: Option<bool>,
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
57#[serde(rename_all = "snake_case")]
58pub struct ArchiveInfo {
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub hash: Option<String>,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub hashes: Option<BTreeMap<String, String>>,
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
66#[serde(rename_all = "snake_case")]
67pub struct VcsInfo {
68 pub vcs: VcsKind,
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub commit_id: Option<String>,
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub requested_revision: Option<String>,
73}
74
75#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
76#[serde(rename_all = "snake_case")]
77pub enum VcsKind {
78 Git,
79 Hg,
80 Bzr,
81 Svn,
82}
83
84impl std::fmt::Display for VcsKind {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 match self {
87 Self::Git => write!(f, "git"),
88 Self::Hg => write!(f, "hg"),
89 Self::Bzr => write!(f, "bzr"),
90 Self::Svn => write!(f, "svn"),
91 }
92 }
93}
94
95impl TryFrom<&DirectUrl> for DisplaySafeUrl {
96 type Error = DisplaySafeUrlError;
97
98 fn try_from(value: &DirectUrl) -> Result<Self, Self::Error> {
99 match value {
100 DirectUrl::LocalDirectory {
101 url,
102 subdirectory,
103 dir_info: _,
104 } => {
105 let mut url = Self::parse(url)?;
106 if let Some(subdirectory) = subdirectory {
107 url.set_fragment(Some(&format!("subdirectory={}", subdirectory.display())));
108 }
109 Ok(url)
110 }
111 DirectUrl::ArchiveUrl {
112 url,
113 subdirectory,
114 archive_info: _,
115 } => {
116 let mut url = Self::parse(url)?;
117 if let Some(subdirectory) = subdirectory {
118 url.set_fragment(Some(&format!("subdirectory={}", subdirectory.display())));
119 }
120 Ok(url)
121 }
122 DirectUrl::VcsUrl {
123 url,
124 vcs_info,
125 subdirectory,
126 } => {
127 let mut url = Self::parse(&format!("{}+{}", vcs_info.vcs, url))?;
128 if let Some(commit_id) = &vcs_info.commit_id {
129 let path = format!("{}@{commit_id}", url.path());
130 url.set_path(&path);
131 } else if let Some(requested_revision) = &vcs_info.requested_revision {
132 let path = format!("{}@{requested_revision}", url.path());
133 url.set_path(&path);
134 }
135 if let Some(subdirectory) = subdirectory {
136 url.set_fragment(Some(&format!("subdirectory={}", subdirectory.display())));
137 }
138 Ok(url)
139 }
140 }
141 }
142}