1use thiserror::Error;
3
4#[cfg(feature = "serde")]
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Error)]
8pub enum Error {
9 #[error("Invalid COSPAR number")]
10 InvalidFormat,
11}
12
13#[derive(Debug, Clone, PartialEq, PartialOrd)]
15#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
16pub struct COSPAR {
17 year: u16,
19 launch: u16,
21 code: String,
24}
25
26impl std::fmt::Display for COSPAR {
27 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
28 write!(f, "{:04}-{:03}{}", self.year, self.launch, self.code)
29 }
30}
31
32impl std::str::FromStr for COSPAR {
33 type Err = Error;
34 fn from_str(s: &str) -> Result<Self, Self::Err> {
35 if s.len() < 9 {
36 return Err(Error::InvalidFormat);
37 }
38 let offset = s.find('-').ok_or(Error::InvalidFormat)?;
39 let (year, rem) = s.split_at(offset);
40 let year = year.parse::<u16>().map_err(|_| Error::InvalidFormat)?;
41 let launch = rem[1..4]
42 .trim()
43 .parse::<u16>()
44 .map_err(|_| Error::InvalidFormat)?;
45 Ok(Self {
46 year,
47 launch,
48 code: rem[4..].trim().to_string(),
49 })
50 }
51}
52
53#[cfg(test)]
54mod test {
55 use crate::cospar::COSPAR;
56 use std::str::FromStr;
57 #[test]
58 fn cospar() {
59 for (desc, expected) in [
60 (
61 "2018-080A",
62 COSPAR {
63 year: 2018,
64 launch: 80,
65 code: "A".to_string(),
66 },
67 ),
68 (
69 "1996-068A",
70 COSPAR {
71 year: 1996,
72 launch: 68,
73 code: "A".to_string(),
74 },
75 ),
76 ] {
77 let cospar = COSPAR::from_str(desc).unwrap();
78 assert_eq!(cospar, expected);
79 let recip = cospar.to_string();
80 assert_eq!(recip, desc, "cospar reciprocal");
81 }
82 }
83}