binstalk_manifests/cargo_crates_v1/
crate_version_source.rs1use std::{
2 borrow::Cow,
3 fmt::{self, Write as _},
4 str::FromStr,
5};
6
7use binstalk_types::maybe_owned::MaybeOwned;
8use compact_str::CompactString;
9use miette::Diagnostic;
10use semver::Version;
11use serde::{Deserialize, Deserializer, Serialize, Serializer};
12use thiserror::Error;
13use url::Url;
14
15use crate::crate_info::{CrateInfo, CrateSource, SourceType};
16
17#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
18pub struct CrateVersionSource {
19 pub name: CompactString,
20 pub version: Version,
21 pub source: Source<'static>,
22}
23
24impl From<&CrateInfo> for CrateVersionSource {
25 fn from(metadata: &CrateInfo) -> Self {
26 use SourceType::*;
27
28 let url = metadata.source.url.clone();
29
30 super::CrateVersionSource {
31 name: metadata.name.clone(),
32 version: metadata.current_version.clone(),
33 source: match metadata.source.source_type {
34 Git => Source::Git(url),
35 Path => Source::Path(url),
36 Registry => Source::Registry(url),
37 Sparse => Source::Sparse(url),
38 },
39 }
40 }
41}
42
43#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
44pub enum Source<'a> {
45 Git(MaybeOwned<'a, Url>),
46 Path(MaybeOwned<'a, Url>),
47 Registry(MaybeOwned<'a, Url>),
48 Sparse(MaybeOwned<'a, Url>),
49}
50
51impl<'a> From<&'a CrateSource> for Source<'a> {
52 fn from(source: &'a CrateSource) -> Self {
53 use SourceType::*;
54
55 let url = MaybeOwned::Borrowed(source.url.as_ref());
56
57 match source.source_type {
58 Git => Self::Git(url),
59 Path => Self::Path(url),
60 Registry => Self::Registry(url),
61 Sparse => Self::Sparse(url),
62 }
63 }
64}
65
66impl FromStr for CrateVersionSource {
67 type Err = CvsParseError;
68 fn from_str(s: &str) -> Result<Self, Self::Err> {
69 match s.splitn(3, ' ').collect::<Vec<_>>()[..] {
70 [name, version, source] => {
71 let version = version.parse()?;
72 let source = match source
73 .trim_matches(&['(', ')'][..])
74 .splitn(2, '+')
75 .collect::<Vec<_>>()[..]
76 {
77 ["git", url] => Source::Git(Url::parse(url)?.into()),
78 ["path", url] => Source::Path(Url::parse(url)?.into()),
79 ["registry", url] => Source::Registry(Url::parse(url)?.into()),
80 [kind, arg] => {
81 return Err(CvsParseError::UnknownSourceType {
82 kind: kind.to_string().into_boxed_str(),
83 arg: arg.to_string().into_boxed_str(),
84 })
85 }
86 _ => return Err(CvsParseError::BadSource),
87 };
88 Ok(Self {
89 name: name.into(),
90 version,
91 source,
92 })
93 }
94 _ => Err(CvsParseError::BadFormat),
95 }
96 }
97}
98
99#[derive(Debug, Diagnostic, Error)]
100#[non_exhaustive]
101pub enum CvsParseError {
102 #[error("Failed to parse url in cvs: {0}")]
103 UrlParse(#[from] url::ParseError),
104
105 #[error("Failed to parse version in cvs: {0}")]
106 VersionParse(#[from] semver::Error),
107
108 #[error("unknown source type {kind}+{arg}")]
109 UnknownSourceType { kind: Box<str>, arg: Box<str> },
110
111 #[error("bad source format")]
112 BadSource,
113
114 #[error("bad CVS format")]
115 BadFormat,
116}
117
118impl fmt::Display for CrateVersionSource {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 let Self {
121 name,
122 version,
123 source,
124 } = &self;
125 write!(f, "{name} {version} ({source})")
126 }
127}
128
129impl fmt::Display for Source<'_> {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 match self {
132 Source::Git(url) => write!(f, "git+{url}"),
133 Source::Path(url) => write!(f, "path+{url}"),
134 Source::Registry(url) => write!(f, "registry+{url}"),
135 Source::Sparse(url) => {
136 let url = url.as_str();
137 write!(f, "sparse+{url}")?;
138 if url.ends_with("/") {
139 Ok(())
140 } else {
141 f.write_char('/')
142 }
143 }
144 }
145 }
146}
147
148impl Serialize for CrateVersionSource {
149 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
150 where
151 S: Serializer,
152 {
153 serializer.serialize_str(&self.to_string())
154 }
155}
156
157impl<'de> Deserialize<'de> for CrateVersionSource {
158 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
159 where
160 D: Deserializer<'de>,
161 {
162 let s = Cow::<'_, str>::deserialize(deserializer)?;
163 Self::from_str(&s).map_err(serde::de::Error::custom)
164 }
165}