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
use serde::de::{Deserialize, Deserializer, Error, MapVisitor, Visitor};
use serde::de::impls::IgnoredAny;
use std::fmt;
use super::super::DateTime;
use json::Value;
use super::{List, Tweet, User};

/// Represents notifications about non-Tweet events are also sent over a stream.
///
/// # Reference
///
/// 1. [Streaming message types — Twitter Developers]
///    (https://dev.twitter.com/streaming/overview/messages-types#Events_event)
#[derive(Clone, Debug, PartialEq)]
pub struct Event {
    pub created_at: DateTime,
    /// An object which indicates the name of the event and contains an optional object which
    /// represents the target of the event.
    pub event: EventKind,
    pub target: User,
    pub source: User,
}

macro_rules! impl_event {
    (
        $(#[$attr:meta])*
        pub enum $T:ident {
            $(
                $(#[$c_attr:meta])*
                :$Container:ident($c_tag:expr, $Content:ty)
            ),*;
            $(
                $(#[$l_attr:meta])*
                :$Label:ident($l_tag:expr)
            ),*;
            $(#[$cu_attr:meta])*
            :$Custom:ident(_, _),
        }
    ) => {
        $(#[$attr])*
        pub enum $T {
            $(
                $(#[$c_attr])*
                $Container($Content),
            )*
            $(
                $(#[$l_attr])*
                $Label,
            )*
            $(#[$cu_attr])*
            $Custom(String, Option<Value>),
        }

        impl Deserialize for Event {
            fn deserialize<D: Deserializer>(d: D) -> Result<Self, D::Error> {
                struct EventVisitor;

                impl Visitor for EventVisitor {
                    type Value = Event;

                    fn visit_map<V: MapVisitor>(self, mut v: V) -> Result<Event, V::Error> {
                        #[derive(Default)]
                        struct EventBuffer {
                            created_at: Option<DateTime>,
                            event: Option<EventKind>,
                            target: Option<User>,
                            source: Option<User>,
                        }

                        let mut event = EventBuffer::default();
                        let mut event_kind: Option<String> = None;
                        let mut target_obj: Option<Value> = None;

                        macro_rules! err_map {
                            () => (|e| V::Error::custom(e.to_string()));
                        }

                        while let Some(k) = v.visit_key::<String>()? {
                            match k.as_str() {
                                "created_at" => {
                                    let val = v.visit_value::<String>()?;
                                    event.created_at = Some(super::super::parse_datetime(&val).map_err(err_map!())?);
                                },
                                "event" => {
                                    let e = v.visit_value::<String>()?;
                                    event.event = if let Some(t) = target_obj.take() {
                                        match e.as_str() {
                                            $($c_tag => {
                                                $T::$Container(<$Content>::deserialize(t).map_err(err_map!())?)
                                            },)*
                                            $($l_tag => $T::$Label,)*
                                            _ => $T::$Custom(e, Some(t)),
                                        }.into()
                                    } else {
                                        match e.as_str() {
                                            $($l_tag => Some($T::$Label),)*
                                            $($c_tag)|* => { event_kind = Some(e); None },
                                            _ => Some($T::Custom(e, None)),
                                        }
                                    };
                                },
                                "target" => event.target = Some(v.visit_value()?),
                                "source" => event.source = Some(v.visit_value()?),
                                "target_object" => if let Some(e) = event_kind.take() {
                                    event.event = match e.as_str() {
                                        $($c_tag => $T::$Container(v.visit_value()?),)*
                                        $($l_tag => { v.visit_value::<IgnoredAny>()?; $T::$Label },)*
                                        _ => $T::$Custom(e, v.visit_value()?),
                                    }.into();
                                } else if event.event.is_none() {
                                    target_obj = Some(v.visit_value()?);
                                } else {
                                    v.visit_value::<IgnoredAny>()?;
                                },
                                _ => { v.visit_value::<IgnoredAny>()?; },
                            }

                            if let EventBuffer {
                                    created_at: Some(ca), event: Some(e), target: Some(t), source: Some(s),
                                } = event
                            {
                                while v.visit::<IgnoredAny, IgnoredAny>()?.is_some() {}
                                return Ok(Event { created_at: ca, event: e, target: t, source: s });
                            }
                        }

                        Err(V::Error::missing_field(if event.created_at.is_none() {
                            "created_at"
                        } else if event.target.is_none() {
                            "target"
                        } else if event.source.is_none() {
                            "source"
                        } else if event.event.is_none() {
                            if target_obj.is_some() {
                                "event"
                            } else {
                                "target_object"
                            }
                        } else {
                            unreachable!();
                        }))
                    }

                    fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
                        write!(f, "a map")
                    }
                }

                d.deserialize_map(EventVisitor)
            }
        }
    };
}

impl_event! {
    /// An object which indicates the name of an event.
    /// It may contain an object called "target object" which represents the target of the event.
    ///
    /// The meaning of `target` and `source` field of an `Event` will be different based on the name of the event,
    /// as described below.
    ///
    /// | Description                         | Event Name             | `source`           | `target`       |
    /// | ----------------------------------- | ---------------------- | ------------------ | -------------- |
    /// | User deauthorizes stream            | `AccessRevoked`        | Deauthorizing user | App owner      |
    /// | User blocks someone                 | `Block`                | Current user       | Blocked user   |
    /// | User removes a block                | `Unblock`              | Current user       | Unblocked user |
    /// | User favorites a Tweet              | `Favorite`             | Current user       | Tweet author   |
    /// | User's Tweet is favorited           | `Favorite`             | Favoriting user    | Current user   |
    /// | User unfavorites a Tweet            | `Unfavorite`           | Current user       | Tweet author   |
    /// | User's Tweet is unfavorited         | `Unfavorite`           | Unfavoriting user  | Current user   |
    /// | User follows someone                | `Follow`               | Current user       | Followed user  |
    /// | User is followed                    | `Follow`               | Following user     | Current user   |
    /// | User unfollows someone              | `Unfollow`             | Current user       | Followed user  |
    /// | User creates a list                 | `ListCreated`          | Current user       | Current user   |
    /// | User deletes a list                 | `ListDestroyed`        | Current user       | Current user   |
    /// | User edits a list                   | `ListUpdated`          | Current user       | Current user   |
    /// | User adds someone to a list         | `ListMemberAdded`      | Current user       | Added user     |
    /// | User is added to a list             | `ListMemberAdded`      | Adding user        | Current user   |
    /// | User removes someone from a list    | `ListMemberRemoved`    | Current user       | Removed user   |
    /// | User is removed from a list         | `ListMemberRemoved`    | Removing user      | Current user   |
    /// | User subscribes to a list           | `ListUserSubscribed`   | Current user       | List owner     |
    /// | User's list is subscribed to        | `ListUserSubscribed`   | Subscribing user   | Current user   |
    /// | User unsubscribes from a list       | `ListUserUnsubscribed` | Current user       | List owner     |
    /// | User's list is unsubscribed from    | `ListUserUnsubscribed` | Unsubscribing user | Current user   |
    /// | User's Tweet is quoted              | `QuotedTweet`          | quoting User       | Current User   |
    /// | User updates their profile          | `UserUpdate`           | Current user       | Current user   |
    /// | User updates their protected status | `UserUpdate`           | Current user       | Current user   |
    #[derive(Clone, Debug, PartialEq)]
    pub enum EventKind {
        :Favorite("favorite", Tweet),
        :Unfavorite("unfavorite", Tweet),
        :ListCreated("list_created", List),
        :ListDestroyed("list_destroyed", List),
        :ListUpdated("list_updated", List),
        :ListMemberAdded("list_member_added", List),
        :ListMemberRemoved("list_member_removed", List),
        :ListUserSubscribed("list_user_subscribed", List),
        :ListUserUnsubscribed("list_user_unsubscribed", List),
        :QuotedTweet("quoted_tweet", Tweet);
        :AccessRevoked("access_revoked"),
        :Block("block"),
        :Unblock("unblock"),
        :Follow("follow"),
        :Unfollow("unfollow"),
        :UserUpdate("user_update");
        /// An event this library does not know. The first value is raw event name
        /// and the second is the target object.
        :Custom(_, _),
    }
}