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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
//! Several objects used to represent pollution index and alerts
extern crate chrono;

use std::fmt;
use std::str::FromStr;

use chrono::NaiveDate;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

use crate::error::RParifError;

/// This struct represent a pollution index
#[derive(Clone, PartialEq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Index {
    /// Date of mesure
    date: NaiveDate,
    /// An url (if any) to a map show the global pollution
    url: Option<String>,
    /// Pollutants (could be global, o3, no2, pm10, so2)
    pollutants: Vec<String>,
    /// Index
    index: u32,
    /// City INSEE code
    insee: Option<String>,
}

impl Index {
    /// Constructor
    ///
    /// # Arguments
    ///
    /// * `date` - Date of the pollution index
    ///
    /// * `url` - URL to a map (if any)
    ///
    /// * `pollutants` - List of pollutant
    ///
    /// * `index` - Index of pollution
    ///
    /// * `insee` - INSEE code of a city
    // Is this usefull to make a builder ?
    // see https://rust-lang.github.io/api-guidelines/type-safety.html#builders-enable-construction-of-complex-values-c-builder
    pub fn new(
        date: NaiveDate,
        url: Option<String>,
        pollutants: Vec<String>,
        index: u32,
        insee: Option<String>,
    ) -> Index {
        Index {
            date,
            url,
            pollutants,
            index,
            insee,
        }
    }

    /// Return the date of pollution index
    pub fn date(&self) -> NaiveDate {
        self.date
    }

    /// Return a link to a pollution map
    pub fn map_url(&self) -> Option<String> {
        self.url.clone()
    }

    /// List of pollutants that are used to compute index
    pub fn pollutants(&self) -> Vec<String> {
        self.pollutants.to_vec()
    }

    /// Pollution index
    pub fn index(&self) -> u32 {
        self.index
    }

    /// INSEE city code
    pub fn insee(&self) -> Option<String> {
        self.insee.clone()
    }
}

impl fmt::Display for Index {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{} (city : {:?}) : {:?} = {} (map : {:?})",
            self.date, self.insee, self.pollutants, self.index, self.url
        )
    }
}

/// Represent a date as use in the HTTP API
pub enum Day {
    Yesterday,
    Today,
    Tomorrow,
}

/// Represent a pollution alert
#[derive(Clone, PartialEq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Episode {
    /// Alert date
    date: NaiveDate,
    /// Alert details
    detail: Option<String>,
    /// Details for pollutants
    pollutants: Vec<PollutantEpisode>,
}

impl Episode {
    /// Constructor
    ///
    /// # Arguments
    ///
    /// * `date` - Date of the alert
    ///
    /// * `detail` - Description and advice
    pub fn new(date: NaiveDate, detail: Option<String>) -> Episode {
        Episode {
            date,
            detail,
            pollutants: vec![],
        }
    }

    /// Add a new pollutant for the alert
    ///
    /// # Arguments
    ///
    /// * `pollutant` - Name of the pollutant (globale, o2, no2, so3)
    ///
    /// * `kind` - if the alert is forecasted or observed
    ///
    /// * `level` - Pollution level for `pollutant`
    ///
    /// * `criteria` - List of criteria that raised pollution alert
    pub fn add(&mut self, pollutant: String, kind: Type, level: Level, criteria: Vec<Criteria>) {
        self.pollutants.push(PollutantEpisode {
            pollutant,
            kind,
            level,
            criteria,
        })
    }

    /// Return the date of pollution alert
    pub fn date(&self) -> NaiveDate {
        self.date
    }

    /// Return the description of the pollution alert
    pub fn detail(&self) -> Option<String> {
        self.detail.clone()
    }

    /// Return the list of pollutant
    pub fn pollutants(&self) -> Vec<PollutantEpisode> {
        self.pollutants.to_vec()
    }
}

impl fmt::Display for Episode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{} {:?} (detail : {:?})",
            self.date, self.pollutants, self.detail
        )
    }
}

impl IntoIterator for Episode {
    type Item = PollutantEpisode;
    type IntoIter = PollutantEpisodeIter;

