cadmus_core/dictionary/monolingual/
metadata.rs1use std::collections::HashMap;
9
10use chrono::NaiveDate;
11use serde::{Deserialize, Serialize};
12
13pub type DictionariesResponse = HashMap<String, HashMap<String, DictionaryEntry>>;
19
20#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
22pub struct DictionaryEntry {
23 pub formats: String,
26
27 #[serde(with = "date_serde")]
29 pub updated: NaiveDate,
30
31 pub words: u64,
33}
34
35pub(super) fn download_url(lang: &str) -> String {
39 format!(
40 "https://www.reader-dict.com/file/{lang}/dictorg-{lang}-{lang}.zip",
41 lang = lang
42 )
43}
44
45pub(super) fn download_url_no_etym(lang: &str) -> String {
49 format!(
50 "https://www.reader-dict.com/file/{lang}/dictorg-{lang}-{lang}-noetym.zip",
51 lang = lang
52 )
53}
54
55mod date_serde {
56 use chrono::NaiveDate;
57 use serde::{Deserialize, Deserializer, Serialize, Serializer};
58
59 const FORMAT: &str = "%Y-%m-%d";
60
61 pub fn serialize<S>(date: &NaiveDate, serializer: S) -> Result<S::Ok, S::Error>
62 where
63 S: Serializer,
64 {
65 date.format(FORMAT).to_string().serialize(serializer)
66 }
67
68 pub fn deserialize<'de, D>(deserializer: D) -> Result<NaiveDate, D::Error>
69 where
70 D: Deserializer<'de>,
71 {
72 let s = String::deserialize(deserializer)?;
73 NaiveDate::parse_from_str(&s, FORMAT).map_err(serde::de::Error::custom)
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 fn make_entry() -> DictionaryEntry {
82 DictionaryEntry {
83 formats: "df,dic,dictorg,kobo,mobi,stardict".to_string(),
84 updated: NaiveDate::from_ymd_opt(2026, 4, 1).unwrap(),
85 words: 1_381_375,
86 }
87 }
88
89 #[test]
90 fn test_download_url_english() {
91 assert_eq!(
92 download_url("en"),
93 "https://www.reader-dict.com/file/en/dictorg-en-en.zip"
94 );
95 }
96
97 #[test]
98 fn test_download_url_no_etym_english() {
99 assert_eq!(
100 download_url_no_etym("en"),
101 "https://www.reader-dict.com/file/en/dictorg-en-en-noetym.zip"
102 );
103 }
104
105 #[test]
106 fn test_download_url_french() {
107 assert_eq!(
108 download_url("fr"),
109 "https://www.reader-dict.com/file/fr/dictorg-fr-fr.zip"
110 );
111 }
112
113 #[test]
114 fn test_deserialize_response() {
115 let json = r#"{
116 "en": {
117 "en": { "formats": "df,dic,dictorg,kobo,mobi,stardict", "updated": "2026-04-01", "words": 1381375 },
118 "fr": { "formats": "df,dic,dictorg,kobo,mobi,stardict", "updated": "2026-04-01", "words": 50000 }
119 },
120 "fr": {
121 "fr": { "formats": "df,dic,dictorg,kobo,mobi,stardict", "updated": "2026-03-01", "words": 2050655 }
122 }
123 }"#;
124
125 let resp: DictionariesResponse = serde_json::from_str(json).unwrap();
126
127 let en_entry = resp.get("en").and_then(|m| m.get("en")).unwrap();
128 assert_eq!(en_entry.words, 1_381_375);
129 assert_eq!(
130 en_entry.updated,
131 NaiveDate::from_ymd_opt(2026, 4, 1).unwrap()
132 );
133
134 let fr_entry = resp.get("fr").and_then(|m| m.get("fr")).unwrap();
135 assert_eq!(fr_entry.words, 2_050_655);
136
137 assert_eq!(*en_entry, make_entry());
138 }
139
140 #[test]
141 fn test_monolingual_filter() {
142 let json = r#"{
143 "en": {
144 "en": { "formats": "df,dic,dictorg,kobo,mobi,stardict", "updated": "2026-04-01", "words": 1381375 },
145 "fr": { "formats": "df,dic,dictorg,kobo,mobi,stardict", "updated": "2026-04-01", "words": 50000 }
146 },
147 "af": {
148 "en": { "formats": "df,dic,dictorg,kobo,mobi,stardict", "updated": "2026-04-01", "words": 8934 }
149 }
150 }"#;
151
152 let resp: DictionariesResponse = serde_json::from_str(json).unwrap();
153
154 let monolingual: Vec<(&str, &DictionaryEntry)> = resp
155 .iter()
156 .filter_map(|(lang, targets)| targets.get(lang.as_str()).map(|e| (lang.as_str(), e)))
157 .collect();
158
159 assert_eq!(monolingual.len(), 1);
160 assert_eq!(monolingual[0].0, "en");
161 }
162}