Skip to main content

use_constellation/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7fn non_empty_text(
8    value: impl AsRef<str>,
9    error: ConstellationTextError,
10) -> Result<String, ConstellationTextError> {
11    let trimmed = value.as_ref().trim();
12
13    if trimmed.is_empty() {
14        Err(error)
15    } else {
16        Ok(trimmed.to_string())
17    }
18}
19
20#[derive(Clone, Copy, Debug, Eq, PartialEq)]
21pub enum ConstellationTextError {
22    EmptyName,
23    EmptyAbbreviation,
24    EmptyRegion,
25}
26
27impl fmt::Display for ConstellationTextError {
28    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
29        match self {
30            Self::EmptyName => formatter.write_str("constellation name cannot be empty"),
31            Self::EmptyAbbreviation => {
32                formatter.write_str("constellation abbreviation cannot be empty")
33            },
34            Self::EmptyRegion => formatter.write_str("constellation region cannot be empty"),
35        }
36    }
37}
38
39impl Error for ConstellationTextError {}
40
41#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
42pub struct ConstellationName(String);
43
44impl ConstellationName {
45    /// Creates a constellation name from non-empty text.
46    ///
47    /// # Errors
48    ///
49    /// Returns [`ConstellationTextError::EmptyName`] when the trimmed input is empty.
50    pub fn new(value: impl AsRef<str>) -> Result<Self, ConstellationTextError> {
51        non_empty_text(value, ConstellationTextError::EmptyName).map(Self)
52    }
53
54    #[must_use]
55    pub fn as_str(&self) -> &str {
56        &self.0
57    }
58}
59
60impl AsRef<str> for ConstellationName {
61    fn as_ref(&self) -> &str {
62        self.as_str()
63    }
64}
65
66impl fmt::Display for ConstellationName {
67    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
68        formatter.write_str(self.as_str())
69    }
70}
71
72impl FromStr for ConstellationName {
73    type Err = ConstellationTextError;
74
75    fn from_str(value: &str) -> Result<Self, Self::Err> {
76        Self::new(value)
77    }
78}
79
80#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
81pub struct ConstellationAbbreviation(String);
82
83impl ConstellationAbbreviation {
84    /// Creates a constellation abbreviation from non-empty text.
85    ///
86    /// # Errors
87    ///
88    /// Returns [`ConstellationTextError::EmptyAbbreviation`] when the trimmed input is empty.
89    pub fn new(value: impl AsRef<str>) -> Result<Self, ConstellationTextError> {
90        non_empty_text(value, ConstellationTextError::EmptyAbbreviation).map(Self)
91    }
92
93    #[must_use]
94    pub fn as_str(&self) -> &str {
95        &self.0
96    }
97}
98
99impl AsRef<str> for ConstellationAbbreviation {
100    fn as_ref(&self) -> &str {
101        self.as_str()
102    }
103}
104
105impl fmt::Display for ConstellationAbbreviation {
106    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
107        formatter.write_str(self.as_str())
108    }
109}
110
111impl FromStr for ConstellationAbbreviation {
112    type Err = ConstellationTextError;
113
114    fn from_str(value: &str) -> Result<Self, Self::Err> {
115        Self::new(value)
116    }
117}
118
119#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
120pub struct ConstellationRegion(String);
121
122impl ConstellationRegion {
123    /// Creates a constellation region label from non-empty text.
124    ///
125    /// # Errors
126    ///
127    /// Returns [`ConstellationTextError::EmptyRegion`] when the trimmed input is empty.
128    pub fn new(value: impl AsRef<str>) -> Result<Self, ConstellationTextError> {
129        non_empty_text(value, ConstellationTextError::EmptyRegion).map(Self)
130    }
131
132    #[must_use]
133    pub fn as_str(&self) -> &str {
134        &self.0
135    }
136}
137
138impl AsRef<str> for ConstellationRegion {
139    fn as_ref(&self) -> &str {
140        self.as_str()
141    }
142}
143
144impl fmt::Display for ConstellationRegion {
145    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
146        formatter.write_str(self.as_str())
147    }
148}
149
150impl FromStr for ConstellationRegion {
151    type Err = ConstellationTextError;
152
153    fn from_str(value: &str) -> Result<Self, Self::Err> {
154        Self::new(value)
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::{
161        ConstellationAbbreviation, ConstellationName, ConstellationRegion, ConstellationTextError,
162    };
163
164    #[test]
165    fn valid_constellation_name() {
166        let name = ConstellationName::new("Lyra").unwrap();
167
168        assert_eq!(name.as_str(), "Lyra");
169    }
170
171    #[test]
172    fn empty_constellation_name_rejected() {
173        assert_eq!(
174            ConstellationName::new("   "),
175            Err(ConstellationTextError::EmptyName)
176        );
177    }
178
179    #[test]
180    fn valid_abbreviation() {
181        let abbreviation = ConstellationAbbreviation::new("Lyr").unwrap();
182
183        assert_eq!(abbreviation.as_str(), "Lyr");
184    }
185
186    #[test]
187    fn empty_abbreviation_rejected() {
188        assert_eq!(
189            ConstellationAbbreviation::new(" "),
190            Err(ConstellationTextError::EmptyAbbreviation)
191        );
192    }
193
194    #[test]
195    fn constellation_region_construction() {
196        let region = ConstellationRegion::new("northern sky").unwrap();
197
198        assert_eq!(region.as_str(), "northern sky");
199    }
200
201    #[test]
202    fn display_behavior() {
203        let name = ConstellationName::new("Orion").unwrap();
204        let abbreviation = ConstellationAbbreviation::new("Ori").unwrap();
205
206        assert_eq!(name.to_string(), "Orion");
207        assert_eq!(abbreviation.to_string(), "Ori");
208    }
209}