jirust_cli/config/config_file.rs
1use base64::prelude::*;
2use serde::{Deserialize, Serialize};
3use std::{fs, io::Write};
4use toml::{self, Table, Value};
5
6/// This struct holds the username and api_key for the Jira API.
7#[derive(Debug)]
8pub struct AuthData {
9 username: String,
10 api_key: String,
11}
12
13/// This struct holds the configuration data to use the Jira API (authentication info and Jira base_url).
14#[derive(Debug, Serialize, Deserialize, Clone)]
15pub struct ConfigFile {
16 auth: AuthSection,
17 jira: JiraSection,
18}
19
20/// This struct holds the authentication token to be used with the Jira API.
21#[derive(Debug, Serialize, Deserialize, Clone)]
22pub struct AuthSection {
23 auth_token: String,
24}
25
26/// This struct holds the Jira base_url.
27#[derive(Debug, Serialize, Deserialize, Clone)]
28pub struct JiraSection {
29 jira_url: String,
30 standard_resolution: String,
31 standard_resolution_comment: String,
32 transitions_names: Table,
33}
34
35/// Implementation of AuthData
36///
37/// # Methods
38/// * `new(username: String, api_key: String) -> AuthData` - creates a new instance of AuthData
39/// * `set_username(username: String)` - sets the username
40/// * `set_api_key(api_key: String)` - sets the api_key
41/// * `get_username() -> String` - gets the username
42/// * `get_api_key() -> String` - gets the api_key
43/// * `to_base64() -> String` - converts the AuthData to a base64 string
44/// * `from_base64(base64_str: String) -> AuthData` - converts a base64 string to an AuthData
45/// * `write_to_file(file: &str) -> Result<(), std::io::Error>` - writes the AuthData to a file
46/// * `read_from_file(file: &str) -> Result<AuthData, std::io::Error>` - reads the AuthData from a file
47impl AuthData {
48 /// Create a new AuthData struct.
49 ///
50 /// # Arguments
51 /// * username - The username to be used for authentication.
52 /// * api_key - The api_key to be used for authentication.
53 ///
54 /// # Returns
55 /// * A new AuthData struct.
56 ///
57 /// # Examples
58 ///
59 /// ```
60 /// use jirust_cli::config::config_file::AuthData;
61 ///
62 /// let auth_data = AuthData::new("username".to_string(), "api_key".to_string());
63 /// ```
64 pub fn new(username: String, api_key: String) -> AuthData {
65 AuthData { username, api_key }
66 }
67
68 /// Set the username for the AuthData struct.
69 ///
70 /// # Arguments
71 /// * username - The username to be used for authentication.
72 ///
73 /// # Examples
74 ///
75 /// ```
76 /// use jirust_cli::config::config_file::AuthData;
77 ///
78 /// let mut auth_data = AuthData::new("username".to_string(), "api_key".to_string());
79 /// auth_data.set_username("new_username".to_string());
80 /// ```
81 pub fn set_username(&mut self, username: String) {
82 self.username = username.replace("\n", "");
83 }
84
85 /// Set the api_key for the AuthData struct.
86 ///
87 /// # Arguments
88 /// * api_key - The api_key to be used for authentication.
89 ///
90 /// # Examples
91 ///
92 /// ```
93 /// use jirust_cli::config::config_file::AuthData;
94 ///
95 /// let mut auth_data = AuthData::new("username".to_string(), "api_key".to_string());
96 /// auth_data.set_api_key("new_api_key".to_string());
97 /// ```
98 pub fn set_api_key(&mut self, api_key: String) {
99 self.api_key = api_key.replace("\n", "");
100 }
101
102 /// Encode the username and api_key to base64 to be used in the Authorization header of the request.
103 ///
104 /// # Returns
105 /// * A base64 encoded string of the username and api_key.
106 ///
107 /// # Examples
108 ///
109 /// ```
110 /// use jirust_cli::config::config_file::AuthData;
111 ///
112 /// let auth_data = AuthData::new("username".to_string(), "api_key".to_string());
113 /// let base64_encoded = auth_data.to_base64();
114 ///
115 /// assert_eq!(base64_encoded, "dXNlcm5hbWU6YXBpX2tleQ==");
116 /// ```
117 pub fn to_base64(&self) -> String {
118 BASE64_STANDARD.encode(format!("{}:{}", self.username, self.api_key).replace("\n", ""))
119 }
120
121 /// Decode a base64 encoded string to get the username and api_key.
122 ///
123 /// # Arguments
124 /// * encoded - The base64 encoded string to be decoded.
125 ///
126 /// # Returns
127 /// * A tuple containing the username and api_key.
128 ///
129 /// # Examples
130 ///
131 /// ```
132 /// use jirust_cli::config::config_file::AuthData;
133 ///
134 /// let (username, api_key) = AuthData::from_base64("dXNlcm5hbWU6YXBpX2tleQ==");
135 ///
136 /// assert_eq!(username, "username");
137 /// assert_eq!(api_key, "api_key");
138 /// ```
139 pub fn from_base64(encoded: &str) -> (String, String) {
140 let decoded = BASE64_STANDARD
141 .decode(encoded)
142 .expect("Failed to decode base64");
143 let decoded_str = String::from_utf8(decoded).expect("Failed to convert to string");
144 let parts: Vec<&str> = decoded_str.split(':').collect();
145 (parts[0].to_string(), parts[1].to_string())
146 }
147}
148
149/// Implementation of ConfigFile
150///
151/// # Methods
152/// * `new(auth_token: String, jira_url: String) -> ConfigFile` - creates a new instance of ConfigFile
153/// * default() -> ConfigFile - creates a new instance of ConfigFile with default values
154/// * `write_to_file(file: &str) -> Result<(), std::io::Error>` - writes the ConfigFile to a file
155/// * `read_from_file(file: &str) -> Result<ConfigFile, std::io::Error>` - reads the ConfigFile from a file
156/// * `get_auth() -> AuthSection` - gets the AuthSection from the ConfigFile
157/// * `get_jira() -> JiraSection` - gets the JiraSection from the ConfigFile
158/// * `set_auth(auth: AuthSection)` - sets the AuthSection in the ConfigFile
159/// * `set_jira(jira: JiraSection)` - sets the JiraSection in the ConfigFile
160/// * `set_standard_resolution(standard_resolution: String)` - sets the standard_resolution in the ConfigFile
161/// * `get_standard_resolution() -> String` - gets the standard_resolution from the ConfigFile
162/// * `set_standard_resolution_comment(standard_resolution_comment: String)` - sets the standard_resolution_comment in the ConfigFile
163/// * `get_standard_resolution_comment() -> String` - gets the standard_resolution_comment from the Config
164/// * `add_transition_name(key: String, value: String)` - adds a transition_name to the ConfigFile
165/// * `get_transition_name(key: &str) -> Option<String>` - gets a transition_name from the ConfigFile
166impl ConfigFile {
167 /// Create a new ConfigFile struct.
168 ///
169 /// # Arguments
170 /// * auth_token - The authentication token to be used with the Jira API.
171 /// * jira_url - The base_url for the Jira API.
172 /// * standard_resolution - The standard resolution to be used when resolving an issue.
173 /// * standard_resolution_comment - The standard comment to be used when resolving an issue.
174 /// * transitions_names - The transitions names to be used when transitioning an issue.
175 ///
176 /// # Returns
177 /// * A new ConfigFile struct.
178 ///
179 /// # Examples
180 ///
181 /// ```
182 /// use jirust_cli::config::config_file::ConfigFile;
183 /// use toml::Table;
184 ///
185 /// let config = ConfigFile::new("auth_token".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
186 ///
187 /// assert_eq!(config.get_auth_key(), "auth_token");
188 /// assert_eq!(config.get_jira_url(), "jira_url");
189 /// assert_eq!(config.get_standard_resolution(), "standard_resolution");
190 /// assert_eq!(config.get_standard_resolution_comment(), "standard_resolution_comment");
191 /// ```
192 pub fn new(
193 auth_token: String,
194 jira_url: String,
195 standard_resolution: String,
196 standard_resolution_comment: String,
197 transitions_names: Table,
198 ) -> ConfigFile {
199 ConfigFile {
200 auth: AuthSection { auth_token },
201 jira: JiraSection {
202 jira_url,
203 standard_resolution,
204 standard_resolution_comment,
205 transitions_names,
206 },
207 }
208 }
209
210 /// Set the authentication token for the ConfigFile struct.
211 /// This is the token that will be used to authenticate with the Jira API.
212 ///
213 /// # Arguments
214 /// * auth_token - The authentication token to be used with the Jira API.
215 ///
216 /// # Examples
217 ///
218 /// ```
219 /// use jirust_cli::config::config_file::ConfigFile;
220 ///
221 /// let mut config = ConfigFile::default();
222 /// config.set_auth_key("auth_key".to_string());
223 ///
224 /// assert_eq!(config.get_auth_key(), "auth_key");
225 /// ```
226 pub fn set_auth_key(&mut self, auth_token: String) {
227 self.auth.auth_token = auth_token.replace("\n", "");
228 }
229
230 /// Get the authentication token for the ConfigFile struct.
231 /// This is the token that will be used to authenticate with the Jira API.
232 /// This is useful for getting the current value of the authentication token.
233 ///
234 /// # Returns
235 /// * The authentication token to be used with the Jira API.
236 ///
237 /// # Examples
238 ///
239 /// ```
240 /// use jirust_cli::config::config_file::ConfigFile;
241 /// use toml::Table;
242 ///
243 /// let config = ConfigFile::new("auth_token".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
244 /// let auth_key = config.get_auth_key();
245 ///
246 /// assert_eq!(auth_key, "auth_token");
247 /// ```
248 pub fn get_auth_key(&self) -> &str {
249 &self.auth.auth_token
250 }
251
252 /// Set the Jira URL for the ConfigFile struct.
253 /// This is the base URL for the Jira API.
254 ///
255 /// # Arguments
256 /// * jira_url - The base URL for the Jira API.
257 ///
258 /// # Examples
259 ///
260 /// ```
261 /// use jirust_cli::config::config_file::ConfigFile;
262 ///
263 /// let mut config = ConfigFile::default();
264 /// config.set_jira_url("jira_url".to_string());
265 ///
266 /// assert_eq!(config.get_jira_url(), "jira_url");
267 /// ```
268 pub fn set_jira_url(&mut self, jira_url: String) {
269 self.jira.jira_url = jira_url.replace("\n", "");
270 }
271
272 /// Get the Jira URL for the ConfigFile struct.
273 /// This is the base URL for the Jira API.
274 ///
275 /// # Returns
276 /// * The base URL for the Jira API.
277 ///
278 /// # Examples
279 ///
280 /// ```
281 /// use jirust_cli::config::config_file::ConfigFile;
282 /// use toml::Table;
283 ///
284 /// let config = ConfigFile::new("auth_token".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
285 /// let jira_url = config.get_jira_url();
286 ///
287 /// assert_eq!(jira_url, "jira_url");
288 /// ```
289 pub fn get_jira_url(&self) -> &str {
290 &self.jira.jira_url
291 }
292
293 /// Set the standard resolution for the ConfigFile struct.
294 /// This is the standard resolution that will be used when resolving an issue.
295 ///
296 /// # Arguments
297 /// * standard_resolution - The standard resolution to be used when resolving an issue.
298 ///
299 /// # Examples
300 ///
301 /// ```
302 /// use jirust_cli::config::config_file::ConfigFile;
303 ///
304 /// let mut config = ConfigFile::default();
305 /// config.set_standard_resolution("standard_resolution".to_string());
306 ///
307 /// assert_eq!(config.get_standard_resolution(), "standard_resolution");
308 /// ```
309 pub fn set_standard_resolution(&mut self, standard_resolution: String) {
310 self.jira.standard_resolution = standard_resolution;
311 }
312
313 /// Get the standard resolution for the ConfigFile struct.
314 /// This is the standard resolution that will be used when resolving an issue.
315 ///
316 /// # Returns
317 /// * The standard resolution to be used when resolving an issue.
318 ///
319 /// # Examples
320 ///
321 /// ```
322 /// use jirust_cli::config::config_file::ConfigFile;
323 /// use toml::Table;
324 ///
325 /// let config = ConfigFile::new("auth_token".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
326 /// let standard_resolution = config.get_standard_resolution();
327 ///
328 /// assert_eq!(config.get_standard_resolution(), "standard_resolution");
329 /// ```
330 pub fn get_standard_resolution(&self) -> &String {
331 &self.jira.standard_resolution
332 }
333
334 /// Set the standard resolution comment for the ConfigFile struct.
335 /// This is the standard resolution comment that will be used when resolving an issue.
336 ///
337 /// # Arguments
338 /// * standard_resolution_comment - The standard resolution comment to be used when resolving an issue.
339 ///
340 /// # Examples
341 ///
342 /// ```
343 /// use jirust_cli::config::config_file::ConfigFile;
344 ///
345 /// let mut config = ConfigFile::default();
346 /// config.set_standard_resolution_comment("standard_resolution_comment".to_string());
347 ///
348 /// assert_eq!(config.get_standard_resolution_comment(), "standard_resolution_comment");
349 /// ```
350 pub fn set_standard_resolution_comment(&mut self, standard_resolution_comment: String) {
351 self.jira.standard_resolution_comment = standard_resolution_comment;
352 }
353
354 /// Get the standard resolution comment for the ConfigFile struct.
355 /// This is the standard resolution comment that will be used when resolving an issue.
356 ///
357 /// # Returns
358 /// * The standard resolution comment to be used when resolving an issue.
359 ///
360 /// # Examples
361 ///
362 /// ```
363 /// use jirust_cli::config::config_file::ConfigFile;
364 /// use toml::Table;
365 ///
366 /// let config = ConfigFile::new("auth_token".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
367 /// let standard_resolution_comment = config.get_standard_resolution_comment();
368 ///
369 /// assert_eq!(standard_resolution_comment, "standard_resolution_comment");
370 /// ```
371 pub fn get_standard_resolution_comment(&self) -> &String {
372 &self.jira.standard_resolution_comment
373 }
374
375 /// Add a transition name to the ConfigFile struct.
376 /// This is used to store the transition name for a specific transition.
377 /// This is used to transition an issue to a specific state.
378 /// The key is the transition internal identifier and the value is the transition name.
379 ///
380 /// # Arguments
381 /// * key - The transition internal identifier.
382 /// * value - The transition name.
383 ///
384 /// # Examples
385 ///
386 /// ```
387 /// use jirust_cli::config::config_file::ConfigFile;
388 ///
389 /// let mut config = ConfigFile::default();
390 /// config.add_transition_name("transition_key".to_string(), "Transition name".to_string());
391 ///
392 /// assert_eq!(config.get_transition_name("transition_key"), Some(vec!["Transition name".to_string()]));
393 /// ```
394 pub fn add_transition_name(&mut self, key: String, value: String) {
395 let mut existing_value: Vec<Value> = self
396 .jira
397 .transitions_names
398 .get(&key)
399 .and_then(|v| v.as_array())
400 .unwrap_or(&vec![])
401 .iter()
402 .map(|v| Value::String(v.as_str().unwrap().to_string()))
403 .collect();
404 existing_value.push(Value::String(value));
405 self.jira
406 .transitions_names
407 .insert(key, Value::Array(existing_value));
408 }
409
410 /// Get the transition name for a specific transition internal identifier.
411 /// This is used to transition an issue to a specific state.
412 /// The key is the transition internal identifier and the value is the transition name.
413 /// If the transition internal identifier does not exist, None is returned.
414 ///
415 /// # Arguments
416 /// * key - The transition internal identifier.
417 ///
418 /// # Returns
419 /// * The transition name for the specific transition internal identifier.
420 ///
421 /// # Examples
422 ///
423 /// ```
424 /// use jirust_cli::config::config_file::ConfigFile;
425 ///
426 /// let mut config = ConfigFile::default();
427 /// config.add_transition_name("transition_key".to_string(), "Transition name".to_string());
428 ///
429 /// assert_eq!(config.get_transition_name("transition_key"), Some(vec!["Transition name".to_string()]));
430 /// ```
431 pub fn get_transition_name(&self, key: &str) -> Option<Vec<String>> {
432 let tranisitons_names = self
433 .jira
434 .transitions_names
435 .get(key)
436 .and_then(|v| v.as_array());
437 Some(
438 tranisitons_names
439 .unwrap_or(&vec![])
440 .iter()
441 .map(|v| v.as_str().unwrap().to_string())
442 .collect(),
443 )
444 }
445
446 /// Stores the configuration to a file.
447 /// This will overwrite the file if it already exists.
448 ///
449 /// # Arguments
450 /// * file_path - The path to the file to write the configuration to.
451 ///
452 /// # Returns
453 /// * A Result containing either an empty Ok or an error.
454 ///
455 /// # Examples
456 ///
457 /// ```
458 /// use jirust_cli::config::config_file::ConfigFile;
459 /// use toml::Table;
460 ///
461 /// let config = ConfigFile::new("auth_token".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
462 /// let result = config.write_to_file("config.toml");
463 ///
464 /// assert!(result.is_ok());
465 /// ```
466 pub fn write_to_file(&self, file_path: &str) -> Result<(), std::io::Error> {
467 let mut file = std::fs::OpenOptions::new()
468 .write(true)
469 .create(true)
470 .truncate(true)
471 .open(file_path)
472 .expect("Failed to open file");
473 let toml_str = toml::to_string(self).expect("Failed to serialize toml");
474 file.write_all(toml_str.as_bytes())
475 }
476
477 /// Loads the configuration from a file.
478 /// If the file does not exist, it will return a ConfigFile with default values.
479 /// If the file is not valid toml, it will return an error.
480 /// If the file is valid toml, it will return the ConfigFile.
481 ///
482 /// # Arguments
483 /// * file_path - The path to the file to read the configuration from.
484 ///
485 /// # Returns
486 /// * A Result containing either the ConfigFile or an error.
487 ///
488 /// # Examples
489 ///
490 /// ```
491 /// use jirust_cli::config::config_file::ConfigFile;
492 /// use std::path::Path;
493 ///
494 /// // Try both possible paths to handle different working directories
495 /// let config_path = if Path::new("config_example.toml").exists() {
496 /// "config_example.toml"
497 /// } else {
498 /// "jirust-cli/config_example.toml"
499 /// };
500 ///
501 /// let config = ConfigFile::read_from_file(config_path);
502 ///
503 /// assert!(config.clone().is_ok());
504 /// assert_eq!(config.clone().unwrap().get_auth_key(), "auth_key");
505 /// assert_eq!(config.clone().unwrap().get_jira_url(), "jira_url");
506 /// ```
507 pub fn read_from_file(file_path: &str) -> Result<ConfigFile, toml::de::Error> {
508 let config_file_str = fs::read_to_string(file_path)
509 .unwrap_or(toml::to_string(&ConfigFile::default()).unwrap_or("".to_string()));
510 toml::from_str(&config_file_str)
511 }
512}
513
514impl Default for ConfigFile {
515 /// Create a new ConfigFile struct with default values.
516 /// This is useful for creating a new configuration file.
517 /// The default values can be set using the set methods.
518 /// The default values are:
519 /// - auth_token: ""
520 /// - jira_url: ""
521 /// - standard_resolution: ""
522 /// - standard_resolution_comment: ""
523 /// - transitions_names: Table::new()
524 ///
525 /// # Returns
526 /// * A new ConfigFile struct with default values.
527 ///
528 /// # Examples
529 ///
530 /// ```
531 /// use jirust_cli::config::config_file::ConfigFile;
532 /// use toml::Table;
533 ///
534 /// let config = ConfigFile::default();
535 ///
536 /// assert_eq!(config.get_auth_key(), "");
537 /// assert_eq!(config.get_jira_url(), "");
538 /// assert_eq!(config.get_standard_resolution(), "");
539 /// assert_eq!(config.get_standard_resolution_comment(), "");
540 /// ```
541 fn default() -> ConfigFile {
542 ConfigFile {
543 auth: AuthSection {
544 auth_token: String::from(""),
545 },
546 jira: JiraSection {
547 jira_url: String::from(""),
548 standard_resolution: String::from(""),
549 standard_resolution_comment: String::from(""),
550 transitions_names: Table::new(),
551 },
552 }
553 }
554}