paster/
config.rs

1use std::collections::HashMap;
2
3use crate::paste::{debug, pastebin, Paste};
4use anyhow::{Error, Result};
5use either::Either;
6use serde::{Deserialize, Serialize};
7
8#[derive(Serialize, Deserialize, Debug)]
9pub struct PasterConfig {
10    /// Config format version
11    pub version: String,
12    /// Default destination name
13    pub default: String,
14    /// Different paste destinations
15    pub dest: HashMap<String, DestinationConfig>,
16}
17
18// TODO: Use GetByKey and Into<Box<dyn Paster>> traits without enum somehow
19#[derive(Serialize, Deserialize, Clone, Debug)]
20#[serde(tag = "type")]
21pub enum DestinationConfig {
22    /// pastebin.com destination
23    Pastebin(pastebin::PastebinConfig),
24    /// debug destination (echoes pasted content)
25    Debug(debug::DebugConfig),
26}
27
28pub trait GetByKey {
29    fn get_by_key(&mut self, key: &str) -> Result<Either<&mut String, &mut dyn GetByKey>>;
30}
31
32// TODO: Make derive macro
33impl GetByKey for PasterConfig {
34    fn get_by_key(&mut self, key: &str) -> Result<Either<&mut String, &mut dyn GetByKey>> {
35        if key == "default" {
36            Ok(Either::Left(&mut self.default))
37        } else if key == "dest" {
38            Ok(Either::Right(&mut self.dest))
39        } else {
40            Err(Error::msg("Key not found"))
41        }
42    }
43}
44
45impl<T> GetByKey for HashMap<String, T>
46where
47    T: GetByKey,
48{
49    fn get_by_key(&mut self, key: &str) -> Result<Either<&mut String, &mut dyn GetByKey>> {
50        self.get_mut(key)
51            .map(|x| Either::<&mut String, &mut dyn GetByKey>::Right(x))
52            .ok_or(Error::msg("Key not found"))
53    }
54}
55
56impl GetByKey for DestinationConfig {
57    fn get_by_key(&mut self, key: &str) -> Result<Either<&mut String, &mut dyn GetByKey>> {
58        match self {
59            // TODO: There must be some clever way to do it
60            DestinationConfig::Pastebin(x) => x.get_by_key(key),
61            DestinationConfig::Debug(x) => x.get_by_key(key),
62        }
63    }
64}
65
66impl From<DestinationConfig> for Box<dyn Paste> {
67    fn from(val: DestinationConfig) -> Self {
68        match val {
69            // TODO: There must be some clever way to do it
70            DestinationConfig::Pastebin(x) => x.into(),
71            DestinationConfig::Debug(x) => x.into(),
72        }
73    }
74}
75
76impl Default for PasterConfig {
77    fn default() -> Self {
78        let default = String::from("pastebin");
79        let mut destinations = HashMap::new();
80        destinations.insert(
81            default.clone(),
82            DestinationConfig::Pastebin(pastebin::PastebinConfig {
83                dev_key: String::from("<your dev API key>"),
84                user_key: None,
85            }),
86        );
87
88        PasterConfig {
89            version: String::from("1"),
90            default,
91            dest: destinations,
92        }
93    }
94}
95
96pub fn update_config_value(config: &mut PasterConfig, key: &str, value: String) -> Result<()> {
97    update_value_by_key(config, key, value)
98}
99
100fn update_value_by_key(config: &mut dyn GetByKey, key: &str, value: String) -> Result<()> {
101    match key.split_once('.') {
102        Some((left, right)) => match config.get_by_key(left)? {
103            Either::Left(_) => Err(Error::msg("Expected nested structure but got plain")),
104            Either::Right(subconfig) => update_value_by_key(subconfig, right, value),
105        },
106        None => match config.get_by_key(key)? {
107            Either::Left(val) => {
108                *val = value;
109                Ok(())
110            }
111            Either::Right(_) => Err(Error::msg("Expected plain value but got nested")),
112        },
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn update_plain() -> Result<()> {
122        let mut config = PasterConfig::default();
123        update_value_by_key(&mut config, "default", String::from("test"))?;
124        assert_eq!(config.default, "test");
125        Ok(())
126    }
127
128    #[test]
129    fn update_nested() -> Result<()> {
130        let mut config = PasterConfig::default();
131        update_value_by_key(&mut config, "dest.pastebin.dev_key", String::from("test"))?;
132        match config.dest.get("pastebin").unwrap() {
133            DestinationConfig::Pastebin(pastebin::PastebinConfig {
134                dev_key,
135                user_key: _,
136            }) => {
137                assert_eq!(dev_key, "test");
138            }
139            DestinationConfig::Debug(_) => panic!(),
140        }
141        Ok(())
142    }
143
144    #[test]
145    fn update_unknown_key() {
146        let mut config = PasterConfig::default();
147        assert!(update_value_by_key(&mut config, "unknown", String::from("test")).is_err());
148    }
149
150    #[test]
151    fn update_plain_as_nested() {
152        let mut config = PasterConfig::default();
153        assert!(
154            update_value_by_key(&mut config, "default.something", String::from("test")).is_err()
155        );
156    }
157
158    #[test]
159    fn update_nested_as_plain() {
160        let mut config = PasterConfig::default();
161        assert!(update_value_by_key(&mut config, "dest", String::from("test")).is_err());
162    }
163}