clean_insights_sdk/
visit.rs

1use chrono::{DateTime, Timelike, Utc};
2use chrono::serde::ts_seconds;
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4
5#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
6pub struct Visit {
7
8    /// A hierarchical path to the scene visited.
9    #[serde(rename = "action_name", serialize_with = "serialize_scene_path", deserialize_with = "deserialize_action_name")]
10    pub scene_path: Vec<String>,
11
12    /// The campaign ID this data point is for.
13    pub campaign_id: String,
14
15    /// Number of times this data point has arisen between `first` and `last`. OPTIONAL, defaults to 1.
16    pub times: u32,
17
18    /// The first time this data point has arisen. OPTIONAL, defaults to now.
19    #[serde(rename = "period_start", with = "ts_seconds")]
20    pub first: DateTime<Utc>,
21
22    /// The last time this data point has arisen. OPTIONAL, defaults to now.
23    #[serde(rename = "period_end", with = "ts_seconds")]
24    pub last: DateTime<Utc>,
25}
26
27fn serialize_scene_path<S>(scene_path: &Vec<String>, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
28    serializer.serialize_str(scene_path.join("/").as_str())
29}
30
31fn deserialize_action_name<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error> where D: Deserializer<'de> {
32    let action_name = String::deserialize(deserializer)?;
33
34    Ok(action_name.split('/').map(|item| item.to_owned()).collect())
35}
36
37impl Visit {
38
39    /// Returns a visit with the given scene_path and campaign_id and sane defaults for the other
40    /// properties.
41    ///
42    /// # Arguments
43    /// * scene_path: A hierarchical path to the scene visited.
44    /// * campaign_id: The campaign ID this data point is for.
45    ///
46    /// # Defaults:
47    /// * times: 1.
48    /// * first: now.
49    /// * last: now.
50    pub fn new(scene_path: Vec<String>, campaign_id: String) -> Visit {
51        let now = Utc::now().with_nanosecond(0).unwrap();
52
53        Visit { scene_path, campaign_id, times: 1, first: now.clone(), last: now }
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60    use chrono::TimeZone;
61
62    #[test]
63    fn new_visit() {
64        let visit = Visit {
65            scene_path: vec!["foo".to_string(), "bar".to_string()],
66            campaign_id: "test".to_string(),
67            times: 100,
68            first: Utc.timestamp_opt(0, 0).unwrap(),
69            last: Utc.timestamp_opt(0, 0).unwrap() };
70
71        let json = serde_json::to_string(&visit).unwrap();
72
73        assert_eq!(json,
74                   "{\"action_name\":\"foo/bar\",\"campaign_id\":\"test\",\"times\":100,\"period_start\":0,\"period_end\":0}");
75
76        let visit2: Visit = serde_json::from_str(json.as_str()).unwrap();
77
78        assert_eq!(visit2, visit);
79    }
80
81    #[test]
82    fn new_visit_with_defaults() {
83        let visit = Visit::new(
84            vec!["foo".to_string(), "bar".to_string()],
85            "test".to_string());
86
87        let json = serde_json::to_string(&visit).unwrap();
88        let now = Utc::now().timestamp();
89
90        assert_eq!(json,
91                   format!("{{\"action_name\":\"foo/bar\",\"campaign_id\":\"test\",\"times\":1,\"period_start\":{now},\"period_end\":{now}}}", now=now));
92
93        let visit2: Visit = serde_json::from_str(json.as_str()).unwrap();
94
95        assert_eq!(visit2, visit);
96    }
97}