alpm_types/openpgp.rs
1use std::{
2 fmt::{Display, Formatter},
3 str::FromStr,
4 string::ToString,
5};
6
7use base64::{Engine, prelude::BASE64_STANDARD};
8use email_address::EmailAddress;
9use fluent_i18n::t;
10use serde::{Deserialize, Serialize};
11use winnow::{
12 ModalResult,
13 Parser,
14 combinator::{cut_err, eof, seq},
15 error::{StrContext, StrContextValue},
16 token::take_till,
17};
18
19use crate::Error;
20
21/// An OpenPGP key identifier.
22///
23/// The `OpenPGPIdentifier` enum represents a valid OpenPGP identifier, which can be either an
24/// OpenPGP Key ID or an OpenPGP v4 fingerprint.
25///
26/// This type wraps an [`OpenPGPKeyId`] and an [`OpenPGPv4Fingerprint`] and provides a unified
27/// interface for both.
28///
29/// ## Examples
30///
31/// ```
32/// use std::str::FromStr;
33///
34/// use alpm_types::{Error, OpenPGPIdentifier, OpenPGPKeyId, OpenPGPv4Fingerprint};
35/// # fn main() -> Result<(), alpm_types::Error> {
36/// // Create a OpenPGPIdentifier from a valid OpenPGP v4 fingerprint
37/// let key = OpenPGPIdentifier::from_str("4A0C4DFFC02E1A7ED969ED231C2358A25A10D94E")?;
38/// assert_eq!(
39/// key,
40/// OpenPGPIdentifier::OpenPGPv4Fingerprint(OpenPGPv4Fingerprint::from_str(
41/// "4A0C4DFFC02E1A7ED969ED231C2358A25A10D94E"
42/// )?)
43/// );
44/// assert_eq!(key.to_string(), "4A0C4DFFC02E1A7ED969ED231C2358A25A10D94E");
45/// assert_eq!(
46/// key,
47/// OpenPGPv4Fingerprint::from_str("4A0C4DFFC02E1A7ED969ED231C2358A25A10D94E")?.into()
48/// );
49///
50/// // Create a OpenPGPIdentifier from a valid OpenPGP Key ID
51/// let key = OpenPGPIdentifier::from_str("2F2670AC164DB36F")?;
52/// assert_eq!(
53/// key,
54/// OpenPGPIdentifier::OpenPGPKeyId(OpenPGPKeyId::from_str("2F2670AC164DB36F")?)
55/// );
56/// assert_eq!(key.to_string(), "2F2670AC164DB36F");
57/// assert_eq!(key, OpenPGPKeyId::from_str("2F2670AC164DB36F")?.into());
58/// # Ok(())
59/// # }
60/// ```
61#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
62pub enum OpenPGPIdentifier {
63 /// An OpenPGP Key ID.
64 #[serde(rename = "openpgp_key_id")]
65 OpenPGPKeyId(OpenPGPKeyId),
66 /// An OpenPGP v4 fingerprint.
67 #[serde(rename = "openpgp_v4_fingerprint")]
68 OpenPGPv4Fingerprint(OpenPGPv4Fingerprint),
69}
70
71impl FromStr for OpenPGPIdentifier {
72 type Err = Error;
73
74 fn from_str(s: &str) -> Result<Self, Self::Err> {
75 match s.parse::<OpenPGPv4Fingerprint>() {
76 Ok(fingerprint) => Ok(OpenPGPIdentifier::OpenPGPv4Fingerprint(fingerprint)),
77 Err(_) => match s.parse::<OpenPGPKeyId>() {
78 Ok(key_id) => Ok(OpenPGPIdentifier::OpenPGPKeyId(key_id)),
79 Err(e) => Err(e),
80 },
81 }
82 }
83}
84
85impl From<OpenPGPKeyId> for OpenPGPIdentifier {
86 fn from(key_id: OpenPGPKeyId) -> Self {
87 OpenPGPIdentifier::OpenPGPKeyId(key_id)
88 }
89}
90
91impl From<OpenPGPv4Fingerprint> for OpenPGPIdentifier {
92 fn from(fingerprint: OpenPGPv4Fingerprint) -> Self {
93 OpenPGPIdentifier::OpenPGPv4Fingerprint(fingerprint)
94 }
95}
96
97impl Display for OpenPGPIdentifier {
98 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
99 match self {
100 OpenPGPIdentifier::OpenPGPKeyId(key_id) => write!(f, "{key_id}"),
101 OpenPGPIdentifier::OpenPGPv4Fingerprint(fingerprint) => write!(f, "{fingerprint}"),
102 }
103 }
104}
105
106/// An OpenPGP Key ID.
107///
108/// The `OpenPGPKeyId` type wraps a `String` representing an [OpenPGP Key ID],
109/// ensuring that it consists of exactly 16 uppercase hexadecimal characters.
110///
111/// [OpenPGP Key ID]: https://openpgp.dev/book/glossary.html#term-Key-ID
112///
113/// ## Note
114///
115/// - This type supports constructing from both uppercase and lowercase hexadecimal characters but
116/// guarantees to return the key ID in uppercase.
117///
118/// - The usage of this type is highly discouraged as the keys may not be unique. This will lead to
119/// a linting error in the future.
120///
121/// ## Examples
122///
123/// ```
124/// use std::str::FromStr;
125///
126/// use alpm_types::{Error, OpenPGPKeyId};
127///
128/// # fn main() -> Result<(), alpm_types::Error> {
129/// // Create OpenPGPKeyId from a valid key ID
130/// let key = OpenPGPKeyId::from_str("2F2670AC164DB36F")?;
131/// assert_eq!(key.as_str(), "2F2670AC164DB36F");
132///
133/// // Attempting to create an OpenPGPKeyId from an invalid key ID will fail
134/// assert!(OpenPGPKeyId::from_str("INVALIDKEYID").is_err());
135///
136/// // Format as String
137/// assert_eq!(format!("{key}"), "2F2670AC164DB36F");
138/// # Ok(())
139/// # }
140/// ```
141#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
142pub struct OpenPGPKeyId(String);
143
144impl OpenPGPKeyId {
145 /// Creates a new `OpenPGPKeyId` instance.
146 ///
147 /// See [`OpenPGPKeyId::from_str`] for more information on how the OpenPGP Key ID is validated.
148 pub fn new(key_id: String) -> Result<Self, Error> {
149 if key_id.len() == 16 && key_id.chars().all(|c| c.is_ascii_hexdigit()) {
150 Ok(Self(key_id.to_ascii_uppercase()))
151 } else {
152 Err(Error::InvalidOpenPGPKeyId(key_id))
153 }
154 }
155
156 /// Returns a reference to the inner OpenPGP Key ID as a `&str`.
157 pub fn as_str(&self) -> &str {
158 &self.0
159 }
160
161 /// Consumes the `OpenPGPKeyId` and returns the inner `String`.
162 pub fn into_inner(self) -> String {
163 self.0
164 }
165}
166
167impl FromStr for OpenPGPKeyId {
168 type Err = Error;
169
170 /// Creates a new `OpenPGPKeyId` instance after validating that it follows the correct format.
171 ///
172 /// A valid OpenPGP Key ID should be exactly 16 characters long and consist only
173 /// of digits (`0-9`) and hexadecimal letters (`A-F`).
174 ///
175 /// # Errors
176 ///
177 /// Returns an error if the OpenPGP Key ID is not valid.
178 fn from_str(s: &str) -> Result<Self, Self::Err> {
179 Self::new(s.to_string())
180 }
181}
182
183impl Display for OpenPGPKeyId {
184 /// Converts the `OpenPGPKeyId` to an uppercase `String`.
185 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
186 write!(f, "{}", self.0)
187 }
188}
189
190/// An OpenPGP v4 fingerprint.
191///
192/// The `OpenPGPv4Fingerprint` type wraps a `String` representing an [OpenPGP v4 fingerprint],
193/// ensuring that it consists of 40 uppercase hexadecimal characters with optional whitespace
194/// separators.
195///
196/// [OpenPGP v4 fingerprint]: https://openpgp.dev/book/certificates.html#fingerprint
197///
198/// ## Note
199///
200/// - This type supports constructing from both uppercase and lowercase hexadecimal characters, with
201/// and without whitespace separators, but guarantees to return the fingerprint in uppercase and
202/// with no whitespaces.
203///
204/// - Whitespaces are only allowed between hexadecimal characters, not at the start or end of the
205/// fingerprint.
206///
207/// ## Examples
208///
209/// ```
210/// use std::str::FromStr;
211///
212/// use alpm_types::{Error, OpenPGPv4Fingerprint};
213///
214/// # fn main() -> Result<(), alpm_types::Error> {
215/// // Create OpenPGPv4Fingerprint from a valid OpenPGP v4 fingerprint
216/// let key = OpenPGPv4Fingerprint::from_str("4A0C4DFFC02E1A7ED969ED231C2358A25A10D94E")?;
217/// assert_eq!(key.as_str(), "4A0C4DFFC02E1A7ED969ED231C2358A25A10D94E");
218///
219/// // Space separated fingerprint is also valid
220/// let key = OpenPGPv4Fingerprint::from_str("4A0C 4DFF C02E 1A7E D969 ED23 1C23 58A2 5A10 D94E")?;
221/// assert_eq!(key.as_str(), "4A0C4DFFC02E1A7ED969ED231C2358A25A10D94E");
222///
223/// // Attempting to create a OpenPGPv4Fingerprint from an invalid fingerprint will fail
224/// assert!(OpenPGPv4Fingerprint::from_str("INVALIDKEY").is_err());
225///
226/// // Format as String
227/// assert_eq!(
228/// format!("{}", key),
229/// "4A0C4DFFC02E1A7ED969ED231C2358A25A10D94E"
230/// );
231/// # Ok(())
232/// # }
233/// ```
234#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
235pub struct OpenPGPv4Fingerprint(String);
236
237impl OpenPGPv4Fingerprint {
238 /// Creates a new `OpenPGPv4Fingerprint` instance
239 ///
240 /// See [`OpenPGPv4Fingerprint::from_str`] for more information on how the OpenPGP v4
241 /// fingerprint is validated.
242 pub fn new(fingerprint: String) -> Result<Self, Error> {
243 Self::from_str(&fingerprint)
244 }
245
246 /// Returns a reference to the inner OpenPGP v4 fingerprint as a `&str`.
247 pub fn as_str(&self) -> &str {
248 &self.0
249 }
250
251 /// Consumes the `OpenPGPv4Fingerprint` and returns the inner `String`.
252 pub fn into_inner(self) -> String {
253 self.0
254 }
255}
256
257impl FromStr for OpenPGPv4Fingerprint {
258 type Err = Error;
259
260 /// Creates a new `OpenPGPv4Fingerprint` instance after validating that it follows the correct
261 /// format.
262 ///
263 /// A valid OpenPGP v4 fingerprint should be a 40 characters long string of digits (`0-9`)
264 /// and hexadecimal letters (`A-F`) optionally separated by whitespaces.
265 ///
266 /// # Errors
267 ///
268 /// Returns an error if the OpenPGP v4 fingerprint is not valid.
269 fn from_str(s: &str) -> Result<Self, Self::Err> {
270 let normalized = s.to_ascii_uppercase().replace(" ", "");
271
272 if !s.starts_with(' ')
273 && !s.ends_with(' ')
274 && normalized.len() == 40
275 && normalized.chars().all(|c| c.is_ascii_hexdigit())
276 {
277 Ok(Self(normalized))
278 } else {
279 Err(Error::InvalidOpenPGPv4Fingerprint)
280 }
281 }
282}
283
284impl Display for OpenPGPv4Fingerprint {
285 /// Converts the `OpenPGPv4Fingerprint` to a uppercase `String`.
286 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
287 write!(f, "{}", self.as_str().to_ascii_uppercase())
288 }
289}
290
291/// A base64 encoded OpenPGP detached signature.
292///
293/// Wraps a [`String`] representing a [base64] encoded [OpenPGP detached signature]
294/// ensuring it consists of valid [base64] characters.
295///
296/// ## Examples
297///
298/// ```
299/// use std::str::FromStr;
300///
301/// use alpm_types::{Error, Base64OpenPGPSignature};
302///
303/// # fn main() -> Result<(), alpm_types::Error> {
304/// // Create Base64OpenPGPSignature from a valid base64 String
305/// let sig = Base64OpenPGPSignature::from_str("iHUEABYKAB0WIQRizHP4hOUpV7L92IObeih9mi7GCAUCaBZuVAAKCRCbeih9mi7GCIlMAP9ws/jU4f580ZRQlTQKvUiLbAZOdcB7mQQj83hD1Nc/GwD/WIHhO1/OQkpMERejUrLo3AgVmY3b4/uGhx9XufWEbgE=")?;
306///
307/// // Attempting to create a Base64OpenPGPSignature from an invalid base64 String will fail
308/// assert!(Base64OpenPGPSignature::from_str("!@#$^&*").is_err());
309///
310/// // Format as String
311/// assert_eq!(
312/// format!("{}", sig),
313/// "iHUEABYKAB0WIQRizHP4hOUpV7L92IObeih9mi7GCAUCaBZuVAAKCRCbeih9mi7GCIlMAP9ws/jU4f580ZRQlTQKvUiLbAZOdcB7mQQj83hD1Nc/GwD/WIHhO1/OQkpMERejUrLo3AgVmY3b4/uGhx9XufWEbgE="
314/// );
315/// # Ok(())
316/// # }
317/// ```
318///
319/// [base64]: https://en.wikipedia.org/wiki/Base64
320/// [OpenPGP detached signature]: https://openpgp.dev/book/signing_data.html#detached-signatures
321#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
322pub struct Base64OpenPGPSignature(String);
323
324impl Base64OpenPGPSignature {
325 /// Creates a new [`Base64OpenPGPSignature`] instance.
326 ///
327 /// See [`Base64OpenPGPSignature::from_str`] for more information on how the OpenPGP signature
328 /// is validated.
329 pub fn new(signature: String) -> Result<Self, Error> {
330 Self::from_str(&signature)
331 }
332
333 /// Returns a reference to the inner OpenPGP signature as a `&str`.
334 pub fn as_str(&self) -> &str {
335 &self.0
336 }
337
338 /// Consumes the [`Base64OpenPGPSignature`] and returns the inner [`String`].
339 pub fn into_inner(self) -> String {
340 self.0
341 }
342}
343
344impl AsRef<str> for Base64OpenPGPSignature {
345 fn as_ref(&self) -> &str {
346 self.as_str()
347 }
348}
349
350impl FromStr for Base64OpenPGPSignature {
351 type Err = Error;
352
353 /// Creates a new [`Base64OpenPGPSignature`] instance after validating that it follows the
354 /// correct format.
355 ///
356 /// A valid [OpenPGP signature] should consist only of [base64] characters (A-Z, a-z, 0-9, +, /)
357 /// and may include padding characters (=) at the end.
358 ///
359 /// # Errors
360 ///
361 /// Returns an error if the OpenPGP signature is not valid.
362 ///
363 /// [base64]: https://en.wikipedia.org/wiki/Base64
364 /// [OpenPGP signature]: https://openpgp.dev/book/signing_data.html#detached-signatures
365 fn from_str(s: &str) -> Result<Self, Self::Err> {
366 BASE64_STANDARD
367 .decode(s)
368 .map_err(|_| Error::InvalidBase64Encoding {
369 expected_item: t!("error-invalid-base64-encoding-pgp-signature"),
370 })?
371 .to_vec();
372 Ok(Self(s.to_string()))
373 }
374}
375
376impl Display for Base64OpenPGPSignature {
377 /// Converts the [`Base64OpenPGPSignature`] to a [`String`].
378 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
379 write!(f, "{}", self.0)
380 }
381}
382
383/// A packager of a package
384///
385/// A `Packager` is represented by a User ID (e.g. `"Foobar McFooFace <foobar@mcfooface.org>"`).
386/// Internally this struct wraps a `String` for the name and an `EmailAddress` for a valid email
387/// address.
388///
389/// ## Examples
390/// ```
391/// use std::str::FromStr;
392///
393/// use alpm_types::{Error, Packager};
394///
395/// # fn main() -> Result<(), alpm_types::Error> {
396/// // create Packager from &str
397/// let packager = Packager::from_str("Foobar McFooface <foobar@mcfooface.org>")?;
398///
399/// // get name
400/// assert_eq!("Foobar McFooface", packager.name());
401///
402/// // get email
403/// assert_eq!("foobar@mcfooface.org", packager.email().to_string());
404///
405/// // get email domain
406/// assert_eq!("mcfooface.org", packager.email().domain());
407///
408/// // format as String
409/// assert_eq!(
410/// "Foobar McFooface <foobar@mcfooface.org>",
411/// format!("{}", packager)
412/// );
413/// # Ok(())
414/// # }
415/// ```
416#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
417pub struct Packager {
418 name: String,
419 email: EmailAddress,
420}
421
422impl Packager {
423 /// Create a new Packager
424 pub fn new(name: String, email: EmailAddress) -> Packager {
425 Packager { name, email }
426 }
427
428 /// Return the name of the Packager
429 pub fn name(&self) -> &str {
430 &self.name
431 }
432
433 /// Return the email of the Packager
434 pub fn email(&self) -> &EmailAddress {
435 &self.email
436 }
437
438 /// Parses a [`Packager`] from a string slice.
439 ///
440 /// Consumes all of its input.
441 ///
442 /// # Examples
443 ///
444 /// See [`Self::from_str`] for code examples.
445 ///
446 /// # Errors
447 ///
448 /// Returns an error if `input` does not represent a valid [`Packager`].
449 pub fn parser(input: &mut &str) -> ModalResult<Self> {
450 seq!(Self {
451 // The name that precedes the email address
452 name: cut_err(take_till(1.., '<'))
453 .map(|s: &str| s.trim().to_string())
454 .context(StrContext::Label("packager name")),
455 // The '<' delimiter that marks the start of the email string
456 _: cut_err('<').context(StrContext::Label("or missing opening delimiter '<' for email address")),
457 // The email address, which is validated by the EmailAddress struct.
458 email: cut_err(
459 take_till(1.., '>')
460 .try_map(EmailAddress::from_str))
461 .context(StrContext::Label("Email address")
462 ),
463 // The '>' delimiter that marks the end of the email string
464 _: cut_err('>').context(StrContext::Label("or missing closing delimiter '>' for email address")),
465 _: eof.context(StrContext::Expected(StrContextValue::Description("end of packager string"))),
466 })
467 .parse_next(input)
468 }
469}
470
471impl FromStr for Packager {
472 type Err = Error;
473 /// Create a Packager from a string
474 fn from_str(s: &str) -> Result<Packager, Self::Err> {
475 Ok(Self::parser.parse(s)?)
476 }
477}
478
479impl Display for Packager {
480 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
481 write!(fmt, "{} <{}>", self.name, self.email)
482 }
483}
484
485#[cfg(test)]
486mod tests {
487 use insta::assert_snapshot;
488 use rstest::rstest;
489 use testresult::TestResult;
490
491 use super::*;
492 use crate::configure_insta;
493
494 #[rstest]
495 #[case("4A0C4DFFC02E1A7ED969ED231C2358A25A10D94E")]
496 #[case("4A0C 4DFF C02E 1A7E D969 ED23 1C23 58A2 5A10 D94E")]
497 #[case("1234567890abcdef1234567890abcdef12345678")]
498 #[case("1234 5678 90ab cdef 1234 5678 90ab cdef 1234 5678")]
499 fn test_parse_openpgp_fingerprint(#[case] input: &str) -> Result<(), Error> {
500 input.parse::<OpenPGPv4Fingerprint>()?;
501 Ok(())
502 }
503
504 #[rstest]
505 // Contains non-hex characters 'G' and 'H'
506 #[case(
507 "A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8G9H0",
508 Err(Error::InvalidOpenPGPv4Fingerprint)
509 )]
510 // Less than 40 characters
511 #[case(
512 "1234567890ABCDEF1234567890ABCDEF1234567",
513 Err(Error::InvalidOpenPGPv4Fingerprint)
514 )]
515 // More than 40 characters
516 #[case(
517 "1234567890ABCDEF1234567890ABCDEF1234567890",
518 Err(Error::InvalidOpenPGPv4Fingerprint)
519 )]
520 // Starts with whitespace
521 #[case(
522 " 4A0C 4DFF C02E 1A7E D969 ED23 1C23 58A2 5A10 D94E",
523 Err(Error::InvalidOpenPGPv4Fingerprint)
524 )]
525 // Ends with whitespace
526 #[case(
527 "4A0C 4DFF C02E 1A7E D969 ED23 1C23 58A2 5A10 D94E ",
528 Err(Error::InvalidOpenPGPv4Fingerprint)
529 )]
530 // Just invalid
531 #[case("invalid", Err(Error::InvalidOpenPGPv4Fingerprint))]
532 fn test_parse_invalid_openpgp_fingerprint(
533 #[case] input: &str,
534 #[case] expected: Result<OpenPGPv4Fingerprint, Error>,
535 ) {
536 let result = input.parse::<OpenPGPv4Fingerprint>();
537 assert_eq!(result, expected);
538 }
539
540 #[rstest]
541 #[case("2F2670AC164DB36F")]
542 #[case("584A3EBFE705CDCD")]
543 fn test_parse_openpgp_key_id(#[case] input: &str) -> Result<(), Error> {
544 input.parse::<OpenPGPKeyId>()?;
545 Ok(())
546 }
547
548 #[test]
549 fn test_serialize_openpgp_key_id() -> TestResult {
550 let id = "584A3EBFE705CDCD".parse::<OpenPGPKeyId>()?;
551 let json = serde_json::to_string(&OpenPGPIdentifier::OpenPGPKeyId(id))?;
552 assert_eq!(r#"{"openpgp_key_id":"584A3EBFE705CDCD"}"#, json);
553
554 Ok(())
555 }
556
557 #[rstest]
558 #[case(
559 "1234567890abcdef1234567890abcdef12345678",
560 "1234567890ABCDEF1234567890ABCDEF12345678"
561 )]
562 #[case(
563 "1234 5678 90ab cdef 1234 5678 90ab cdef 1234 5678",
564 "1234567890ABCDEF1234567890ABCDEF12345678"
565 )]
566 fn test_serialize_openpgp_v4_fingerprint(
567 #[case] input: &str,
568 #[case] output: &str,
569 ) -> TestResult {
570 let print = input.parse::<OpenPGPv4Fingerprint>()?;
571 let json = serde_json::to_string(&OpenPGPIdentifier::OpenPGPv4Fingerprint(print))?;
572 assert_eq!(format!("{{\"openpgp_v4_fingerprint\":\"{output}\"}}"), json);
573
574 Ok(())
575 }
576
577 #[rstest]
578 // Contains non-hex characters 'G' and 'H'
579 #[case("1234567890ABCGH", Err(Error::InvalidOpenPGPKeyId("1234567890ABCGH".to_string())))]
580 // Less than 16 characters
581 #[case("1234567890ABCDE", Err(Error::InvalidOpenPGPKeyId("1234567890ABCDE".to_string())))]
582 // More than 16 characters
583 #[case("1234567890ABCDEF0", Err(Error::InvalidOpenPGPKeyId("1234567890ABCDEF0".to_string())))]
584 // Just invalid
585 #[case("invalid", Err(Error::InvalidOpenPGPKeyId("invalid".to_string())))]
586 fn test_parse_invalid_openpgp_key_id(
587 #[case] input: &str,
588 #[case] expected: Result<OpenPGPKeyId, Error>,
589 ) {
590 let result = input.parse::<OpenPGPKeyId>();
591 assert_eq!(result, expected);
592 }
593
594 #[rstest]
595 #[case("d2hhdCBhcmUgeW91IGxvb2tpbmcgZm9yPyA7LTsK")]
596 fn test_parse_openpgp_signature(#[case] input: &str) -> Result<(), Error> {
597 input.parse::<Base64OpenPGPSignature>()?;
598 Ok(())
599 }
600
601 #[rstest]
602 // "=" in the middle
603 #[case(
604 "d2hhdCBhcmUge=W91IGxvb2tpbmcgZm9yPyA7LTsK",
605 Err(Error::InvalidBase64Encoding { expected_item: t!("error-invalid-base64-encoding-pgp-signature") })
606 )]
607 // invalid characters
608 #[case("!@#$%^&*", Err(Error::InvalidBase64Encoding { expected_item: t!("error-invalid-base64-encoding-pgp-signature") }))]
609 // just invalid
610 #[case(
611 "iHUEABYKh9mi7GCIlMAP9ws/jU4WEbgE=",
612 Err(Error::InvalidBase64Encoding { expected_item: t!("error-invalid-base64-encoding-pgp-signature") })
613 )]
614 fn test_parse_invalid_openpgp_signature(
615 #[case] input: &str,
616 #[case] expected: Result<Base64OpenPGPSignature, Error>,
617 ) {
618 let result = input.parse::<Base64OpenPGPSignature>();
619 assert_eq!(result, expected);
620 }
621
622 #[rstest]
623 #[case(
624 "Foobar McFooface (The Third) <foobar@mcfooface.org>",
625 Packager{
626 name: "Foobar McFooface (The Third)".to_string(),
627 email: EmailAddress::from_str("foobar@mcfooface.org").unwrap()
628 }
629 )]
630 #[case(
631 "Foobar McFooface <foobar@mcfooface.org>",
632 Packager{
633 name: "Foobar McFooface".to_string(),
634 email: EmailAddress::from_str("foobar@mcfooface.org").unwrap()
635 }
636 )]
637 fn valid_packager(#[case] from_str: &str, #[case] packager: Packager) {
638 assert_eq!(Packager::from_str(from_str), Ok(packager));
639 }
640
641 /// Test that invalid packager expressions are detected as such and throw the expected error.
642 #[rstest]
643 #[case::no_name("<foobar@mcfooface.org>")]
644 #[case::no_name_and_address_not_wrapped("foobar@mcfooface.org")]
645 #[case::no_wrapped_address("Foobar McFooface")]
646 #[case::two_wrapped_addresses(
647 "Foobar McFooface <foobar@mcfooface.org> <foobar@mcfoofacemcfooface.org>"
648 )]
649 #[case::address_without_local_part("Foobar McFooface <@mcfooface.org>")]
650 fn invalid_packager(#[case] input: &str) {
651 let Err(err_msg) = Packager::from_str(input) else {
652 panic!("'{input}' erroneously parsed as a Package")
653 };
654
655 let (test_name, _guard) = configure_insta();
656 assert_snapshot!(test_name, err_msg.to_string());
657 }
658
659 #[rstest]
660 #[case(
661 Packager::from_str("Foobar McFooface <foobar@mcfooface.org>").unwrap(),
662 "Foobar McFooface <foobar@mcfooface.org>"
663 )]
664 fn packager_format_string(#[case] packager: Packager, #[case] packager_str: &str) {
665 assert_eq!(packager_str, format!("{packager}"));
666 }
667
668 #[rstest]
669 #[case(Packager::from_str("Foobar McFooface <foobar@mcfooface.org>").unwrap(), "Foobar McFooface")]
670 fn packager_name(#[case] packager: Packager, #[case] name: &str) {
671 assert_eq!(name, packager.name());
672 }
673
674 #[rstest]
675 #[case(
676 Packager::from_str("Foobar McFooface <foobar@mcfooface.org>").unwrap(),
677 &EmailAddress::from_str("foobar@mcfooface.org").unwrap(),
678 )]
679 fn packager_email(#[case] packager: Packager, #[case] email: &EmailAddress) {
680 assert_eq!(email, packager.email());
681 }
682}