Skip to main content

alpm_types/
openpgp.rs

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