1use std::{convert::Infallible, fmt::Display, str::FromStr};
2
3use serde::{Deserialize, Serialize};
4use strum::{Display, EnumString};
5
6use crate::{Error, Name};
7
8#[derive(Clone, Copy, Debug, Display, EnumString, Eq, PartialEq, Serialize)]
26pub enum PackageType {
27 #[strum(to_string = "debug")]
29 Debug,
30 #[strum(to_string = "pkg")]
32 Package,
33 #[strum(to_string = "src")]
35 Source,
36 #[strum(to_string = "split")]
38 Split,
39}
40
41#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
63pub struct PackageDescription(String);
64
65impl PackageDescription {
66 pub fn new(description: &str) -> Self {
68 Self::from(description)
69 }
70}
71
72impl FromStr for PackageDescription {
73 type Err = Infallible;
74
75 fn from_str(s: &str) -> Result<Self, Self::Err> {
76 Ok(Self::from(s))
77 }
78}
79
80impl AsRef<str> for PackageDescription {
81 fn as_ref(&self) -> &str {
83 &self.0
84 }
85}
86
87impl From<&str> for PackageDescription {
88 fn from(value: &str) -> Self {
94 let mut description = value.trim().replace(['\n', '\r', '\t'], " ");
96
97 let mut previous = ' ';
99 description.retain(|ch| {
100 if ch == ' ' && previous == ' ' {
101 return false;
102 };
103 previous = ch;
104 true
105 });
106
107 Self(description)
108 }
109}
110
111impl Display for PackageDescription {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 write!(f, "{}", self.0)
114 }
115}
116
117pub type PackageBaseName = Name;
138
139#[derive(Clone, Debug, PartialEq, Serialize)]
143pub struct ExtraData {
144 key: String,
145 value: String,
146}
147
148impl ExtraData {
149 pub fn new(key: String, value: String) -> Self {
151 Self { key, value }
152 }
153
154 pub fn key(&self) -> &str {
156 &self.key
157 }
158
159 pub fn value(&self) -> &str {
161 &self.value
162 }
163}
164
165impl FromStr for ExtraData {
166 type Err = Error;
167
168 fn from_str(s: &str) -> Result<Self, Self::Err> {
192 const DELIMITER: char = '=';
193 let mut parts = s.splitn(2, DELIMITER);
194 let key = parts
195 .next()
196 .map(|v| v.trim())
197 .filter(|v| !v.is_empty())
198 .ok_or(Error::MissingComponent { component: "key" })?;
199 let value = parts
200 .next()
201 .map(|v| v.trim())
202 .filter(|v| !v.is_empty())
203 .ok_or(Error::MissingComponent { component: "value" })?;
204 Ok(Self::new(key.to_string(), value.to_string()))
205 }
206}
207
208impl Display for ExtraData {
209 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
210 write!(f, "{}={}", self.key, self.value)
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use std::str::FromStr;
217
218 use rstest::rstest;
219
220 use super::*;
221
222 #[rstest]
223 #[case("debug", Ok(PackageType::Debug))]
224 #[case("pkg", Ok(PackageType::Package))]
225 #[case("src", Ok(PackageType::Source))]
226 #[case("split", Ok(PackageType::Split))]
227 #[case("foo", Err(strum::ParseError::VariantNotFound))]
228 fn pkgtype_from_string(
229 #[case] from_str: &str,
230 #[case] result: Result<PackageType, strum::ParseError>,
231 ) {
232 assert_eq!(PackageType::from_str(from_str), result);
233 }
234
235 #[rstest]
236 #[case(PackageType::Debug, "debug")]
237 #[case(PackageType::Package, "pkg")]
238 #[case(PackageType::Source, "src")]
239 #[case(PackageType::Split, "split")]
240 fn pkgtype_format_string(#[case] pkgtype: PackageType, #[case] pkgtype_str: &str) {
241 assert_eq!(pkgtype_str, format!("{pkgtype}"));
242 }
243
244 #[rstest]
245 #[case("key=value", "key", "value")]
246 #[case("pkgtype=debug", "pkgtype", "debug")]
247 #[case("test-123@.foo_+=1000", "test-123@.foo_+", "1000")]
248 fn extra_data_from_str(
249 #[case] data: &str,
250 #[case] key: &str,
251 #[case] value: &str,
252 ) -> testresult::TestResult<()> {
253 let extra_data: ExtraData = ExtraData::from_str(data)?;
254 assert_eq!(extra_data.key(), key);
255 assert_eq!(extra_data.value(), value);
256 assert_eq!(extra_data.to_string(), data);
257 Ok(())
258 }
259
260 #[rstest]
261 #[case("key", Err(Error::MissingComponent { component: "value" }))]
262 #[case("key=", Err(Error::MissingComponent { component: "value" }))]
263 #[case("=value", Err(Error::MissingComponent { component: "key" }))]
264 fn extra_data_from_str_error(
265 #[case] extra_data: &str,
266 #[case] result: Result<ExtraData, Error>,
267 ) {
268 assert_eq!(ExtraData::from_str(extra_data), result);
269 }
270
271 #[rstest]
272 #[case(" trailing ", "trailing")]
273 #[case("in between words", "in between words")]
274 #[case("\nsome\t whitespace\n chars\n", "some whitespace chars")]
275 #[case(" \neverything\t combined\n yeah \n ", "everything combined yeah")]
276 fn package_description(#[case] input: &str, #[case] result: &str) {
277 assert_eq!(PackageDescription::new(input).to_string(), result);
278 }
279}