1use core::fmt;
4
5use crate::custom_bodies::{validate_canonical_text, CustomDefinitionValidationError};
6
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9#[derive(Clone, Debug, PartialEq, Eq, Hash)]
10#[non_exhaustive]
11pub enum HouseSystem {
12 Placidus,
14 Koch,
16 Porphyry,
18 Regiomontanus,
20 Campanus,
22 Carter,
24 Horizon,
26 Apc,
28 KrusinskiPisaGoelzer,
30 Equal,
32 EqualMidheaven,
34 EqualAries,
36 Vehlow,
38 Sripati,
40 WholeSign,
42 Alcabitius,
44 Albategnius,
46 PullenSd,
48 PullenSr,
50 Meridian,
52 Axial,
54 Topocentric,
56 Morinus,
58 Sunshine,
60 Gauquelin,
62 Custom(CustomHouseSystem),
64}
65
66impl fmt::Display for HouseSystem {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 match self {
69 Self::Placidus => f.write_str("Placidus"),
70 Self::Koch => f.write_str("Koch"),
71 Self::Porphyry => f.write_str("Porphyry"),
72 Self::Regiomontanus => f.write_str("Regiomontanus"),
73 Self::Campanus => f.write_str("Campanus"),
74 Self::Carter => f.write_str("Carter (poli-equatorial)"),
75 Self::Horizon => f.write_str("Horizon/Azimuth"),
76 Self::Apc => f.write_str("APC"),
77 Self::KrusinskiPisaGoelzer => f.write_str("Krusinski-Pisa-Goelzer"),
78 Self::Equal => f.write_str("Equal"),
79 Self::EqualMidheaven => f.write_str("Equal (MC)"),
80 Self::EqualAries => f.write_str("Equal (1=Aries)"),
81 Self::Vehlow => f.write_str("Vehlow Equal"),
82 Self::Sripati => f.write_str("Sripati"),
83 Self::WholeSign => f.write_str("Whole Sign"),
84 Self::Alcabitius => f.write_str("Alcabitius"),
85 Self::Albategnius => f.write_str("Albategnius"),
86 Self::PullenSd => f.write_str("Pullen SD"),
87 Self::PullenSr => f.write_str("Pullen SR"),
88 Self::Meridian => f.write_str("Meridian"),
89 Self::Axial => f.write_str("Axial"),
90 Self::Topocentric => f.write_str("Topocentric"),
91 Self::Morinus => f.write_str("Morinus"),
92 Self::Sunshine => f.write_str("Sunshine"),
93 Self::Gauquelin => f.write_str("Gauquelin sectors"),
94 Self::Custom(custom) => fmt::Display::fmt(custom, f),
95 }
96 }
97}
98
99impl HouseSystem {
100 pub fn validate(&self) -> Result<(), CustomDefinitionValidationError> {
106 match self {
107 Self::Custom(custom) => custom.validate(),
108 _ => Ok(()),
109 }
110 }
111
112 pub fn validate_against_reserved_labels(
118 &self,
119 is_reserved_label: impl Fn(&str) -> bool,
120 ) -> Result<(), CustomDefinitionValidationError> {
121 match self {
122 Self::Custom(custom) => custom.validate_against_reserved_labels(is_reserved_label),
123 _ => Ok(()),
124 }
125 }
126}
127
128#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
130#[derive(Clone, Debug, PartialEq, Eq, Hash)]
131pub struct CustomHouseSystem {
132 pub name: String,
134 pub aliases: Vec<String>,
136 pub notes: Option<String>,
138}
139
140impl CustomHouseSystem {
141 pub fn new(name: impl Into<String>) -> Self {
143 Self {
144 name: name.into(),
145 aliases: Vec::new(),
146 notes: None,
147 }
148 }
149
150 pub fn validate(&self) -> Result<(), CustomDefinitionValidationError> {
157 validate_canonical_text("custom house system", "name", &self.name)?;
158
159 let mut aliases: Vec<&str> = Vec::with_capacity(self.aliases.len());
160 for (index, alias) in self.aliases.iter().enumerate() {
161 let field = format!("alias[{index}]");
162 validate_canonical_text("custom house system", field, alias)?;
163
164 if self.name.eq_ignore_ascii_case(alias)
165 || aliases
166 .iter()
167 .any(|existing| existing.eq_ignore_ascii_case(alias))
168 {
169 return Err(CustomDefinitionValidationError::duplicate_alias(
170 "custom house system",
171 alias,
172 ));
173 }
174
175 aliases.push(alias);
176 }
177
178 if let Some(notes) = &self.notes {
179 validate_canonical_text("custom house system", "notes", notes)?;
180 }
181
182 Ok(())
183 }
184
185 pub fn validate_against_reserved_labels(
193 &self,
194 is_reserved_label: impl Fn(&str) -> bool,
195 ) -> Result<(), CustomDefinitionValidationError> {
196 self.validate()?;
197
198 if is_reserved_label(&self.name) {
199 return Err(CustomDefinitionValidationError::reserved_label(
200 "custom house system",
201 "name",
202 &self.name,
203 ));
204 }
205
206 for (index, alias) in self.aliases.iter().enumerate() {
207 if is_reserved_label(alias) {
208 return Err(CustomDefinitionValidationError::reserved_label(
209 "custom house system",
210 format!("alias[{index}]"),
211 alias,
212 ));
213 }
214 }
215
216 Ok(())
217 }
218}
219
220impl fmt::Display for CustomHouseSystem {
221 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222 f.write_str(&self.name)?;
223
224 if !self.aliases.is_empty() {
225 write!(f, " [aliases: {}]", self.aliases.join(", "))?;
226 }
227
228 if let Some(notes) = &self.notes {
229 write!(f, " ({notes})")?;
230 }
231
232 Ok(())
233 }
234}