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 rstest::rstest;
488 use testresult::TestResult;
489
490 use super::*;
491
492 #[rstest]
493 #[case("4A0C4DFFC02E1A7ED969ED231C2358A25A10D94E")]
494 #[case("4A0C 4DFF C02E 1A7E D969 ED23 1C23 58A2 5A10 D94E")]
495 #[case("1234567890abcdef1234567890abcdef12345678")]
496 #[case("1234 5678 90ab cdef 1234 5678 90ab cdef 1234 5678")]
497 fn test_parse_openpgp_fingerprint(#[case] input: &str) -> Result<(), Error> {
498 input.parse::<OpenPGPv4Fingerprint>()?;
499 Ok(())
500 }
501
502 #[rstest]
503 // Contains non-hex characters 'G' and 'H'
504 #[case(
505 "A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8G9H0",
506 Err(Error::InvalidOpenPGPv4Fingerprint)
507 )]
508 // Less than 40 characters
509 #[case(
510 "1234567890ABCDEF1234567890ABCDEF1234567",
511 Err(Error::InvalidOpenPGPv4Fingerprint)
512 )]
513 // More than 40 characters
514 #[case(
515 "1234567890ABCDEF1234567890ABCDEF1234567890",
516 Err(Error::InvalidOpenPGPv4Fingerprint)
517 )]
518 // Starts with whitespace
519 #[case(
520 " 4A0C 4DFF C02E 1A7E D969 ED23 1C23 58A2 5A10 D94E",
521 Err(Error::InvalidOpenPGPv4Fingerprint)
522 )]
523 // Ends with whitespace
524 #[case(
525 "4A0C 4DFF C02E 1A7E D969 ED23 1C23 58A2 5A10 D94E ",
526 Err(Error::InvalidOpenPGPv4Fingerprint)
527 )]
528 // Just invalid
529 #[case("invalid", Err(Error::InvalidOpenPGPv4Fingerprint))]
530 fn test_parse_invalid_openpgp_fingerprint(
531 #[case] input: &str,
532 #[case] expected: Result<OpenPGPv4Fingerprint, Error>,
533 ) {
534 let result = input.parse::<OpenPGPv4Fingerprint>();
535 assert_eq!(result, expected);
536 }
537
538 #[rstest]
539 #[case("2F2670AC164DB36F")]
540 #[case("584A3EBFE705CDCD")]
541 fn test_parse_openpgp_key_id(#[case] input: &str) -> Result<(), Error> {
542 input.parse::<OpenPGPKeyId>()?;
543 Ok(())
544 }
545
546 #[test]
547 fn test_serialize_openpgp_key_id() -> TestResult {
548 let id = "584A3EBFE705CDCD".parse::<OpenPGPKeyId>()?;
549 let json = serde_json::to_string(&OpenPGPIdentifier::OpenPGPKeyId(id))?;
550 assert_eq!(r#"{"openpgp_key_id":"584A3EBFE705CDCD"}"#, json);
551
552 Ok(())
553 }
554
555 #[rstest]
556 #[case(
557 "1234567890abcdef1234567890abcdef12345678",
558 "1234567890ABCDEF1234567890ABCDEF12345678"
559 )]
560 #[case(
561 "1234 5678 90ab cdef 1234 5678 90ab cdef 1234 5678",
562 "1234567890ABCDEF1234567890ABCDEF12345678"
563 )]
564 fn test_serialize_openpgp_v4_fingerprint(
565 #[case] input: &str,
566 #[case] output: &str,
567 ) -> TestResult {
568 let print = input.parse::<OpenPGPv4Fingerprint>()?;
569 let json = serde_json::to_string(&OpenPGPIdentifier::OpenPGPv4Fingerprint(print))?;
570 assert_eq!(format!("{{\"openpgp_v4_fingerprint\":\"{output}\"}}"), json);
571
572 Ok(())
573 }
574
575 #[rstest]
576 // Contains non-hex characters 'G' and 'H'
577 #[case("1234567890ABCGH", Err(Error::InvalidOpenPGPKeyId("1234567890ABCGH".to_string())))]
578 // Less than 16 characters
579 #[case("1234567890ABCDE", Err(Error::InvalidOpenPGPKeyId("1234567890ABCDE".to_string())))]
580 // More than 16 characters
581 #[case("1234567890ABCDEF0", Err(Error::InvalidOpenPGPKeyId("1234567890ABCDEF0".to_string())))]
582 // Just invalid
583 #[case("invalid", Err(Error::InvalidOpenPGPKeyId("invalid".to_string())))]
584 fn test_parse_invalid_openpgp_key_id(
585 #[case] input: &str,
586 #[case] expected: Result<OpenPGPKeyId, Error>,
587 ) {
588 let result = input.parse::<OpenPGPKeyId>();
589 assert_eq!(result, expected);
590 }
591
592 #[rstest]
593 #[case("d2hhdCBhcmUgeW91IGxvb2tpbmcgZm9yPyA7LTsK")]
594 fn test_parse_openpgp_signature(#[case] input: &str) -> Result<(), Error> {
595 input.parse::<Base64OpenPGPSignature>()?;
596 Ok(())
597 }
598
599 #[rstest]
600 // "=" in the middle
601 #[case(
602 "d2hhdCBhcmUge=W91IGxvb2tpbmcgZm9yPyA7LTsK",
603 Err(Error::InvalidBase64Encoding { expected_item: t!("error-invalid-base64-encoding-pgp-signature") })
604 )]
605 // invalid characters
606 #[case("!@#$%^&*", Err(Error::InvalidBase64Encoding { expected_item: t!("error-invalid-base64-encoding-pgp-signature") }))]
607 // just invalid
608 #[case(
609 "iHUEABYKh9mi7GCIlMAP9ws/jU4WEbgE=",
610 Err(Error::InvalidBase64Encoding { expected_item: t!("error-invalid-base64-encoding-pgp-signature") })
611 )]
612 fn test_parse_invalid_openpgp_signature(
613 #[case] input: &str,
614 #[case] expected: Result<Base64OpenPGPSignature, Error>,
615 ) {
616 let result = input.parse::<Base64OpenPGPSignature>();
617 assert_eq!(result, expected);
618 }
619
620 #[rstest]
621 #[case(
622 "Foobar McFooface (The Third) <foobar@mcfooface.org>",
623 Packager{
624 name: "Foobar McFooface (The Third)".to_string(),
625 email: EmailAddress::from_str("foobar@mcfooface.org").unwrap()
626 }
627 )]
628 #[case(
629 "Foobar McFooface <foobar@mcfooface.org>",
630 Packager{
631 name: "Foobar McFooface".to_string(),
632 email: EmailAddress::from_str("foobar@mcfooface.org").unwrap()
633 }
634 )]
635 fn valid_packager(#[case] from_str: &str, #[case] packager: Packager) {
636 assert_eq!(Packager::from_str(from_str), Ok(packager));
637 }
638
639 /// Test that invalid packager expressions are detected as such and throw the expected error.
640 #[rstest]
641 #[case::no_name("<foobar@mcfooface.org>", "invalid packager name")]
642 #[case::no_name_and_address_not_wrapped(
643 "foobar@mcfooface.org",
644 "invalid or missing opening delimiter '<' for email address"
645 )]
646 #[case::no_wrapped_address(
647 "Foobar McFooface",
648 "invalid or missing opening delimiter '<' for email address"
649 )]
650 #[case::two_wrapped_addresses(
651 "Foobar McFooface <foobar@mcfooface.org> <foobar@mcfoofacemcfooface.org>",
652 "expected end of packager string"
653 )]
654 #[case::address_without_local_part("Foobar McFooface <@mcfooface.org>", "Local part is empty")]
655 fn invalid_packager(#[case] packager: &str, #[case] expected_error: &str) -> TestResult {
656 let Err(err) = Packager::from_str(packager) else {
657 panic!("Expected packager string to be invalid: {packager}");
658 };
659
660 let error = err.to_string();
661 assert!(
662 error.contains(expected_error),
663 "Expected error:\n{error}\n\nto contain string:\n{expected_error}"
664 );
665
666 Ok(())
667 }
668
669 #[rstest]
670 #[case(
671 Packager::from_str("Foobar McFooface <foobar@mcfooface.org>").unwrap(),
672 "Foobar McFooface <foobar@mcfooface.org>"
673 )]
674 fn packager_format_string(#[case] packager: Packager, #[case] packager_str: &str) {
675 assert_eq!(packager_str, format!("{packager}"));
676 }
677
678 #[rstest]
679 #[case(Packager::from_str("Foobar McFooface <foobar@mcfooface.org>").unwrap(), "Foobar McFooface")]
680 fn packager_name(#[case] packager: Packager, #[case] name: &str) {
681 assert_eq!(name, packager.name());
682 }
683
684 #[rstest]
685 #[case(
686 Packager::from_str("Foobar McFooface <foobar@mcfooface.org>").unwrap(),
687 &EmailAddress::from_str("foobar@mcfooface.org").unwrap(),
688 )]
689 fn packager_email(#[case] packager: Packager, #[case] email: &EmailAddress) {
690 assert_eq!(email, packager.email());
691 }
692}