1use serde::{Deserialize, Serialize};
20
21use crate::data::stars::{MUTAGEN, StarKey};
22use crate::data::types::*;
23use crate::error::BridgeError;
24use crate::i18n::{
25 translate_brightness, translate_earthly_branch, translate_five_elements_class,
26 translate_gender, translate_heavenly_stem, translate_mutagen, translate_palace,
27 translate_pattern, translate_star,
28};
29use crate::models::astrolabe::Astrolabe;
30use crate::models::horoscope::{HoroscopeData, HoroscopeItem};
31use crate::models::palace::PalaceData;
32use crate::models::star::Star;
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
40#[serde(rename_all = "camelCase")]
41pub struct StarDto {
42 pub key: String,
44 pub name: String,
46 #[serde(rename = "type")]
48 pub star_type: String,
49 pub scope: String,
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub brightness: Option<String>,
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub brightness_key: Option<String>,
57 #[serde(skip_serializing_if = "Option::is_none")]
59 pub mutagen: Option<String>,
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub mutagen_key: Option<String>,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67#[serde(rename_all = "camelCase")]
68pub struct DecadalDto {
69 pub range: [u32; 2],
71 pub heavenly_stem: String,
73 pub heavenly_stem_key: String,
75 pub earthly_branch: String,
77 pub earthly_branch_key: String,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
83#[serde(rename_all = "camelCase")]
84pub struct PalaceDto {
85 pub index: usize,
87 pub name: String,
89 pub name_key: String,
91 pub is_body_palace: bool,
93 pub is_original_palace: bool,
95 pub heavenly_stem: String,
97 pub heavenly_stem_key: String,
99 pub earthly_branch: String,
101 pub earthly_branch_key: String,
103 pub major_stars: Vec<StarDto>,
105 pub minor_stars: Vec<StarDto>,
107 pub adjective_stars: Vec<StarDto>,
109 pub changsheng12: String,
111 pub changsheng12_key: String,
113 pub boshi12: String,
115 pub boshi12_key: String,
117 pub jiangqian12: String,
119 pub jiangqian12_key: String,
121 pub suiqian12: String,
123 pub suiqian12_key: String,
125 pub decadal: DecadalDto,
127 pub ages: Vec<u32>,
129 pub mutagen_star_keys: [String; 4],
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
137#[serde(rename_all = "camelCase")]
138pub struct RawLunarDateDto {
139 pub lunar_year: i64,
141 pub lunar_month: u32,
143 pub lunar_day: u32,
145 pub is_leap: bool,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
151#[serde(rename_all = "camelCase")]
152pub struct RawChineseDateDto {
153 pub yearly: [String; 2],
155 pub monthly: [String; 2],
157 pub daily: [String; 2],
159 pub hourly: [String; 2],
161 pub yearly_keys: [String; 2],
163 pub monthly_keys: [String; 2],
165 pub daily_keys: [String; 2],
167 pub hourly_keys: [String; 2],
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize)]
173#[serde(rename_all = "camelCase")]
174pub struct RawDatesDto {
175 pub lunar_date: RawLunarDateDto,
177 pub chinese_date: RawChineseDateDto,
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
183#[serde(rename_all = "camelCase")]
184pub struct ConfigDto {
185 pub year_divide: String,
187 pub horoscope_divide: String,
189 pub age_divide: String,
191 pub day_divide: String,
193 pub algorithm: String,
195 pub astro_type: String,
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
201#[serde(rename_all = "camelCase")]
202pub struct AstrolabeDto {
203 pub gender: String,
205 pub solar_date: String,
207 pub lunar_date: String,
209 pub chinese_date: String,
211 pub raw_dates: RawDatesDto,
213 pub time: String,
215 pub time_range: String,
217 pub sign: String,
219 pub zodiac: String,
221 pub earthly_branch_of_soul_palace: String,
223 pub earthly_branch_of_soul_palace_key: String,
225 pub earthly_branch_of_body_palace: String,
227 pub earthly_branch_of_body_palace_key: String,
229 pub soul: String,
231 pub soul_key: String,
233 pub body: String,
235 pub body_key: String,
237 pub five_elements_class: String,
239 pub five_elements_class_key: String,
241 pub palaces: Vec<PalaceDto>,
243 pub gender_key: String,
245 pub time_index: u8,
247 pub fix_leap: bool,
249 pub language: String,
251 pub config: ConfigDto,
253}
254
255#[derive(Debug, Clone, Serialize, Deserialize)]
257#[serde(rename_all = "camelCase")]
258pub struct HoroscopeScopeDto {
259 pub index: usize,
261 pub name: String,
263 pub heavenly_stem: String,
265 pub heavenly_stem_key: String,
267 pub earthly_branch: String,
269 pub earthly_branch_key: String,
271 pub palace_names: Vec<String>,
273 pub palace_name_keys: Vec<String>,
275 pub mutagen: Vec<String>,
277 pub mutagen_keys: Vec<String>,
279 #[serde(skip_serializing_if = "Option::is_none")]
281 pub stars: Option<Vec<Vec<StarDto>>>,
282}
283
284#[derive(Debug, Clone, Serialize, Deserialize)]
286#[serde(rename_all = "camelCase")]
287pub struct AgeDto {
288 #[serde(flatten)]
290 pub base: HoroscopeScopeDto,
291 pub nominal_age: u32,
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize)]
297pub struct YearlyDecStarDto {
298 pub suiqian12: Vec<String>,
300 #[serde(rename = "suiqian12Keys")]
302 pub suiqian12_keys: Vec<String>,
303 pub jiangqian12: Vec<String>,
305 #[serde(rename = "jiangqian12Keys")]
307 pub jiangqian12_keys: Vec<String>,
308}
309
310#[derive(Debug, Clone, Serialize, Deserialize)]
312#[serde(rename_all = "camelCase")]
313pub struct YearlyDto {
314 #[serde(flatten)]
316 pub base: HoroscopeScopeDto,
317 pub yearly_dec_star: YearlyDecStarDto,
319}
320
321#[derive(Debug, Clone, Serialize, Deserialize)]
323#[serde(rename_all = "camelCase")]
324pub struct HoroscopeDto {
325 pub lunar_date: String,
327 pub solar_date: String,
329 pub decadal: HoroscopeScopeDto,
331 pub age: AgeDto,
333 pub yearly: YearlyDto,
335 pub monthly: HoroscopeScopeDto,
337 pub daily: HoroscopeScopeDto,
339 pub hourly: HoroscopeScopeDto,
341}
342
343pub(crate) fn error_json(err: &BridgeError) -> String {
352 #[derive(Serialize)]
353 struct ErrorJson<'a> {
354 error: &'a str,
355 code: &'a str,
356 }
357 serde_json::to_string(&ErrorJson {
358 error: &err.message,
359 code: err.code,
360 })
361 .expect("错误 JSON 只含两个字符串字段,序列化不会失败")
362}
363
364pub(crate) fn panic_message(panic: &(dyn std::any::Any + Send)) -> &str {
367 panic
368 .downcast_ref::<String>()
369 .map(String::as_str)
370 .or_else(|| panic.downcast_ref::<&str>().copied())
371 .unwrap_or("computation panicked")
372}
373
374#[derive(Debug, Default, Deserialize)]
376#[serde(rename_all = "camelCase", default)]
377struct ConfigPatch {
378 year_divide: Option<String>,
379 horoscope_divide: Option<String>,
380 age_divide: Option<String>,
381 day_divide: Option<String>,
382 algorithm: Option<String>,
383 astro_type: Option<String>,
384 mutagens: Option<std::collections::HashMap<String, Vec<String>>>,
386 brightness: Option<std::collections::HashMap<String, Vec<String>>>,
388}
389
390pub fn parse_config_json(json: Option<&str>) -> Result<Config, BridgeError> {
396 let json = match json {
397 None => return Ok(Config::default()),
398 Some(s) if s.trim().is_empty() => return Ok(Config::default()),
399 Some(s) => s,
400 };
401 let patch: ConfigPatch = serde_json::from_str(json)
402 .map_err(|e| BridgeError::invalid_argument(format!("invalid config JSON: {e}")))?;
403 config_from_patch(patch)
404}
405
406pub fn parse_config_value(value: &serde_json::Value) -> Result<Config, BridgeError> {
412 let patch: ConfigPatch = serde_json::from_value(value.clone())
413 .map_err(|e| BridgeError::invalid_argument(format!("invalid config: {e}")))?;
414 config_from_patch(patch)
415}
416
417fn bad_switch(field: &str, value: &str, expected: &str) -> BridgeError {
419 BridgeError::invalid_argument(format!("invalid {field} '{value}': expected {expected}"))
420}
421
422fn config_from_patch(patch: ConfigPatch) -> Result<Config, BridgeError> {
423 let mut config = Config::default();
424 if let Some(v) = patch.year_divide {
425 config.year_divide = YearDivide::from_key(&v)
426 .ok_or_else(|| bad_switch("yearDivide", &v, "'normal' or 'exact'"))?;
427 }
428 if let Some(v) = patch.horoscope_divide {
429 config.horoscope_divide = HoroscopeDivide::from_key(&v)
430 .ok_or_else(|| bad_switch("horoscopeDivide", &v, "'normal' or 'exact'"))?;
431 }
432 if let Some(v) = patch.age_divide {
433 config.age_divide = AgeDivide::from_key(&v)
434 .ok_or_else(|| bad_switch("ageDivide", &v, "'normal' or 'birthday'"))?;
435 }
436 if let Some(v) = patch.day_divide {
437 config.day_divide = DayDivide::from_key(&v)
438 .ok_or_else(|| bad_switch("dayDivide", &v, "'forward' or 'current'"))?;
439 }
440 if let Some(v) = patch.algorithm {
441 config.algorithm = Algorithm::from_key(&v)
442 .ok_or_else(|| bad_switch("algorithm", &v, "'default' or 'zhongzhou'"))?;
443 }
444 if let Some(v) = patch.astro_type {
445 config.astro_type = AstroType::from_key(&v)
446 .ok_or_else(|| bad_switch("astroType", &v, "'heaven', 'earth' or 'human'"))?;
447 }
448
449 if let Some(map) = patch.mutagens {
451 for (stem_key, star_keys) in map {
452 let stem = HeavenlyStem::from_key(&stem_key).ok_or_else(|| {
453 BridgeError::invalid_argument(format!(
454 "invalid mutagens key '{stem_key}': unknown heavenly stem"
455 ))
456 })?;
457 if star_keys.len() != 4 {
458 return Err(BridgeError::invalid_argument(format!(
459 "invalid mutagens for '{stem_key}': expected 4 stars (lu, quan, ke, ji), got {}",
460 star_keys.len()
461 )));
462 }
463 let mut stars = [StarKey::ZiweiMaj; 4];
464 for (i, key) in star_keys.iter().enumerate() {
465 stars[i] = StarKey::from_key(key).ok_or_else(|| {
466 BridgeError::invalid_argument(format!(
467 "invalid mutagens for '{stem_key}': unknown star '{key}'"
468 ))
469 })?;
470 }
471 config = config.with_mutagens(stem, stars);
472 }
473 }
474 if let Some(map) = patch.brightness {
475 for (star_key, brightness_keys) in map {
476 let star = StarKey::from_key(&star_key).ok_or_else(|| {
477 BridgeError::invalid_argument(format!(
478 "invalid brightness key '{star_key}': unknown star"
479 ))
480 })?;
481 if brightness_keys.len() != 12 {
482 return Err(BridgeError::invalid_argument(format!(
483 "invalid brightness for '{star_key}': expected 12 entries, got {}",
484 brightness_keys.len()
485 )));
486 }
487 let mut table = [None; 12];
488 for (i, key) in brightness_keys.iter().enumerate() {
489 if key.is_empty() {
491 continue;
492 }
493 table[i] = Some(Brightness::from_key(key).ok_or_else(|| {
494 BridgeError::invalid_argument(format!(
495 "invalid brightness for '{star_key}': unknown brightness '{key}'"
496 ))
497 })?);
498 }
499 config = config.with_brightness(star, table);
500 }
501 }
502
503 Ok(config)
504}
505
506impl From<Config> for ConfigDto {
507 fn from(c: Config) -> Self {
508 ConfigDto {
509 year_divide: c.year_divide.as_key().to_string(),
510 horoscope_divide: c.horoscope_divide.as_key().to_string(),
511 age_divide: c.age_divide.as_key().to_string(),
512 day_divide: c.day_divide.as_key().to_string(),
513 algorithm: c.algorithm.as_key().to_string(),
514 astro_type: c.astro_type.as_key().to_string(),
515 }
516 }
517}
518
519fn is_mutagen_candidate(key: crate::data::stars::StarKey) -> bool {
526 use crate::data::stars::StarKey::*;
527 matches!(
528 key,
529 ZiweiMaj
530 | TianjiMaj
531 | TaiyangMaj
532 | WuquMaj
533 | TiantongMaj
534 | LianzhenMaj
535 | TianfuMaj
536 | TaiyinMaj
537 | TanlangMaj
538 | JumenMaj
539 | TianxiangMaj
540 | TianliangMaj
541 | QishaMaj
542 | PojunMaj
543 | WenchangMin
544 | WenquMin
545 | ZuofuMin
546 | YoubiMin
547 )
548}
549
550fn primary_star_dto(s: &Star, lang: Language) -> StarDto {
553 StarDto {
554 key: s.key.as_key().to_string(),
555 name: s.name.clone(),
556 star_type: StarType::as_key(s.star_type).to_string(),
557 scope: Scope::as_key(s.scope).to_string(),
558 brightness: Some(
559 s.brightness
560 .map(|b| translate_brightness(b, lang).to_string())
561 .unwrap_or_default(),
562 ),
563 brightness_key: s.brightness.map(|b| b.as_key().to_string()),
564 mutagen: if is_mutagen_candidate(s.key) {
565 Some(
566 s.mutagen
567 .map(|m| translate_mutagen(m, lang).to_string())
568 .unwrap_or_default(),
569 )
570 } else {
571 None
572 },
573 mutagen_key: s.mutagen.map(|m| m.as_key().to_string()),
574 }
575}
576
577fn bare_star_dto(s: &Star) -> StarDto {
579 StarDto {
580 key: s.key.as_key().to_string(),
581 name: s.name.clone(),
582 star_type: StarType::as_key(s.star_type).to_string(),
583 scope: Scope::as_key(s.scope).to_string(),
584 brightness: None,
585 brightness_key: None,
586 mutagen: None,
587 mutagen_key: None,
588 }
589}
590
591fn pillar(p: (HeavenlyStem, EarthlyBranch), lang: Language) -> [String; 2] {
592 [
593 translate_heavenly_stem(p.0, lang).to_string(),
594 translate_earthly_branch(p.1, lang).to_string(),
595 ]
596}
597
598fn pillar_keys(p: (HeavenlyStem, EarthlyBranch)) -> [String; 2] {
600 [p.0.as_key().to_string(), p.1.as_key().to_string()]
601}
602
603fn palace_dto(p: &PalaceData, lang: Language) -> PalaceDto {
604 PalaceDto {
605 index: p.index,
606 name: translate_palace(p.name, lang).to_string(),
607 name_key: p.name.as_key().to_string(),
608 is_body_palace: p.is_body_palace,
609 is_original_palace: p.is_original_palace,
610 heavenly_stem: translate_heavenly_stem(p.heavenly_stem, lang).to_string(),
611 heavenly_stem_key: p.heavenly_stem.as_key().to_string(),
612 earthly_branch: translate_earthly_branch(p.earthly_branch, lang).to_string(),
613 earthly_branch_key: p.earthly_branch.as_key().to_string(),
614 major_stars: p
615 .major_stars
616 .iter()
617 .map(|s| primary_star_dto(s, lang))
618 .collect(),
619 minor_stars: p
620 .minor_stars
621 .iter()
622 .map(|s| primary_star_dto(s, lang))
623 .collect(),
624 adjective_stars: p.adjective_stars.iter().map(bare_star_dto).collect(),
625 changsheng12: translate_star(p.changsheng12, lang).to_string(),
626 changsheng12_key: p.changsheng12.as_key().to_string(),
627 boshi12: translate_star(p.boshi12, lang).to_string(),
628 boshi12_key: p.boshi12.as_key().to_string(),
629 jiangqian12: translate_star(p.jiangqian12, lang).to_string(),
630 jiangqian12_key: p.jiangqian12.as_key().to_string(),
631 suiqian12: translate_star(p.suiqian12, lang).to_string(),
632 suiqian12_key: p.suiqian12.as_key().to_string(),
633 decadal: DecadalDto {
634 range: [p.decadal.range.0, p.decadal.range.1],
635 heavenly_stem: translate_heavenly_stem(p.decadal.heavenly_stem, lang).to_string(),
636 heavenly_stem_key: p.decadal.heavenly_stem.as_key().to_string(),
637 earthly_branch: translate_earthly_branch(p.decadal.earthly_branch, lang).to_string(),
638 earthly_branch_key: p.decadal.earthly_branch.as_key().to_string(),
639 },
640 ages: p.ages.clone(),
641 mutagen_star_keys: {
642 let stars = p.mutagen_stars(&MUTAGEN);
643 std::array::from_fn(|i| stars[i].as_key().to_string())
644 },
645 }
646}
647
648impl Astrolabe {
649 pub fn to_dto(&self) -> AstrolabeDto {
651 let lang = self.language;
652 AstrolabeDto {
653 gender: translate_gender(self.gender, lang).to_string(),
654 solar_date: self.solar_date.clone(),
655 lunar_date: self.lunar_date.clone(),
656 chinese_date: self.chinese_date.clone(),
657 raw_dates: RawDatesDto {
658 lunar_date: RawLunarDateDto {
659 lunar_year: self.raw_dates.lunar_date.lunar_year,
660 lunar_month: self.raw_dates.lunar_date.lunar_month,
661 lunar_day: self.raw_dates.lunar_date.lunar_day,
662 is_leap: self.raw_dates.lunar_date.is_leap,
663 },
664 chinese_date: RawChineseDateDto {
666 yearly: pillar(self.raw_dates.chinese_date.yearly, Language::ZhCN),
667 monthly: pillar(self.raw_dates.chinese_date.monthly, Language::ZhCN),
668 daily: pillar(self.raw_dates.chinese_date.daily, Language::ZhCN),
669 hourly: pillar(self.raw_dates.chinese_date.hourly, Language::ZhCN),
670 yearly_keys: pillar_keys(self.raw_dates.chinese_date.yearly),
671 monthly_keys: pillar_keys(self.raw_dates.chinese_date.monthly),
672 daily_keys: pillar_keys(self.raw_dates.chinese_date.daily),
673 hourly_keys: pillar_keys(self.raw_dates.chinese_date.hourly),
674 },
675 },
676 time: self.time.clone(),
677 time_range: self.time_range.clone(),
678 sign: self.sign.clone(),
679 zodiac: self.zodiac.clone(),
680 earthly_branch_of_soul_palace: translate_earthly_branch(
681 self.earthly_branch_of_soul_palace,
682 lang,
683 )
684 .to_string(),
685 earthly_branch_of_soul_palace_key: self
686 .earthly_branch_of_soul_palace
687 .as_key()
688 .to_string(),
689 earthly_branch_of_body_palace: translate_earthly_branch(
690 self.earthly_branch_of_body_palace,
691 lang,
692 )
693 .to_string(),
694 earthly_branch_of_body_palace_key: self
695 .earthly_branch_of_body_palace
696 .as_key()
697 .to_string(),
698 soul: translate_star(self.soul, lang).to_string(),
699 soul_key: self.soul.as_key().to_string(),
700 body: translate_star(self.body, lang).to_string(),
701 body_key: self.body.as_key().to_string(),
702 five_elements_class: translate_five_elements_class(self.five_elements_class, lang)
703 .to_string(),
704 five_elements_class_key: self.five_elements_class.as_key().to_string(),
705 palaces: self.palaces.iter().map(|p| palace_dto(p, lang)).collect(),
706 gender_key: match self.gender {
707 Gender::Male => "male",
708 Gender::Female => "female",
709 }
710 .to_string(),
711 time_index: self.time_index,
712 fix_leap: self.fix_leap,
713 language: lang.as_code().to_string(),
714 config: self.config.clone().into(),
715 }
716 }
717}
718
719fn scope_dto(item: &HoroscopeItem, lang: Language) -> HoroscopeScopeDto {
720 HoroscopeScopeDto {
721 index: item.index,
722 name: item.name.clone(),
723 heavenly_stem: translate_heavenly_stem(item.heavenly_stem, lang).to_string(),
724 heavenly_stem_key: item.heavenly_stem.as_key().to_string(),
725 earthly_branch: translate_earthly_branch(item.earthly_branch, lang).to_string(),
726 earthly_branch_key: item.earthly_branch.as_key().to_string(),
727 palace_names: item
728 .palace_names
729 .iter()
730 .map(|p| translate_palace(*p, lang).to_string())
731 .collect(),
732 palace_name_keys: item
733 .palace_names
734 .iter()
735 .map(|p| p.as_key().to_string())
736 .collect(),
737 mutagen: item
738 .mutagen
739 .iter()
740 .map(|k| translate_star(*k, lang).to_string())
741 .collect(),
742 mutagen_keys: item
743 .mutagen
744 .iter()
745 .map(|k| k.as_key().to_string())
746 .collect(),
747 stars: item.stars.as_ref().map(|groups| {
748 groups
749 .iter()
750 .map(|g| g.iter().map(bare_star_dto).collect())
751 .collect()
752 }),
753 }
754}
755
756impl HoroscopeData {
757 pub fn to_dto(&self, lang: Language) -> HoroscopeDto {
759 HoroscopeDto {
760 lunar_date: self.lunar_date.clone(),
761 solar_date: self.solar_date.clone(),
762 decadal: scope_dto(&self.decadal, lang),
763 age: AgeDto {
764 base: scope_dto(&self.age.base, lang),
765 nominal_age: self.age.nominal_age,
766 },
767 yearly: YearlyDto {
768 base: scope_dto(&self.yearly.base, lang),
769 yearly_dec_star: YearlyDecStarDto {
770 suiqian12: self
771 .yearly
772 .yearly_dec_star
773 .suiqian12
774 .iter()
775 .map(|k| translate_star(*k, lang).to_string())
776 .collect(),
777 suiqian12_keys: self
778 .yearly
779 .yearly_dec_star
780 .suiqian12
781 .iter()
782 .map(|k| k.as_key().to_string())
783 .collect(),
784 jiangqian12: self
785 .yearly
786 .yearly_dec_star
787 .jiangqian12
788 .iter()
789 .map(|k| translate_star(*k, lang).to_string())
790 .collect(),
791 jiangqian12_keys: self
792 .yearly
793 .yearly_dec_star
794 .jiangqian12
795 .iter()
796 .map(|k| k.as_key().to_string())
797 .collect(),
798 },
799 },
800 monthly: scope_dto(&self.monthly, lang),
801 daily: scope_dto(&self.daily, lang),
802 hourly: scope_dto(&self.hourly, lang),
803 }
804 }
805}
806
807#[derive(Debug, Clone, Serialize, Deserialize)]
813#[serde(rename_all = "camelCase")]
814pub struct PatternStarDto {
815 pub key: String,
817 pub name: String,
819 pub palace_index: usize,
821 #[serde(skip_serializing_if = "Option::is_none")]
823 pub brightness: Option<String>,
824 #[serde(skip_serializing_if = "Option::is_none")]
826 pub brightness_key: Option<String>,
827 #[serde(skip_serializing_if = "Option::is_none")]
829 pub mutagen: Option<String>,
830 #[serde(skip_serializing_if = "Option::is_none")]
832 pub mutagen_key: Option<String>,
833}
834
835#[derive(Debug, Clone, Serialize, Deserialize)]
837#[serde(rename_all = "camelCase")]
838pub struct PatternHitDto {
839 pub key: String,
841 pub name: String,
843 pub scope: String,
849 pub palace_index: usize,
851 pub palace_name: String,
853 pub palace_name_key: String,
855 #[serde(skip_serializing_if = "Option::is_none")]
857 pub variant: Option<String>,
858 pub broken: bool,
860 pub stars: Vec<PatternStarDto>,
862}
863
864impl crate::pattern::PatternHit {
865 pub fn to_dto(&self, palace_names: &[Palace], lang: Language) -> PatternHitDto {
868 let palace_name = palace_names
869 .get(self.palace)
870 .copied()
871 .unwrap_or(Palace::Soul);
872 PatternHitDto {
873 key: self.key.as_key().to_string(),
874 name: translate_pattern(self.key, lang).to_string(),
875 scope: self.scope.as_key().to_string(),
876 palace_index: self.palace,
877 palace_name: translate_palace(palace_name, lang).to_string(),
878 palace_name_key: palace_name.as_key().to_string(),
879 variant: self.variant.map(str::to_string),
880 broken: self.broken,
881 stars: self
882 .stars
883 .iter()
884 .map(|s| PatternStarDto {
885 key: s.star.as_key().to_string(),
886 name: translate_star(s.star, lang).to_string(),
887 palace_index: s.palace,
888 brightness: s
889 .brightness
890 .map(|b| translate_brightness(b, lang).to_string()),
891 brightness_key: s.brightness.map(|b| b.as_key().to_string()),
892 mutagen: s.mutagen.map(|m| translate_mutagen(m, lang).to_string()),
893 mutagen_key: s.mutagen.map(|m| m.as_key().to_string()),
894 })
895 .collect(),
896 }
897 }
898}
899
900impl Astrolabe {
901 pub fn patterns_dto(&self, config: &crate::pattern::PatternConfig) -> Vec<PatternHitDto> {
903 let names: Vec<Palace> = self.palaces.iter().map(|p| p.name).collect();
904 self.patterns_with(config)
905 .iter()
906 .map(|h| h.to_dto(&names, self.language))
907 .collect()
908 }
909}
910
911impl HoroscopeData {
912 pub fn patterns_dto(
914 &self,
915 astrolabe: &Astrolabe,
916 scope: Scope,
917 config: &crate::pattern::PatternConfig,
918 ) -> Vec<PatternHitDto> {
919 let names: Vec<Palace> = match self.scope_item(scope) {
920 Some(item) => item.palace_names.clone(),
921 None => astrolabe.palaces.iter().map(|p| p.name).collect(),
922 };
923 crate::pattern::patterns_at(astrolabe, self, scope, config)
924 .iter()
925 .map(|h| h.to_dto(&names, astrolabe.language))
926 .collect()
927 }
928}