1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5pub enum YinYang {
6 Yang,
8 Yin,
10}
11
12impl YinYang {
13 pub fn as_str(self) -> &'static str {
17 match self {
18 YinYang::Yang => "阳",
19 YinYang::Yin => "阴",
20 }
21 }
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
26pub enum FiveElements {
27 Wood,
29 Metal,
31 Water,
33 Fire,
35 Earth,
37}
38
39impl FiveElements {
40 pub fn as_str(self) -> &'static str {
44 match self {
45 FiveElements::Wood => "木",
46 FiveElements::Metal => "金",
47 FiveElements::Water => "水",
48 FiveElements::Fire => "火",
49 FiveElements::Earth => "土",
50 }
51 }
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
56#[repr(usize)]
57pub enum HeavenlyStem {
58 Jia,
60 Yi,
62 Bing,
64 Ding,
66 Wu,
68 Ji,
70 Geng,
72 Xin,
74 Ren,
76 Gui,
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
82#[repr(usize)]
83pub enum EarthlyBranch {
84 Zi,
86 Chou,
88 Yin,
90 Mao,
92 Chen,
94 Si,
96 Wu,
98 Wei,
100 Shen,
102 You,
104 Xu,
106 Hai,
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
112#[repr(usize)]
113pub enum Palace {
114 Soul,
116 Parents,
118 Spirit,
120 Property,
122 Career,
124 Friends,
126 Surface,
128 Health,
130 Wealth,
132 Children,
134 Spouse,
136 Siblings,
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
142#[repr(u8)]
143pub enum FiveElementsClass {
144 Water2nd = 2,
146 Wood3rd = 3,
148 Metal4th = 4,
150 Earth5th = 5,
152 Fire6th = 6,
154}
155
156#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
158pub enum Mutagen {
159 Lu,
161 Quan,
163 Ke,
165 Ji,
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
171pub enum Brightness {
172 Miao,
174 Wang,
176 De,
178 Li,
180 Ping,
182 Bu,
184 Xian,
186}
187
188#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
190#[non_exhaustive]
191pub enum StarType {
192 Major,
194 Soft,
196 Tough,
198 Adjective,
200 Flower,
202 Helper,
204 Lucun,
206 Tianma,
208}
209
210impl StarType {
211 pub fn as_key(self) -> &'static str {
213 match self {
214 StarType::Major => "major",
215 StarType::Soft => "soft",
216 StarType::Tough => "tough",
217 StarType::Adjective => "adjective",
218 StarType::Flower => "flower",
219 StarType::Helper => "helper",
220 StarType::Lucun => "lucun",
221 StarType::Tianma => "tianma",
222 }
223 }
224}
225
226#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
228#[non_exhaustive]
229pub enum Scope {
230 Origin,
232 Decadal,
234 Yearly,
236 Monthly,
238 Daily,
240 Hourly,
242}
243
244impl Scope {
245 pub fn as_key(self) -> &'static str {
247 match self {
248 Scope::Origin => "origin",
249 Scope::Decadal => "decadal",
250 Scope::Yearly => "yearly",
251 Scope::Monthly => "monthly",
252 Scope::Daily => "daily",
253 Scope::Hourly => "hourly",
254 }
255 }
256
257 pub fn from_key(key: &str) -> Option<Self> {
259 match key {
260 "origin" => Some(Scope::Origin),
261 "decadal" => Some(Scope::Decadal),
262 "yearly" => Some(Scope::Yearly),
263 "monthly" => Some(Scope::Monthly),
264 "daily" => Some(Scope::Daily),
265 "hourly" => Some(Scope::Hourly),
266 _ => None,
267 }
268 }
269}
270
271#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
273pub enum HoroscopeName {
274 Decadal,
276 Childhood,
278 Age,
280 Yearly,
282 Monthly,
284 Daily,
286 Hourly,
288}
289
290#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
292pub enum Gender {
293 Male,
295 Female,
297}
298
299impl Gender {
300 pub fn yin_yang(self) -> YinYang {
302 match self {
303 Gender::Male => YinYang::Yang,
304 Gender::Female => YinYang::Yin,
305 }
306 }
307}
308
309#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
311pub enum Language {
312 ZhCN,
314 ZhTW,
316 EnUS,
318 JaJP,
320 KoKR,
322 ViVN,
324}
325
326impl Language {
327 pub fn as_code(self) -> &'static str {
329 match self {
330 Language::ZhCN => "zh-CN",
331 Language::ZhTW => "zh-TW",
332 Language::EnUS => "en-US",
333 Language::JaJP => "ja-JP",
334 Language::KoKR => "ko-KR",
335 Language::ViVN => "vi-VN",
336 }
337 }
338
339 pub fn from_code(code: &str) -> Option<Self> {
342 match code.to_ascii_lowercase().replace('_', "-").as_str() {
343 "zh-cn" => Some(Language::ZhCN),
344 "zh-tw" => Some(Language::ZhTW),
345 "en-us" => Some(Language::EnUS),
346 "ja-jp" => Some(Language::JaJP),
347 "ko-kr" => Some(Language::KoKR),
348 "vi-vn" => Some(Language::ViVN),
349 _ => None,
350 }
351 }
352}
353
354#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
356#[non_exhaustive]
357pub enum Algorithm {
358 Default,
360 Zhongzhou,
362}
363
364#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
366#[non_exhaustive]
367pub enum AstroType {
368 Heaven,
370 Earth,
372 Human,
374}
375
376#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
384pub enum LeapMonth {
385 NotLeap,
387 Leap,
389 LeapFixed,
391}
392
393impl LeapMonth {
394 pub fn from_flags(is_leap_month: bool, fix_leap: bool) -> Self {
396 match (is_leap_month, fix_leap) {
397 (false, _) => LeapMonth::NotLeap,
398 (true, false) => LeapMonth::Leap,
399 (true, true) => LeapMonth::LeapFixed,
400 }
401 }
402
403 pub fn is_leap_month(self) -> bool {
405 !matches!(self, LeapMonth::NotLeap)
406 }
407
408 pub fn fix_leap(self) -> bool {
410 matches!(self, LeapMonth::LeapFixed)
411 }
412}
413
414#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
416pub enum YearDivide {
417 Normal,
419 Exact,
421}
422
423#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
425pub enum HoroscopeDivide {
426 Normal,
428 Exact,
430}
431
432#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
434pub enum AgeDivide {
435 Normal,
437 Birthday,
439}
440
441#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
443pub enum DayDivide {
444 Forward,
446 Current,
448}
449
450macro_rules! config_switch_keys {
455 ($ty:ident { $($variant:ident => $key:literal),+ $(,)? }) => {
456 impl $ty {
457 pub fn as_key(self) -> &'static str {
459 match self {
460 $($ty::$variant => $key),+
461 }
462 }
463
464 pub fn from_key(key: &str) -> Option<Self> {
466 match key {
467 $($key => Some($ty::$variant),)+
468 _ => None,
469 }
470 }
471 }
472 };
473}
474
475config_switch_keys!(YearDivide { Normal => "normal", Exact => "exact" });
476config_switch_keys!(HoroscopeDivide { Normal => "normal", Exact => "exact" });
477config_switch_keys!(AgeDivide { Normal => "normal", Birthday => "birthday" });
478config_switch_keys!(DayDivide { Forward => "forward", Current => "current" });
479config_switch_keys!(Algorithm { Default => "default", Zhongzhou => "zhongzhou" });
480config_switch_keys!(AstroType { Heaven => "heaven", Earth => "earth", Human => "human" });
481config_switch_keys!(LeapMonth { NotLeap => "notLeap", Leap => "leap", LeapFixed => "leapFixed" });
482
483#[derive(Debug, Clone, PartialEq, Eq, Default)]
490pub struct TableOverrides {
491 mutagens: std::collections::HashMap<HeavenlyStem, [crate::data::stars::StarKey; 4]>,
493 brightness: std::collections::HashMap<crate::data::stars::StarKey, [Option<Brightness>; 12]>,
495}
496
497impl TableOverrides {
498 pub fn set_mutagens(&mut self, stem: HeavenlyStem, stars: [crate::data::stars::StarKey; 4]) {
500 self.mutagens.insert(stem, stars);
501 }
502
503 pub fn set_brightness(
505 &mut self,
506 star: crate::data::stars::StarKey,
507 table: [Option<Brightness>; 12],
508 ) {
509 self.brightness.insert(star, table);
510 }
511
512 pub fn mutagens_of(&self, stem: HeavenlyStem) -> Option<&[crate::data::stars::StarKey; 4]> {
514 self.mutagens.get(&stem)
515 }
516
517 pub fn brightness_of(
519 &self,
520 star: crate::data::stars::StarKey,
521 ) -> Option<&[Option<Brightness>; 12]> {
522 self.brightness.get(&star)
523 }
524
525 pub fn is_empty(&self) -> bool {
527 self.mutagens.is_empty() && self.brightness.is_empty()
528 }
529}
530
531#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
533pub struct Config {
534 pub year_divide: YearDivide,
536 pub horoscope_divide: HoroscopeDivide,
538 pub age_divide: AgeDivide,
540 pub day_divide: DayDivide,
542 pub algorithm: Algorithm,
544 pub astro_type: AstroType,
546 #[serde(skip)]
551 pub overrides: Option<std::sync::Arc<TableOverrides>>,
552}
553
554impl Default for Config {
555 fn default() -> Self {
557 Config {
558 year_divide: YearDivide::Normal,
559 horoscope_divide: HoroscopeDivide::Normal,
560 age_divide: AgeDivide::Normal,
561 day_divide: DayDivide::Forward,
562 algorithm: Algorithm::Default,
563 astro_type: AstroType::Heaven,
564 overrides: None,
565 }
566 }
567}
568
569impl Config {
570 pub fn with_astro_type(mut self, astro_type: AstroType) -> Self {
572 self.astro_type = astro_type;
573 self
574 }
575
576 pub fn with_mutagens(
578 mut self,
579 stem: HeavenlyStem,
580 stars: [crate::data::stars::StarKey; 4],
581 ) -> Self {
582 let mut tables = self
583 .overrides
584 .take()
585 .map_or_else(TableOverrides::default, |arc| {
586 std::sync::Arc::try_unwrap(arc).unwrap_or_else(|shared| (*shared).clone())
587 });
588 tables.set_mutagens(stem, stars);
589 self.overrides = Some(std::sync::Arc::new(tables));
590 self
591 }
592
593 pub fn with_brightness(
595 mut self,
596 star: crate::data::stars::StarKey,
597 table: [Option<Brightness>; 12],
598 ) -> Self {
599 let mut tables = self
600 .overrides
601 .take()
602 .map_or_else(TableOverrides::default, |arc| {
603 std::sync::Arc::try_unwrap(arc).unwrap_or_else(|shared| (*shared).clone())
604 });
605 tables.set_brightness(star, table);
606 self.overrides = Some(std::sync::Arc::new(tables));
607 self
608 }
609
610 pub fn mutagens_of(&self, stem: HeavenlyStem) -> [crate::data::stars::StarKey; 4] {
612 self.overrides
613 .as_ref()
614 .and_then(|t| t.mutagens_of(stem))
615 .copied()
616 .unwrap_or_else(|| crate::data::heavenly_stems::get_heavenly_stem_info(stem).mutagen)
617 }
618
619 pub fn brightness_of(
623 &self,
624 star: crate::data::stars::StarKey,
625 palace_index: usize,
626 ) -> Option<Brightness> {
627 let index = palace_index % 12;
628 if let Some(table) = self.overrides.as_ref().and_then(|t| t.brightness_of(star)) {
629 return table[index];
630 }
631 crate::data::stars::get_brightness_table(star)?[index]
632 }
633}
634
635pub type TimeIndex = u8;
637
638impl HeavenlyStem {
639 pub fn index(&self) -> usize {
641 *self as usize
642 }
643 pub fn from_index(i: usize) -> Self {
645 crate::data::constants::HEAVENLY_STEMS[i % 10]
646 }
647}
648
649impl EarthlyBranch {
650 pub fn index(&self) -> usize {
652 *self as usize
653 }
654 pub fn from_index(i: usize) -> Self {
656 crate::data::constants::EARTHLY_BRANCHES[i % 12]
657 }
658}
659
660impl Palace {
661 pub fn index(&self) -> usize {
663 *self as usize
664 }
665 pub fn from_index(i: usize) -> Self {
667 crate::data::constants::PALACES[i % 12]
668 }
669}
670
671impl FiveElementsClass {
672 pub fn value(&self) -> usize {
674 *self as usize
675 }
676}
677
678impl Palace {
679 pub fn as_key(&self) -> &'static str {
681 match self {
682 Palace::Soul => "soulPalace",
683 Palace::Parents => "parentsPalace",
684 Palace::Spirit => "spiritPalace",
685 Palace::Property => "propertyPalace",
686 Palace::Career => "careerPalace",
687 Palace::Friends => "friendsPalace",
688 Palace::Surface => "surfacePalace",
689 Palace::Health => "healthPalace",
690 Palace::Wealth => "wealthPalace",
691 Palace::Children => "childrenPalace",
692 Palace::Spouse => "spousePalace",
693 Palace::Siblings => "siblingsPalace",
694 }
695 }
696}
697
698impl HeavenlyStem {
699 pub fn as_key(&self) -> &'static str {
701 match self {
702 HeavenlyStem::Jia => "jiaHeavenly",
703 HeavenlyStem::Yi => "yiHeavenly",
704 HeavenlyStem::Bing => "bingHeavenly",
705 HeavenlyStem::Ding => "dingHeavenly",
706 HeavenlyStem::Wu => "wuHeavenly",
707 HeavenlyStem::Ji => "jiHeavenly",
708 HeavenlyStem::Geng => "gengHeavenly",
709 HeavenlyStem::Xin => "xinHeavenly",
710 HeavenlyStem::Ren => "renHeavenly",
711 HeavenlyStem::Gui => "guiHeavenly",
712 }
713 }
714}
715
716impl EarthlyBranch {
717 pub fn as_key(&self) -> &'static str {
719 match self {
720 EarthlyBranch::Zi => "ziEarthly",
721 EarthlyBranch::Chou => "chouEarthly",
722 EarthlyBranch::Yin => "yinEarthly",
723 EarthlyBranch::Mao => "maoEarthly",
724 EarthlyBranch::Chen => "chenEarthly",
725 EarthlyBranch::Si => "siEarthly",
726 EarthlyBranch::Wu => "wuEarthly",
727 EarthlyBranch::Wei => "weiEarthly",
728 EarthlyBranch::Shen => "shenEarthly",
729 EarthlyBranch::You => "youEarthly",
730 EarthlyBranch::Xu => "xuEarthly",
731 EarthlyBranch::Hai => "haiEarthly",
732 }
733 }
734}
735
736impl Mutagen {
737 pub fn as_key(&self) -> &'static str {
739 match self {
740 Mutagen::Lu => "sihuaLu",
741 Mutagen::Quan => "sihuaQuan",
742 Mutagen::Ke => "sihuaKe",
743 Mutagen::Ji => "sihuaJi",
744 }
745 }
746}
747
748impl Brightness {
749 pub fn as_key(&self) -> &'static str {
751 match self {
752 Brightness::Miao => "miao",
753 Brightness::Wang => "wang",
754 Brightness::De => "de",
755 Brightness::Li => "li",
756 Brightness::Ping => "ping",
757 Brightness::Bu => "bu",
758 Brightness::Xian => "xian",
759 }
760 }
761}
762
763impl FiveElementsClass {
764 pub fn as_key(&self) -> &'static str {
766 match self {
767 FiveElementsClass::Water2nd => "water2nd",
768 FiveElementsClass::Wood3rd => "wood3rd",
769 FiveElementsClass::Metal4th => "metal4th",
770 FiveElementsClass::Earth5th => "earth5th",
771 FiveElementsClass::Fire6th => "fire6th",
772 }
773 }
774}
775
776impl Palace {
777 pub fn from_key(key: &str) -> Option<Palace> {
779 match key {
780 "soulPalace" => Some(Palace::Soul),
781 "parentsPalace" => Some(Palace::Parents),
782 "spiritPalace" => Some(Palace::Spirit),
783 "propertyPalace" => Some(Palace::Property),
784 "careerPalace" => Some(Palace::Career),
785 "friendsPalace" => Some(Palace::Friends),
786 "surfacePalace" => Some(Palace::Surface),
787 "healthPalace" => Some(Palace::Health),
788 "wealthPalace" => Some(Palace::Wealth),
789 "childrenPalace" => Some(Palace::Children),
790 "spousePalace" => Some(Palace::Spouse),
791 "siblingsPalace" => Some(Palace::Siblings),
792 _ => None,
793 }
794 }
795}
796
797impl HeavenlyStem {
798 pub fn from_key(key: &str) -> Option<HeavenlyStem> {
800 match key {
801 "jiaHeavenly" => Some(HeavenlyStem::Jia),
802 "yiHeavenly" => Some(HeavenlyStem::Yi),
803 "bingHeavenly" => Some(HeavenlyStem::Bing),
804 "dingHeavenly" => Some(HeavenlyStem::Ding),
805 "wuHeavenly" => Some(HeavenlyStem::Wu),
806 "jiHeavenly" => Some(HeavenlyStem::Ji),
807 "gengHeavenly" => Some(HeavenlyStem::Geng),
808 "xinHeavenly" => Some(HeavenlyStem::Xin),
809 "renHeavenly" => Some(HeavenlyStem::Ren),
810 "guiHeavenly" => Some(HeavenlyStem::Gui),
811 _ => None,
812 }
813 }
814}
815
816impl EarthlyBranch {
817 pub fn from_key(key: &str) -> Option<EarthlyBranch> {
819 match key {
820 "ziEarthly" => Some(EarthlyBranch::Zi),
821 "chouEarthly" => Some(EarthlyBranch::Chou),
822 "yinEarthly" => Some(EarthlyBranch::Yin),
823 "maoEarthly" => Some(EarthlyBranch::Mao),
824 "chenEarthly" => Some(EarthlyBranch::Chen),
825 "siEarthly" => Some(EarthlyBranch::Si),
826 "wuEarthly" => Some(EarthlyBranch::Wu),
827 "weiEarthly" => Some(EarthlyBranch::Wei),
828 "shenEarthly" => Some(EarthlyBranch::Shen),
829 "youEarthly" => Some(EarthlyBranch::You),
830 "xuEarthly" => Some(EarthlyBranch::Xu),
831 "haiEarthly" => Some(EarthlyBranch::Hai),
832 _ => None,
833 }
834 }
835}
836
837impl Mutagen {
838 pub fn from_key(key: &str) -> Option<Mutagen> {
840 match key {
841 "sihuaLu" => Some(Mutagen::Lu),
842 "sihuaQuan" => Some(Mutagen::Quan),
843 "sihuaKe" => Some(Mutagen::Ke),
844 "sihuaJi" => Some(Mutagen::Ji),
845 _ => None,
846 }
847 }
848}
849
850impl Brightness {
851 pub fn from_key(key: &str) -> Option<Brightness> {
853 match key {
854 "miao" => Some(Brightness::Miao),
855 "wang" => Some(Brightness::Wang),
856 "de" => Some(Brightness::De),
857 "li" => Some(Brightness::Li),
858 "ping" => Some(Brightness::Ping),
859 "bu" => Some(Brightness::Bu),
860 "xian" => Some(Brightness::Xian),
861 _ => None,
862 }
863 }
864}
865
866impl FiveElementsClass {
867 pub fn from_key(key: &str) -> Option<FiveElementsClass> {
869 match key {
870 "water2nd" => Some(FiveElementsClass::Water2nd),
871 "wood3rd" => Some(FiveElementsClass::Wood3rd),
872 "metal4th" => Some(FiveElementsClass::Metal4th),
873 "earth5th" => Some(FiveElementsClass::Earth5th),
874 "fire6th" => Some(FiveElementsClass::Fire6th),
875 _ => None,
876 }
877 }
878}
879
880#[cfg(test)]
881mod key_roundtrip_tests {
882 use super::*;
883 use crate::astro::builder::by_solar;
884 use crate::data::constants::{EARTHLY_BRANCHES, HEAVENLY_STEMS, PALACES};
885 use crate::data::stars::StarKey;
886
887 #[test]
889 fn test_language_code_aliases() {
890 for lang in [
891 Language::ZhCN,
892 Language::ZhTW,
893 Language::EnUS,
894 Language::JaJP,
895 Language::KoKR,
896 Language::ViVN,
897 ] {
898 let code = lang.as_code();
899 assert_eq!(Language::from_code(code), Some(lang));
900 assert_eq!(Language::from_code(&code.to_lowercase()), Some(lang));
901 assert_eq!(Language::from_code(&code.replace('-', "_")), Some(lang));
902 }
903 assert_eq!(Language::from_code("zh_cn"), Some(Language::ZhCN));
904 assert_eq!(Language::from_code("klingon"), None);
905 }
906
907 #[test]
909 fn test_enum_key_roundtrip() {
910 for p in PALACES {
911 assert_eq!(Palace::from_key(p.as_key()), Some(p), "{p:?}");
912 }
913 for s in HEAVENLY_STEMS {
914 assert_eq!(HeavenlyStem::from_key(s.as_key()), Some(s), "{s:?}");
915 }
916 for b in EARTHLY_BRANCHES {
917 assert_eq!(EarthlyBranch::from_key(b.as_key()), Some(b), "{b:?}");
918 }
919 for m in [Mutagen::Lu, Mutagen::Quan, Mutagen::Ke, Mutagen::Ji] {
920 assert_eq!(Mutagen::from_key(m.as_key()), Some(m), "{m:?}");
921 }
922 for b in [
923 Brightness::Miao,
924 Brightness::Wang,
925 Brightness::De,
926 Brightness::Li,
927 Brightness::Ping,
928 Brightness::Bu,
929 Brightness::Xian,
930 ] {
931 assert_eq!(Brightness::from_key(b.as_key()), Some(b), "{b:?}");
932 }
933 for c in [
934 FiveElementsClass::Water2nd,
935 FiveElementsClass::Wood3rd,
936 FiveElementsClass::Metal4th,
937 FiveElementsClass::Earth5th,
938 FiveElementsClass::Fire6th,
939 ] {
940 assert_eq!(FiveElementsClass::from_key(c.as_key()), Some(c), "{c:?}");
941 }
942
943 for v in [YearDivide::Normal, YearDivide::Exact] {
944 assert_eq!(YearDivide::from_key(v.as_key()), Some(v), "{v:?}");
945 }
946 for v in [HoroscopeDivide::Normal, HoroscopeDivide::Exact] {
947 assert_eq!(HoroscopeDivide::from_key(v.as_key()), Some(v), "{v:?}");
948 }
949 for v in [AgeDivide::Normal, AgeDivide::Birthday] {
950 assert_eq!(AgeDivide::from_key(v.as_key()), Some(v), "{v:?}");
951 }
952 for v in [DayDivide::Forward, DayDivide::Current] {
953 assert_eq!(DayDivide::from_key(v.as_key()), Some(v), "{v:?}");
954 }
955 for v in [Algorithm::Default, Algorithm::Zhongzhou] {
956 assert_eq!(Algorithm::from_key(v.as_key()), Some(v), "{v:?}");
957 }
958 for v in [AstroType::Heaven, AstroType::Earth, AstroType::Human] {
959 assert_eq!(AstroType::from_key(v.as_key()), Some(v), "{v:?}");
960 }
961 for v in [LeapMonth::NotLeap, LeapMonth::Leap, LeapMonth::LeapFixed] {
962 assert_eq!(LeapMonth::from_key(v.as_key()), Some(v), "{v:?}");
963 assert_eq!(
964 LeapMonth::from_flags(v.is_leap_month(), v.fix_leap()),
965 v,
966 "{v:?}"
967 );
968 }
969 assert_eq!(LeapMonth::from_flags(false, true), LeapMonth::NotLeap);
970
971 assert_eq!(Palace::from_key("bodyPalace"), None);
972 assert_eq!(StarKey::from_key("nope"), None);
973 assert_eq!(Algorithm::from_key("normal"), None);
974 }
975
976 #[test]
978 fn test_star_key_roundtrip_over_chart() {
979 let chart = by_solar(
980 "2000-8-16",
981 2,
982 Gender::Female,
983 true,
984 Language::ZhCN,
985 Config::default(),
986 )
987 .unwrap();
988
989 let mut checked = 0;
990 for palace in &chart.palaces {
991 for star in palace
992 .major_stars
993 .iter()
994 .chain(palace.minor_stars.iter())
995 .chain(palace.adjective_stars.iter())
996 {
997 assert_eq!(
998 StarKey::from_key(star.key.as_key()),
999 Some(star.key),
1000 "{:?} 的 key 往返失败",
1001 star.key
1002 );
1003 checked += 1;
1004 }
1005 assert_eq!(
1006 StarKey::from_key(palace.changsheng12.as_key()),
1007 Some(palace.changsheng12)
1008 );
1009 assert_eq!(
1010 StarKey::from_key(palace.suiqian12.as_key()),
1011 Some(palace.suiqian12)
1012 );
1013 }
1014 assert!(checked > 60, "覆盖的星耀太少:{checked}");
1015 }
1016}