1use std::{
2 fmt::{Display, Formatter},
3 str::FromStr,
4 string::ToString,
5};
6
7use email_address::EmailAddress;
8use serde::{Deserialize, Serialize};
9use winnow::{
10 ModalResult,
11 Parser,
12 combinator::{cut_err, eof, seq},
13 error::{StrContext, StrContextValue},
14 token::take_till,
15};
16
17use crate::Error;
18
19#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
60pub enum OpenPGPIdentifier {
61 #[serde(rename = "openpgp_key_id")]
63 OpenPGPKeyId(OpenPGPKeyId),
64 #[serde(rename = "openpgp_v4_fingerprint")]
66 OpenPGPv4Fingerprint(OpenPGPv4Fingerprint),
67}
68
69impl FromStr for OpenPGPIdentifier {
70 type Err = Error;
71
72 fn from_str(s: &str) -> Result<Self, Self::Err> {
73 match s.parse::<OpenPGPv4Fingerprint>() {
74 Ok(fingerprint) => Ok(OpenPGPIdentifier::OpenPGPv4Fingerprint(fingerprint)),
75 Err(_) => match s.parse::<OpenPGPKeyId>() {
76 Ok(key_id) => Ok(OpenPGPIdentifier::OpenPGPKeyId(key_id)),
77 Err(e) => Err(e),
78 },
79 }
80 }
81}
82
83impl From<OpenPGPKeyId> for OpenPGPIdentifier {
84 fn from(key_id: OpenPGPKeyId) -> Self {
85 OpenPGPIdentifier::OpenPGPKeyId(key_id)
86 }
87}
88
89impl From<OpenPGPv4Fingerprint> for OpenPGPIdentifier {
90 fn from(fingerprint: OpenPGPv4Fingerprint) -> Self {
91 OpenPGPIdentifier::OpenPGPv4Fingerprint(fingerprint)
92 }
93}
94
95impl Display for OpenPGPIdentifier {
96 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
97 match self {
98 OpenPGPIdentifier::OpenPGPKeyId(key_id) => write!(f, "{key_id}"),
99 OpenPGPIdentifier::OpenPGPv4Fingerprint(fingerprint) => write!(f, "{fingerprint}"),
100 }
101 }
102}
103
104#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
140pub struct OpenPGPKeyId(String);
141
142impl OpenPGPKeyId {
143 pub fn new(key_id: String) -> Result<Self, Error> {
147 if key_id.len() == 16 && key_id.chars().all(|c| c.is_ascii_hexdigit()) {
148 Ok(Self(key_id.to_ascii_uppercase()))
149 } else {
150 Err(Error::InvalidOpenPGPKeyId(key_id))
151 }
152 }
153
154 pub fn as_str(&self) -> &str {
156 &self.0
157 }
158
159 pub fn into_inner(self) -> String {
161 self.0
162 }
163}
164
165impl FromStr for OpenPGPKeyId {
166 type Err = Error;
167
168 fn from_str(s: &str) -> Result<Self, Self::Err> {
177 Self::new(s.to_string())
178 }
179}
180
181impl Display for OpenPGPKeyId {
182 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
184 write!(f, "{}", self.0)
185 }
186}
187
188#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
224pub struct OpenPGPv4Fingerprint(String);
225
226impl OpenPGPv4Fingerprint {
227 pub fn new(fingerprint: String) -> Result<Self, Error> {
232 Self::from_str(&fingerprint)
233 }
234
235 pub fn as_str(&self) -> &str {
237 &self.0
238 }
239
240 pub fn into_inner(self) -> String {
242 self.0
243 }
244}
245
246impl FromStr for OpenPGPv4Fingerprint {
247 type Err = Error;
248
249 fn from_str(s: &str) -> Result<Self, Self::Err> {
259 if s.len() == 40 && s.chars().all(|c| c.is_ascii_hexdigit()) {
260 Ok(Self(s.to_ascii_uppercase()))
261 } else {
262 Err(Error::InvalidOpenPGPv4Fingerprint)
263 }
264 }
265}
266
267impl Display for OpenPGPv4Fingerprint {
268 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
270 write!(f, "{}", self.as_str().to_ascii_uppercase())
271 }
272}
273
274#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
308pub struct Packager {
309 name: String,
310 email: EmailAddress,
311}
312
313impl Packager {
314 pub fn new(name: String, email: EmailAddress) -> Packager {
316 Packager { name, email }
317 }
318
319 pub fn name(&self) -> &str {
321 &self.name
322 }
323
324 pub fn email(&self) -> &EmailAddress {
326 &self.email
327 }
328
329 pub fn parser(input: &mut &str) -> ModalResult<Self> {
341 seq!(Self {
342 name: cut_err(take_till(1.., '<'))
344 .map(|s: &str| s.trim().to_string())
345 .context(StrContext::Label("packager name")),
346 _: cut_err('<').context(StrContext::Label("or missing opening delimiter '<' for email address")),
348 email: cut_err(
350 take_till(1.., '>')
351 .try_map(EmailAddress::from_str))
352 .context(StrContext::Label("Email address")
353 ),
354 _: cut_err('>').context(StrContext::Label("or missing closing delimiter '>' for email address")),
356 _: eof.context(StrContext::Expected(StrContextValue::Description("end of packager string"))),
357 })
358 .parse_next(input)
359 }
360}
361
362impl FromStr for Packager {
363 type Err = Error;
364 fn from_str(s: &str) -> Result<Packager, Self::Err> {
366 Ok(Self::parser.parse(s)?)
367 }
368}
369
370impl Display for Packager {
371 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
372 write!(fmt, "{} <{}>", self.name, self.email)
373 }
374}
375
376#[cfg(test)]
377mod tests {
378 use rstest::rstest;
379 use testresult::{TestError, TestResult};
380
381 use super::*;
382
383 #[rstest]
384 #[case("4A0C4DFFC02E1A7ED969ED231C2358A25A10D94E")]
385 #[case("1234567890abcdef1234567890abcdef12345678")]
386 fn test_parse_openpgp_fingerprint(#[case] input: &str) -> Result<(), Error> {
387 input.parse::<OpenPGPv4Fingerprint>()?;
388 Ok(())
389 }
390
391 #[rstest]
392 #[case(
394 "A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8G9H0",
395 Err(Error::InvalidOpenPGPv4Fingerprint)
396 )]
397 #[case(
399 "1234567890ABCDEF1234567890ABCDEF1234567",
400 Err(Error::InvalidOpenPGPv4Fingerprint)
401 )]
402 #[case(
404 "1234567890ABCDEF1234567890ABCDEF1234567890",
405 Err(Error::InvalidOpenPGPv4Fingerprint)
406 )]
407 #[case("invalid", Err(Error::InvalidOpenPGPv4Fingerprint))]
409 fn test_parse_invalid_openpgp_fingerprint(
410 #[case] input: &str,
411 #[case] expected: Result<OpenPGPv4Fingerprint, Error>,
412 ) {
413 let result = input.parse::<OpenPGPv4Fingerprint>();
414 assert_eq!(result, expected);
415 }
416
417 #[rstest]
418 #[case("2F2670AC164DB36F")]
419 #[case("584A3EBFE705CDCD")]
420 fn test_parse_openpgp_key_id(#[case] input: &str) -> Result<(), Error> {
421 input.parse::<OpenPGPKeyId>()?;
422 Ok(())
423 }
424
425 #[test]
426 fn test_serialize_openpgp_key_id() -> TestResult {
427 let id = "584A3EBFE705CDCD".parse::<OpenPGPKeyId>()?;
428 let json = serde_json::to_string(&OpenPGPIdentifier::OpenPGPKeyId(id))?;
429 assert_eq!(r#"{"openpgp_key_id":"584A3EBFE705CDCD"}"#, json);
430
431 Ok(())
432 }
433
434 #[test]
435 fn test_serialize_openpgp_v4_fingerprint() -> TestResult {
436 let print = "1234567890abcdef1234567890abcdef12345678".parse::<OpenPGPv4Fingerprint>()?;
437 let json = serde_json::to_string(&OpenPGPIdentifier::OpenPGPv4Fingerprint(print))?;
438 assert_eq!(
439 r#"{"openpgp_v4_fingerprint":"1234567890ABCDEF1234567890ABCDEF12345678"}"#,
440 json
441 );
442
443 Ok(())
444 }
445
446 #[rstest]
447 #[case("1234567890ABCGH", Err(Error::InvalidOpenPGPKeyId("1234567890ABCGH".to_string())))]
449 #[case("1234567890ABCDE", Err(Error::InvalidOpenPGPKeyId("1234567890ABCDE".to_string())))]
451 #[case("1234567890ABCDEF0", Err(Error::InvalidOpenPGPKeyId("1234567890ABCDEF0".to_string())))]
453 #[case("invalid", Err(Error::InvalidOpenPGPKeyId("invalid".to_string())))]
455 fn test_parse_invalid_openpgp_key_id(
456 #[case] input: &str,
457 #[case] expected: Result<OpenPGPKeyId, Error>,
458 ) {
459 let result = input.parse::<OpenPGPKeyId>();
460 assert_eq!(result, expected);
461 }
462
463 #[rstest]
464 #[case(
465 "Foobar McFooface (The Third) <foobar@mcfooface.org>",
466 Packager{
467 name: "Foobar McFooface (The Third)".to_string(),
468 email: EmailAddress::from_str("foobar@mcfooface.org").unwrap()
469 }
470 )]
471 #[case(
472 "Foobar McFooface <foobar@mcfooface.org>",
473 Packager{
474 name: "Foobar McFooface".to_string(),
475 email: EmailAddress::from_str("foobar@mcfooface.org").unwrap()
476 }
477 )]
478 fn valid_packager(#[case] from_str: &str, #[case] packager: Packager) {
479 assert_eq!(Packager::from_str(from_str), Ok(packager));
480 }
481
482 #[rstest]
484 #[case::no_name("<foobar@mcfooface.org>", "invalid packager name")]
485 #[case::no_name_and_address_not_wrapped(
486 "foobar@mcfooface.org",
487 "invalid or missing opening delimiter '<' for email address"
488 )]
489 #[case::no_wrapped_address(
490 "Foobar McFooface",
491 "invalid or missing opening delimiter '<' for email address"
492 )]
493 #[case::two_wrapped_addresses(
494 "Foobar McFooface <foobar@mcfooface.org> <foobar@mcfoofacemcfooface.org>",
495 "expected end of packager string"
496 )]
497 #[case::address_without_local_part("Foobar McFooface <@mcfooface.org>", "Local part is empty")]
498 fn invalid_packager(#[case] packager: &str, #[case] expected_error: &str) -> TestResult {
499 let Err(err) = Packager::from_str(packager) else {
500 return Err(TestError::from(format!(
501 "Expected packager string to be invalid: {packager}"
502 )));
503 };
504
505 let error = err.to_string();
506 assert!(
507 error.contains(expected_error),
508 "Expected error:\n{error}\n\nto contain string:\n{expected_error}"
509 );
510
511 Ok(())
512 }
513
514 #[rstest]
515 #[case(
516 Packager::from_str("Foobar McFooface <foobar@mcfooface.org>").unwrap(),
517 "Foobar McFooface <foobar@mcfooface.org>"
518 )]
519 fn packager_format_string(#[case] packager: Packager, #[case] packager_str: &str) {
520 assert_eq!(packager_str, format!("{packager}"));
521 }
522
523 #[rstest]
524 #[case(Packager::from_str("Foobar McFooface <foobar@mcfooface.org>").unwrap(), "Foobar McFooface")]
525 fn packager_name(#[case] packager: Packager, #[case] name: &str) {
526 assert_eq!(name, packager.name());
527 }
528
529 #[rstest]
530 #[case(
531 Packager::from_str("Foobar McFooface <foobar@mcfooface.org>").unwrap(),
532 &EmailAddress::from_str("foobar@mcfooface.org").unwrap(),
533 )]
534 fn packager_email(#[case] packager: Packager, #[case] email: &EmailAddress) {
535 assert_eq!(email, packager.email());
536 }
537}