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}