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 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 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 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}