    fn into_iter(self) -> Self::IntoIter {
        PollutantEpisodeIter {
            episode: self,
            i: 0,
        }
    }
}

/// Allow to iterate through PollutantEpisode of an Episode
pub struct PollutantEpisodeIter {
    episode: Episode,
    i: usize,
}

impl Iterator for PollutantEpisodeIter {
    type Item = PollutantEpisode;

    fn next(&mut self) -> Option<Self::Item> {
        if self.i < self.episode.pollutants.len() {
            // TODO handle error ?
            let episode: PollutantEpisode = self.episode.pollutants().get(self.i).unwrap().clone();
            self.i += 1;
            Some(episode)
        } else {
            None
        }
    }
}

/// Details of pollution alert
#[derive(Clone, PartialEq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PollutantEpisode {
    /// Name of the pollutant o3, no2, so2, pm10
    pollutant: String,
    /// Type of the alert
    kind: Type,
    /// Alert severity for the pollutant
    level: Level,
    /// List of criteria that raise the alert
    criteria: Vec<Criteria>,
}

impl PollutantEpisode {
    /// Return the pollutant name
    pub fn pollutant_name(&self) -> String {
        self.pollutant.clone()
    }

    /// Return alert type for the pollutant
    pub fn kind(&self) -> Type {
        self.kind.clone()
    }

    /// Return alert level for the pollutant
    pub fn level(&self) -> Level {
        self.level.clone()
    }

    /// Return criteria that raise the alert
    pub fn criteria(&self) -> Vec<Criteria> {
        self.criteria.to_vec()
    }
}

impl fmt::Display for PollutantEpisode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{} : {:?} {:?} {:?}",
            self.pollutant, self.kind, self.level, self.criteria
        )
    }
}

/// Level of pollution alert
#[derive(Clone, PartialEq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Level {
    Info,
    Alert,
    Normal,
}

impl FromStr for Level {
    type Err = RParifError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s == "info" {
            Ok(Level::Info)
        } else if s == "alerte" {
            Ok(Level::Alert)
        } else if s == "normal" {
            Ok(Level::Normal)
        } else {
            Err(RParifError::UnkownEnumValue(s.to_string()))
        }
    }
}

/// Type of alert
#[derive(Clone, PartialEq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Type {
    /// alert was forecast
    Forecast,
    /// alert was observed
    Observed,
}

impl FromStr for Type {
    type Err = RParifError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s == "prevu" {
            Ok(Type::Forecast)
        } else if s == "constate" {
            Ok(Type::Observed)
        } else {
            Err(RParifError::UnkownEnumValue(s.to_string()))
        }
    }
}

/// Criteria that can raise an alert
#[derive(Clone, PartialEq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Criteria {
    /// More than 100km²
    Area,
    /// More than 10% of population
    Population,
}

impl FromStr for Criteria {
    type Err = RParifError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s == "km" {
            Ok(Criteria::Area)
        } else if s == "pop" {
            Ok(Criteria::Population)
        } else {
            Err(RParifError::UnkownEnumValue(s.to_string()))
        }
    }
}

#[cfg(test)]
mod test {
    use crate::objects::{Criteria, Episode, Level, Type};

    use super::chrono::{Datelike, NaiveDate, Utc};

    #[test]
    fn test_episode_iterator() {
        let today = Utc::today();
        let mut episode = Episode::new(
            NaiveDate::from_ymd_opt(today.year(), today.month(), today.day()).unwrap(),
            None,
        );
        episode.add(
            "o3".to_string(),
            Type::Observed,
            Level::Info,
            vec![Criteria::Area, Criteria::Population],
        );
        episode.add(
            "so2".to_string(),
            Type::Observed,
            Level::Alert,
            vec![Criteria::Population],
        );
        episode.add(
            "no2".to_string(),
            Type::Observed,
            Level::Normal,
            vec![Criteria::Area],
        );

        let mut i: usize = 0;
        for pollution_episode in episode.clone() {
            assert_eq!(
                pollution_episode,
                episode.pollutants().get(i).unwrap().clone()
            );
            i += 1;
        }
    }
}