1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
use serde_yaml;

use errors::*;
use models::comments::gen_hash;
use std::fs::File;

/// The main struct which all input data from `oration.yaml` is pushed into.
#[derive(Serialize, Deserialize, Debug)]
pub struct Config {
    /// Top level location of the blog we are serving.
    pub host: String,
    /// Name of the blog we are serving.
    pub blog_name: String,
    /// A salt for slightly more anonymous `anonymous` user identification.
    pub salt: String,
    /// Blog Author to highlight as an authority in comments.
    pub author: Author,
    /// Limit of thread nesting in comments.
    pub nesting_limit: u32,
    /// Time limit that restricts user editing of their own comments.
    pub edit_timeout: f32,
    /// Email notification system and connection details.
    pub notifications: Notifications,
    /// Telegram notification endpoint details.
    pub telegram: Telegram,
}

impl Config {
    /// Reads and parses data from the `oration.yaml` file and command line arguments.
    pub fn load() -> Result<Config> {
        let reader = File::open("oration.yaml").chain_err(|| ErrorKind::ConfigLoad)?;
        // Decode configuration file.
        let mut decoded_config: Config =
            serde_yaml::from_reader(reader).chain_err(|| ErrorKind::Deserialize)?;
        Config::parse(&decoded_config).chain_err(|| ErrorKind::ConfigParse)?;

        decoded_config.author.gen_hash();

        Ok(decoded_config)
    }

    /// Additional checks to the configuration file that cannot be done implicitly
    /// by the type checker.
    fn parse(&self) -> Result<()> {
        let handle = self.host.get(0..4);
        if handle != Some("http") {
            return Err(ErrorKind::NoHTTPHandle.into());
        }

        if self.notifications.new_comment {
            // Empty values are parsed as ~, so we want to check for those
            if self
                .notifications
                .smtp_server
                .into_iter()
                .any(|x| x.is_empty() || x == "~")
            {
                return Err(ErrorKind::EmptySMTP.into());
            }
            if self.notifications.recipient.email.is_empty()
                || self.notifications.recipient.email == "~"
            {
                return Err(ErrorKind::EmptyRecipientEmail.into());
            }
        }
        Ok(())
    }
}

/// Details of the blog author.
#[derive(Serialize, Deserialize, Debug)]
pub struct Author {
    /// Blog author's name.
    name: Option<String>,
    /// Blog author's email address.
    email: Option<String>,
    /// Blog author's website.
    url: Option<String>,
    #[serde(skip)]
    /// A Sha224 hash of the blog author's details (automitically generated).
    pub hash: String,
}

impl Author {
    /// Generates a Sha224 hash for the blog author if details are set.
    fn gen_hash(&mut self) {
        self.hash = gen_hash(&self.name, &self.email, &self.url, None);
    }
}

/// Details of the email notification system.
#[derive(Serialize, Deserialize, Debug)]
pub struct Notifications {
    /// Toggle if an email is to be sent when a new comment is posted.
    pub new_comment: bool,
    /// SMTP connection details.
    pub smtp_server: SMTPServer,
    /// Who to send the notification to.
    pub recipient: Recipient,
}

/// Details of the SMTP server which the notification system should connect to.
#[derive(Serialize, Deserialize, Debug)]
pub struct SMTPServer {
    /// SMTP host url. (No need for a protocol header).
    pub host: String,
    /// Username for authentication.
    pub user_name: String,
    /// Password for authentication.
    pub password: String,
}

impl<'a> IntoIterator for &'a SMTPServer {
    type Item = &'a str;
    type IntoIter = SMTPServerIterator<'a>;

    fn into_iter(self) -> Self::IntoIter {
        SMTPServerIterator {
            server: self,
            index: 0,
        }
    }
}

/// Iterator helper for `SMTPServer`
pub struct SMTPServerIterator<'a> {
    /// The SMTPServer struct.
    server: &'a SMTPServer,
    /// A helper index.
    index: usize,
}

impl<'a> Iterator for SMTPServerIterator<'a> {
    type Item = &'a str;
    fn next(&mut self) -> Option<&'a str> {
        let result = match self.index {
            0 => &self.server.host,
            1 => &self.server.user_name,
            2 => &self.server.password,
            _ => return None,
        };
        self.index += 1;
        Some(result)
    }
}

/// Details of a person to email the notifications to.
#[derive(Serialize, Deserialize, Debug)]
pub struct Recipient {
    /// Recipient's email address.
    pub email: String,
    /// Recipient's name.
    pub name: String,
}

/// Details of the telegram notification system.
#[derive(Serialize, Deserialize, Debug)]
pub struct Telegram {
    /// If true, the notification system will be active.
    pub push_notifications: bool,
    /// API token for your telegram bot.
    pub bot_id: String,
    /// The ID of your personal chat with the bot.
    pub chat_id: String,
}