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 #[serde(skip_serializing_if = "Option::is_none")]
74 pub git_lfs: Option<bool>, }
76
77#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
78#[serde(rename_all = "snake_case")]
79pub enum VcsKind {
80 Git,
81 Hg,
82 Bzr,
83 Svn,
84}
85
86impl std::fmt::Display for VcsKind {
87 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88 match self {
89 Self::Git => write!(f, "git"),
90 Self::Hg => write!(f, "hg"),
91 Self::Bzr => write!(f, "bzr"),
92 Self::Svn => write!(f, "svn"),
93 }
94 }
95}
96
97impl TryFrom<&DirectUrl> for DisplaySafeUrl {
98 type Error = DisplaySafeUrlError;
99
100 fn try_from(value: &DirectUrl) -> Result<Self, Self::Error> {
101 match value {
102 DirectUrl::LocalDirectory {
103 url,
104 subdirectory,
105 dir_info: _,
106 } => {
107 let mut url = Self::parse(url)?;
108 if let Some(subdirectory) = subdirectory {
109 url.set_fragment(Some(&format!("subdirectory={}", subdirectory.display())));
110 }
111 Ok(url)
112 }
113 DirectUrl::ArchiveUrl {
114 url,
115 subdirectory,
116 archive_info: _,
117 } => {
118 let mut url = Self::parse(url)?;
119 if let Some(subdirectory) = subdirectory {
120 url.set_fragment(Some(&format!("subdirectory={}", subdirectory.display())));
121 }
122 Ok(url)
123 }
124 DirectUrl::VcsUrl {
125 url,
126 vcs_info,
127 subdirectory,
128 } => {
129 let mut url = Self::parse(&format!("{}+{}", vcs_info.vcs, url))?;
130 if let Some(commit_id) = &vcs_info.commit_id {
131 let path = format!("{}@{commit_id}", url.path());
132 url.set_path(&path);
133 } else if let Some(requested_revision) = &vcs_info.requested_revision {
134 let path = format!("{}@{requested_revision}", url.path());
135 url.set_path(&path);
136 }
137 let mut frags: Vec<String> = Vec::new();
138 if let Some(subdirectory) = subdirectory {
139 frags.push(format!("subdirectory={}", subdirectory.display()));
140 }
141 if let Some(true) = vcs_info.git_lfs {
143 frags.push("lfs=true".to_string());
144 }
145 if !frags.is_empty() {
146 url.set_fragment(Some(&frags.join("&")));
147 }
148 Ok(url)
149 }
150 }
151 }
152}