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}