Skip to main content

neda_lib/providers/aladhan/
mod.rs

1use crate::core::config::{Config, GetType};
2use crate::core::prayers_times::{PrayersTimes, PrayersTimesStuck};
3use crate::core::providers::{Provider, ProviderError};
4use chrono::{Datelike, NaiveDate, NaiveTime};
5use serde_json::Value;
6use std::str::FromStr;
7mod aladhan_api_config;
8
9#[cfg(test)]
10mod test;
11
12pub struct AladhanProvider {
13    pub config: Config,
14}
15
16impl AladhanProvider {
17    pub fn new(config: Config) -> Self {
18        Self { config }
19    }
20}
21
22impl Provider for AladhanProvider {
23    fn get_prayers_times(&self, config: &self::Config) -> Result<PrayersTimesStuck, ProviderError> {
24        let aladhan_api_config = aladhan_api_config::get_aladhan_api_config();
25        match config.get_type {
26            GetType::Today => {
27                let url = format!(
28                    "{}/v{}/{}/{}-{}-{}?city={}&country={}",
29                    aladhan_api_config.base_url,
30                    aladhan_api_config.api_version,
31                    aladhan_api_config.endpoint.today,
32                    config.day,
33                    config.month,
34                    config.year,
35                    config.city,
36                    config.country
37                );
38
39                match reqwest::blocking::get(url) {
40                    Ok(response) => match response.status() {
41                        reqwest::StatusCode::OK => {
42                            match response.text() {
43                                Ok(body) => {
44                                    match serde_json::from_str::<Value>(&body) {
45                                        Ok(v) => {
46                                            // Extract prayer times from JSON, with better error handling
47                                            let fajr = v["data"]["timings"]["Fajr"]
48                                                .as_str()
49                                                .ok_or(ProviderError::InvalidResponse)?;
50                                            let dhuhr = v["data"]["timings"]["Dhuhr"]
51                                                .as_str()
52                                                .ok_or(ProviderError::InvalidResponse)?;
53                                            let asr = v["data"]["timings"]["Asr"]
54                                                .as_str()
55                                                .ok_or(ProviderError::InvalidResponse)?;
56                                            let maghrib = v["data"]["timings"]["Maghrib"]
57                                                .as_str()
58                                                .ok_or(ProviderError::InvalidResponse)?;
59                                            let isha = v["data"]["timings"]["Isha"]
60                                                .as_str()
61                                                .ok_or(ProviderError::InvalidResponse)?;
62
63                                            // Parse time strings into NaiveTime
64                                            let prayers_times = PrayersTimesStuck::new(
65                                                NaiveDate::from_ymd_opt(
66                                                    config.year,
67                                                    config.month,
68                                                    config.day,
69                                                )
70                                                .unwrap(),
71                                                NaiveDate::from_ymd_opt(
72                                                    config.year,
73                                                    config.month,
74                                                    config.day,
75                                                )
76                                                .unwrap(),
77                                                vec![PrayersTimes::new(
78                                                    NaiveTime::from_str(fajr).map_err(|_| {
79                                                        ProviderError::InvalidResponse
80                                                    })?,
81                                                    NaiveTime::from_str(dhuhr).map_err(|_| {
82                                                        ProviderError::InvalidResponse
83                                                    })?,
84                                                    NaiveTime::from_str(asr).map_err(|_| {
85                                                        ProviderError::InvalidResponse
86                                                    })?,
87                                                    NaiveTime::from_str(maghrib).map_err(|_| {
88                                                        ProviderError::InvalidResponse
89                                                    })?,
90                                                    NaiveTime::from_str(isha).map_err(|_| {
91                                                        ProviderError::InvalidResponse
92                                                    })?,
93                                                )],
94                                            );
95                                            Ok(prayers_times)
96                                        }
97                                        Err(_) => Err(ProviderError::InvalidResponse),
98                                    }
99                                }
100                                Err(_) => Err(ProviderError::InvalidResponse),
101                            }
102                        }
103                        _ => Err(ProviderError::InvalidResponse),
104                    },
105                    Err(_) => Err(ProviderError::ConnectionError),
106                }
107            }
108            GetType::Date => Err(ProviderError::UnsupportedOperation),
109            GetType::Month => {
110                let from_date =
111                    chrono::NaiveDate::from_ymd_opt(config.year, config.month, config.day)
112                        .ok_or(ProviderError::InvalidResponse)?;
113
114                let to_date = from_date
115                    .checked_add_days(chrono::Days::new(30))
116                    .ok_or(ProviderError::InvalidResponse)?;
117
118                let mut month_endpoint = aladhan_api_config.endpoint.month;
119
120                // Replace placeholders in the endpoint
121                month_endpoint = month_endpoint.replace(
122                    "{from}",
123                    &format!(
124                        "{}-{}-{}",
125                        from_date.day(),
126                        from_date.month(),
127                        from_date.year()
128                    ),
129                );
130
131                month_endpoint = month_endpoint.replace(
132                    "{to}",
133                    &format!("{}-{}-{}", to_date.day(), to_date.month(), to_date.year()),
134                );
135
136                let url = format!(
137                    "{}/v{}/{}?city={}&country={}",
138                    aladhan_api_config.base_url,
139                    aladhan_api_config.api_version,
140                    month_endpoint,
141                    config.city,
142                    config.country
143                );
144
145                fn parse_day_times(
146                    day_times: &Value,
147                    time: &str,
148                ) -> Result<NaiveTime, ProviderError> {
149                    let salat_time = day_times[time]
150                        .as_str()
151                        .ok_or(ProviderError::InvalidResponse)?;
152
153                    let salat_time = salat_time.split(" ").collect::<Vec<&str>>();
154                    let salat_time = salat_time.first().ok_or(ProviderError::InvalidResponse)?;
155                    let salat_time = NaiveTime::from_str(salat_time)
156                        .map_err(|_| ProviderError::InvalidResponse)?;
157
158                    Ok(salat_time)
159                }
160
161                match reqwest::blocking::get(url) {
162                    Ok(response) => match response.status() {
163                        reqwest::StatusCode::OK => match response.text() {
164                            Ok(body) => match serde_json::from_str::<Value>(&body) {
165                                Ok(v) => {
166                                    // First, collect all the prayer times
167                                    let mut prayer_times_vec = Vec::new();
168
169                                    match v["data"].as_array() {
170                                        Some(times_array) => {
171                                            for day_times in times_array {
172                                                let day_times = &day_times["timings"];
173
174                                                let fajr = parse_day_times(day_times, "Fajr")?;
175                                                let dhuhr = parse_day_times(day_times, "Dhuhr")?;
176                                                let asr = parse_day_times(day_times, "Asr")?;
177                                                let maghrib =
178                                                    parse_day_times(day_times, "Maghrib")?;
179                                                let isha = parse_day_times(day_times, "Isha")?;
180
181                                                let prayer_times = PrayersTimes::new(
182                                                    fajr,
183                                                    dhuhr,
184                                                    asr,
185                                                    maghrib,
186                                                    isha,
187                                                );
188                                                prayer_times_vec.push(prayer_times);
189                                            }
190                                        }
191                                        _ => return Err(ProviderError::InvalidResponse),
192                                    };
193
194                                    // Create a new PrayersTimesStuck with the collected data
195                                    let prayers_times = PrayersTimesStuck::new(
196                                        from_date,
197                                        to_date,
198                                        prayer_times_vec,
199                                    );
200
201                                    Ok(prayers_times)
202                                }
203                                Err(_) => Err(ProviderError::InvalidResponse),
204                            },
205                            Err(_) => Err(ProviderError::InvalidResponse),
206                        },
207                        _ => Err(ProviderError::InvalidResponse),
208                    },
209                    Err(_) => Err(ProviderError::ConnectionError),
210                }
211            }
212            GetType::Year => Err(ProviderError::UnsupportedOperation),
213        }
214    }
215}