1use std::{
2 env,
3 fs::File,
4 io::{Read, Write},
5};
6
7use anyhow::{anyhow, Context, Result};
8
9pub mod api_usage;
10pub mod cli;
11pub mod user_setup;
12
13#[cfg(test)]
14mod testing;
15
16mod program_info;
17mod types;
18
19pub mod constants {
20 pub const API_JSON_NAME: &str = "api";
22
23 pub const USER_SETTING_JSON_NAME: &str = "setting";
25
26 pub const WEATHER_API_URL: &str = "https://api.openweathermap.org/data/2.5/weather?lat={LAT_VALUE}&lon={LON_VALUE}&appid={API_KEY}&units={UNIT}";
53
54 pub const GEOLOCATION_API_URL: &str =
73 "http://api.openweathermap.org/geo/1.0/direct?q={QUERY}&limit=10&appid={API_KEY}";
74}
75
76pub fn get_executable_directory() -> Result<String> {
78 let executable_path =
79 env::current_exe().context("Failed to get the executable file directory!")?;
80 let executable_directory = executable_path
81 .parent()
82 .context("Failed to get the executable directory!")?;
83
84 if let Some(dir_str) = executable_directory.to_str() {
85 return Ok(dir_str.to_string());
86 }
87
88 Err(anyhow!("Unable to get the executable directory."))
89}
90
91pub fn get_json_file(json_suffix: &str) -> Result<File> {
93 let executable_dir = get_executable_directory()?;
94
95 let file = match File::open(format!(
96 "{}/{}",
97 executable_dir,
98 make_json_file_name(json_suffix)
99 )) {
100 Ok(f) => f,
101 Err(_) => {
102 let mut new_file = File::create(format!(
103 "{}/{}",
104 executable_dir,
105 make_json_file_name(json_suffix)
106 ))
107 .context("Failed to create a json file.")?;
108 new_file
109 .write_all("{}".as_bytes())
110 .context("Failed to create a json file.")?;
111
112 File::open(format!(
113 "{}/{}",
114 executable_dir,
115 make_json_file_name(json_suffix)
116 ))
117 .context("Failed to get the json file.")?
118 }
119 };
120
121 Ok(file)
122}
123
124pub fn make_json_file_name(suffix: &str) -> String {
131 format!("weather-cli-{}.json", suffix)
132}
133
134pub enum ErrorMessageType {
135 SettingRead,
136 ApiResponseRead,
137 InvalidApiKey,
138}
139
140fn get_file_read_error_message(error_type: ErrorMessageType, context: Option<&str>) -> String {
141 match (error_type, context) {
142 (ErrorMessageType::SettingRead, Some(context)) => {
143 if context == "api" {
144 format!(
145 "Failed to read {}. Please make sure to setup your API key.",
146 make_json_file_name(context)
147 )
148 } else {
149 format!("Failed to read the following file: {}", context)
150 }
151 }
152 (ErrorMessageType::ApiResponseRead, Some(context)) => {
153 format!("The given '{}' JSON input may be invalid.", context)
154 }
155 (ErrorMessageType::InvalidApiKey, None) => {
156 "API Key is invalid. Please try again.".to_string()
157 }
158 _ => unreachable!(),
159 }
160}
161
162pub fn read_json_file<T: serde::de::DeserializeOwned>(json_name: &str) -> Result<T> {
164 let mut file = get_json_file(json_name)?;
165 let mut json_string = String::new();
166 file.read_to_string(&mut json_string)?;
167
168 let api_key_data: T = serde_json::from_str(&json_string).context(
169 get_file_read_error_message(ErrorMessageType::SettingRead, Some(json_name)),
170 )?; Ok(api_key_data)
173}
174
175pub fn read_json_response<T: serde::de::DeserializeOwned>(
177 response: &str,
178 error_message_type: ErrorMessageType,
179 error_context: &str,
180) -> Result<T> {
181 use serde_json::Value;
182
183 let api_response: Value = serde_json::from_str(response).context(
184 get_file_read_error_message(ErrorMessageType::ApiResponseRead, Some(error_context)),
185 )?;
186
187 if let Some(401) = api_response["cod"].as_i64() {
189 return Err(anyhow!(get_file_read_error_message(
190 ErrorMessageType::InvalidApiKey,
191 None
192 )));
193 }
194
195 let response_data: T = serde_json::from_str(response).context(get_file_read_error_message(
196 error_message_type,
197 Some(error_context),
198 ))?;
199
200 Ok(response_data)
201}
202
203pub struct URLPlaceholder {
214 pub placeholder: String,
215 pub value: String,
216}
217
218pub fn replace_url_placeholders(url: &str, url_placeholders: &[URLPlaceholder]) -> String {
220 let mut replaced_url = String::from(url);
221 for url_placeholder in url_placeholders {
222 replaced_url = replaced_url.replace(&url_placeholder.placeholder, &url_placeholder.value);
223 }
224
225 replaced_url
226}