diff --git a/Cargo.toml b/Cargo.toml index 5f47ab7..e6d5222 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,8 +33,12 @@ features = ["client"] [dev-dependencies] env_logger = "0.9.0" mockall = "0.11.0" +assert-json-diff = "^2.0.2" [dev-dependencies.async-tls] version = "0.11.0" default-features = false -features = ["server"] \ No newline at end of file +features = ["server"] + +[build] +incremental = true diff --git a/examples/events_app_mention.rs b/examples/events_app_mention.rs index e24289d..4e7e43c 100644 --- a/examples/events_app_mention.rs +++ b/examples/events_app_mention.rs @@ -2,7 +2,7 @@ use std::env; use async_trait::async_trait; use slack::chat::post_message::{post_message, PostMessageRequest}; -use slack::event_api::event::{Event, EventCallbackType}; +use slack::event_api::event::{Event, EventType}; use slack::http_client::{default_client, SlackWebAPIClient}; use slack::socket::event::{EventsAPI, HelloEvent}; use slack::socket::socket_mode::{ack, EventHandler, SocketMode, Stream}; @@ -51,8 +51,8 @@ where .expect("socket mode ack error."); match e.payload { - Event::EventCallback(event_callback) => match event_callback.event { - EventCallbackType::AppMention { + Event{event, ..} => match event { + EventType::AppMention { text, channel, ts, diff --git a/src/event_api/app.rs b/src/event_api/app.rs index c17785e..a77aa92 100644 --- a/src/event_api/app.rs +++ b/src/event_api/app.rs @@ -3,13 +3,25 @@ use crate::users::user::User; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use serde_json::Value; #[skip_serializing_none] #[derive(Deserialize, Serialize, Debug, PartialEq)] +/// See more at +/// +/// Note that the `enterprise` field is a bit ambiguous. Slack's official +/// documentation says this value may be null or not null (but does not provide +/// an example). Therefore we use the `serde_json::Value::Null` variant to +/// represent this possibility pub struct AppRequest { - pub id: Option, pub app: Option, + pub enterprise: Value, + pub id: Option, + pub message: Option, pub previous_resolution: Option, + pub scopes: Option>, + pub team: Option, + pub user: Option, } #[skip_serializing_none] @@ -25,10 +37,6 @@ pub struct App { pub is_app_directory_approved: Option, pub is_internal: Option, pub additional_info: Option, - pub user: Option, - pub team: Option, - pub scopes: Option>, - pub message: Option, } #[skip_serializing_none] @@ -46,3 +54,74 @@ pub struct Scope { pub is_sensitive: Option, pub token_type: Option, } + +#[cfg(test)] +mod tests { + use crate::event_api::event::EventType; + use assert_json_diff::*; + use serde_json::Value; + + #[test] + fn deserialized_app_requested_event() { + let json = r##" + { + "type": "app_requested", + "app_request":{ + "id": "1234", + "app": { + "id": "A5678", + "name": "Brent's app", + "description": "They're good apps, Bront.", + "help_url": "brontsapp.com", + "privacy_policy_url": "brontsapp.com", + "app_homepage_url": "brontsapp.com", + "app_directory_url": "https://slack.slack.com/apps/A102ARD7Y", + "is_app_directory_approved": true, + "is_internal": false, + "additional_info": "none" + }, + "previous_resolution": { + "status": "approved", + "scopes": [ + { + "name": "app_requested", + "description": "allows this app to listen for app install requests", + "is_sensitive": false, + "token_type": "user" + }] + }, + "user":{ + "id": "U1234", + "name": "Bront", + "email": "bront@brent.com" + }, + "team": { + "id": "T1234", + "name": "Brant App Team", + "domain": "brantappteam" + }, + "enterprise": null, + "scopes": [ + { + "name": "app_requested", + "description": "allows this app to listen for app install requests", + "is_sensitive": false, + "token_type": "user" + } + ], + "message": "none" + } + } + "##; + let deserialized = serde_json::from_str::(&json).unwrap(); + // Make comparison between our deserialized struct and generic serde_json Value to ensure + // that all expected keys exist + let expected: Value = serde_json::from_str(&json).unwrap(); + assert_json_eq!(deserialized, expected); + + match deserialized { + EventType::AppRequested{..} => assert!(true), + _ => panic!("unrecognized variant"), + } + } +} diff --git a/src/event_api/event.rs b/src/event_api/event.rs index 0a5833b..2255fcb 100644 --- a/src/event_api/event.rs +++ b/src/event_api/event.rs @@ -2,123 +2,60 @@ use crate::channels::channel::Channel; use crate::event_api::app::AppRequest; -use crate::team::teams::Team; +use crate::event_api::messages::{MessageBasic, MessageSubtype, MessageMetadata}; +use crate::invites::invites::{Invite}; +use crate::items::item::{Item}; +use crate::files::file::{File}; +use crate::team::teams::{Team}; +use crate::team::subteams::*; +use crate::users::user::{User}; use crate::views::view::View; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; /// [Event API](https://api.slack.com/events?filter=Events) -#[derive(Deserialize, Serialize, Debug, PartialEq)] -#[serde(tag = "type")] -pub enum Event { - /// Event callback - #[serde(rename = "event_callback")] - EventCallback(EventCallback), -} - -impl Event { - pub fn block_type(&self) -> EventType { - match self { - Event::EventCallback(event_callback) => match event_callback.event { - EventCallbackType::AppHomeOpened { .. } => EventType::AppHomeOpened, - EventCallbackType::AppMention { .. } => EventType::AppMention, - EventCallbackType::AppRateLimited { .. } => EventType::AppRateLimited, - EventCallbackType::AppRequested { .. } => EventType::AppRequested, - EventCallbackType::AppUninstalled { .. } => EventType::AppUninstalled, - EventCallbackType::ChannelArchive { .. } => EventType::ChannelArchive, - EventCallbackType::ChannelCreated { .. } => EventType::ChannelCreated, - EventCallbackType::ChannelDeleted { .. } => EventType::ChannelDeleted, - EventCallbackType::ChannelHistoryChanged { .. } => EventType::ChannelHistoryChanged, - EventCallbackType::ChannelIDChanged { .. } => EventType::ChannelIDChanged, - EventCallbackType::ChannelLeft { .. } => EventType::ChannelLeft, - EventCallbackType::ChannelRename { .. } => EventType::ChannelRename, - EventCallbackType::ChannelShared { .. } => EventType::ChannelShared, - EventCallbackType::ChannelUnarchive { .. } => EventType::ChannelUnarchive, - EventCallbackType::ChannelUnshared { .. } => EventType::ChannelUnshared, - EventCallbackType::EmojiChanged { .. } => EventType::EmojiChanged, - EventCallbackType::GridMigrationFinished { .. } => EventType::GridMigrationFinished, - EventCallbackType::GridMigrationStarted { .. } => EventType::GridMigrationStarted, - EventCallbackType::GroupArchive { .. } => EventType::GroupArchive, - EventCallbackType::GroupClose { .. } => EventType::GroupClose, - EventCallbackType::GroupDeleted { .. } => EventType::GroupDeleted, - EventCallbackType::GroupHistoryChanged { .. } => EventType::GroupHistoryChanged, - EventCallbackType::GroupLeft { .. } => EventType::GroupLeft, - EventCallbackType::GroupOpen { .. } => EventType::GroupOpen, - EventCallbackType::GroupRename { .. } => EventType::GroupRename, - EventCallbackType::GroupUnarchive { .. } => EventType::GroupUnarchive, - EventCallbackType::ImClose { .. } => EventType::ImClose, - EventCallbackType::ImCreated { .. } => EventType::ImCreated, - EventCallbackType::ImHistoryChanged { .. } => EventType::ImHistoryChanged, - EventCallbackType::ImOpen { .. } => EventType::ImOpen, - EventCallbackType::InviteRequested { .. } => EventType::InviteRequested, - EventCallbackType::LinkShared => EventType::LinkShared, - EventCallbackType::MemberJoinedChannel { .. } => EventType::MemberJoinedChannel, - EventCallbackType::MemberLeftChannel { .. } => EventType::MemberLeftChannel, - EventCallbackType::Message { .. } => EventType::Message, - EventCallbackType::Other => EventType::Other, - }, - } - } -} - /// [Event API Type](https://api.slack.com/events?filter=Events) +/// Example of an [event wrapper](https://api.slack.com/types/event) +/// ``` +/// { +/// "token": "XXYYZZ", +/// "team_id": "TXXXXXXXX", +/// "api_app_id": "AXXXXXXXXX", +/// "event": { +/// "type": "name_of_event", +/// "event_ts": "1234567890.123456", +/// "user": "UXXXXXXX1" +/// }, +/// "type": "event_callback", +/// "authed_users": [ +/// "UXXXXXXX1", +/// "UXXXXXXX2" +/// ], +/// "event_id": "Ev08MFMKH6", +/// "event_time": 1234567890 +/// } +/// ``` #[derive(Deserialize, Serialize, Debug, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum EventType { - AppHomeOpened, - AppMention, - AppRateLimited, - AppRequested, - AppUninstalled, - ChannelArchive, - ChannelCreated, - ChannelDeleted, - ChannelHistoryChanged, - ChannelIDChanged, - ChannelLeft, - ChannelRename, - ChannelShared, - ChannelUnarchive, - ChannelUnshared, - EmojiChanged, - GridMigrationFinished, - GridMigrationStarted, - GroupArchive, - GroupClose, - GroupDeleted, - GroupHistoryChanged, - GroupLeft, - GroupOpen, - GroupRename, - GroupUnarchive, - ImClose, - ImCreated, - ImHistoryChanged, - ImOpen, - InviteRequested, - LinkShared, - MemberJoinedChannel, - MemberLeftChannel, - Message, - #[serde(other)] - Other, -} - -#[skip_serializing_none] -#[derive(Deserialize, Serialize, Debug, PartialEq)] -pub struct EventCallback { +#[serde(tag = "type", rename = "event_callback")] +pub struct Event { pub token: String, pub team_id: String, pub api_app_id: String, - pub event: EventCallbackType, + #[serde(skip_serializing_if = "Option::is_none")] + pub authed_users: Option>, pub event_id: String, - pub event_time: i32, + pub event_time: u32, + pub event: EventType, } #[derive(Deserialize, Serialize, Debug, PartialEq)] #[skip_serializing_none] #[serde(rename_all = "snake_case", tag = "type")] -pub enum EventCallbackType { +pub enum EventType { + /// The list of accounts a user is signed into has changed + /// + /// + AccountsChanged, /// User clicked into your App Home #[serde(rename = "app_home_opened")] AppHomeOpened { @@ -126,9 +63,12 @@ pub enum EventCallbackType { channel: String, event_ts: String, tab: String, - view: View, + #[serde(skip_serializing_if = "Option::is_none")] + view: Option, }, /// Subscribe to only the message events that mention your app or bot + /// + /// AppMention { channel: String, event_ts: String, @@ -138,7 +78,8 @@ pub enum EventCallbackType { user: String, }, /// Indicates your app's event subscriptions are being rate limited - #[serde(rename = "app_rate_limited")] + /// + /// AppRateLimited { token: String, team_id: String, @@ -146,20 +87,49 @@ pub enum EventCallbackType { api_app_id: String, }, /// User requested an app - #[serde(rename = "app_requested")] - AppRequested { app_request: AppRequest }, + /// + /// + AppRequested { + app_request: AppRequest + }, /// Your Slack app was uninstalled. + /// + /// AppUninstalled, + /// A call was rejected + /// + /// + CallRejected { + call_id: String, + channel_id: String, + external_unique_id: String, + user_id: String, + }, /// A channel was archived + /// + /// #[serde(rename = "channel_archive")] - ChannelArchive { channel: String, user: String }, + ChannelArchive { + channel: String, + user: String + }, /// A channel was created + /// + /// #[serde(rename = "channel_created")] - ChannelCreated { channel: Channel }, + ChannelCreated { + channel: Channel + }, /// A channel was deleted + /// + /// #[serde(rename = "channel_deleted")] - ChannelDeleted { channel: String }, + ChannelDeleted { + channel: String + }, /// Bulk updates were made to a channel's history + /// + /// #[serde(rename = "channel_history_changed")] ChannelHistoryChanged { latest: String, @@ -167,6 +137,8 @@ pub enum EventCallbackType { event_ts: String, }, /// A channel ID changed + /// + /// #[serde(rename = "channel_id_changed")] ChannelIDChanged { old_channel_id: String, @@ -174,12 +146,22 @@ pub enum EventCallbackType { event_ts: String, }, /// You left a channel + /// + /// #[serde(rename = "channel_left")] - ChannelLeft { channel: String }, + ChannelLeft { + channel: String + }, /// A channel was renamed + /// + /// #[serde(rename = "channel_rename")] - ChannelRename { channel: Channel }, + ChannelRename { + channel: Channel + }, /// A channel has been shared with an external workspace + /// + /// #[serde(rename = "channel_shared")] ChannelShared { connected_team_id: String, @@ -187,9 +169,16 @@ pub enum EventCallbackType { event_ts: String, }, /// A channel was unarchived + /// + /// #[serde(rename = "channel_unarchive")] - ChannelUnarchive { channel: String, user: String }, + ChannelUnarchive { + channel: String, + user: String + }, ///A channel has been unshared with an external workspace + /// + /// #[serde(rename = "channel_unshared")] ChannelUnshared { previously_connected_team_id: String, @@ -197,63 +186,175 @@ pub enum EventCallbackType { is_ext_shared: bool, event_ts: String, }, + /// Do not Disturb settings changed for the current user + /// + /// + #[serde(rename = "dnd_updated")] + DoNotDisturbUpdated { + user: String, + dnd_status: DoNotDisturbStatus + }, + /// Do not Disturb settings changed for a member + /// + /// + #[serde(rename = "dnd_updated_user")] + DoNotDisturbUpdatedUser { + user: String, + dnd_status: DoNotDisturbStatus + }, + /// The workspace email domain has changed + /// + /// + EmailDomainChanged { + email_domain: String, + event_ts: String, + }, /// A custom emoji has been added or changed + /// + /// #[serde(rename = "emoji_changed")] - EmojiChanged { - subtype: String, - names: Vec, + EmojiChanged(EmojiSubtype), + /// A file was changed + /// + /// + FileChange { + file_id: String, + file: File, + }, + /// A file was created + /// + /// + FileCreated { + file_id: String, + file: File, + }, + /// A file was deleted + /// + /// + FileDeleted { + file_id: String, + event_ts: String, + }, + /// A file was made public + /// + /// + FilePublic { + file_id: String, + file: File, + }, + /// A file was shared + /// + /// + FileShared { + channel_id: String, event_ts: String, + file_id: String, + file: File, + user_id: String, + }, + /// A file was unshared + /// + /// + FileUnshared { + file_id: String, + file: File, }, /// An enterprise grid migration has finished on this workspace. - GridMigrationFinished, + /// + /// + GridMigrationFinished { + enterprise_id: String, + }, /// An enterprise grid migration has started on this workspace. - GridMigrationStarted, + /// + /// + GridMigrationStarted { + enterprise_id: String, + }, /// A private channel was archived - #[serde(rename = "group_archive")] - GroupArchive { channel: String }, + /// + /// + GroupArchive { + channel: String + }, /// You closed a private channel - #[serde(rename = "group_close")] - GroupClose { user: String, channel: String }, + /// + /// + GroupClose { + user: String, + channel: String + }, /// A private channel was deleted - #[serde(rename = "group_deleted")] - GroupDeleted { channel: String }, + /// + /// + GroupDeleted { + channel: String + }, /// A private channel was deleted - #[serde(rename = "group_history_changed")] + /// + /// GroupHistoryChanged { latest: String, ts: String, event_ts: String, }, /// You left a private channel - #[serde(rename = "group_left")] - GroupLeft { channel: String }, + /// + /// + GroupLeft { + channel: String + }, /// You created a group DM - #[serde(rename = "group_open")] - GroupOpen { user: String, channel: String }, + /// + /// + GroupOpen { + user: String, + channel: String + }, /// A private channel was renamed - #[serde(rename = "group_rename")] - GroupRename { channel: Channel }, + /// + /// + GroupRename { + channel: Channel + }, /// A private channel was unarchived - #[serde(rename = "group_unarchive")] - GroupUnarchive { channel: String }, + /// + /// + GroupUnarchive { + channel: String + }, /// You closed a DM - #[serde(rename = "im_close")] - ImClose { user: String, channel: String }, + /// + /// + ImClose { + user: String, + channel: String + }, /// A DM was created - #[serde(rename = "im_created")] - ImCreated { user: String, channel: Channel }, + /// + /// + ImCreated { + user: String, + channel: Channel + }, /// Bulk updates were made to a DM's history - #[serde(rename = "im_history_changed")] + /// + /// ImHistoryChanged { latest: String, ts: String, event_ts: String, }, /// You opened a DM - #[serde(rename = "im_open")] - ImOpen { user: String, channel: String }, + /// + /// + ImOpen { + user: String, + channel: String + }, /// User requested an invite - #[serde(rename = "invite_requested")] + /// + /// InviteRequested { id: String, email: String, @@ -266,44 +367,313 @@ pub enum EventCallbackType { request_reason: String, team: Team, }, - // TODO: To be implemented in the future /// A message was posted containing one or more links relevant to your application - #[serde(rename = "link_shared")] - LinkShared, + /// + /// + LinkShared { + channel: String, + is_bot_user_member: bool, + user: String, + message_ts: String, + unfurl_id: String, + thread_ts: String, + source: LinkSource, + links: Vec, + }, /// A user joined a public or private channel - #[serde(rename = "member_joined_channel")] + /// + /// MemberJoinedChannel { user: String, channel: String, - channel_type: String, + channel_type: ChannelType, team: String, inviter: String, }, /// A user left a public or private channel - #[serde(rename = "member_joined_channel")] + /// + /// MemberLeftChannel { user: String, channel: String, - channel_type: String, + channel_type: ChannelType, team: String, }, /// A message was sent to a channel - Message { - channel_type: String, - channel: String, + #[serde(rename = "message")] + Message(MessageBasic), + #[serde(rename = "message")] + MessageSubtype(MessageSubtype), + /// Message metadata was deleted + /// + /// + MessageMetadataDeleted { + app_id: String, + bot_id: String, + channel_id: String, + deleted_ts: String, event_ts: String, - text: String, - thread_ts: Option, - ts: String, + message_ts: String, + previous_metadata: MessageMetadata, + team_id: String, + user_id: String, + }, + /// Message metadata was posted + /// + /// + MessageMetadataPosted { + app_id: String, + bot_id: String, + channel_id: String, + event_ts: String, + message_ts: String, + metadata: MessageMetadata, + team_id: String, + user_id: String, + }, + /// Message metadata was updated + /// + /// + MessageMetadataUpdated { + app_id: String, + bot_id: String, + channel_id: String, + event_ts: String, + message_ts: String, + metadata: MessageMetadata, + previous_metadata: MessageMetadata, + team_id: String, + user_id: String, + }, + /// A pin was added to a channel + /// + /// + PinAdded { + channel_id: String, + event_ts: String, + item: Item, + user: String, + }, + /// A pin was removed from a channel + /// + /// + PinRemoved { + channel_id: String, + event_ts: String, + has_pins: bool, + item: Item, user: String, }, + /// A member added an emoji reaction + /// + /// + ReactionAdded { + event_ts: String, + item: Item, + item_user: String, + reaction: String, + user: String, + }, + /// A member removed an emoji reaction + /// + /// + ReactionRemoved { + event_ts: String, + item: Item, + item_user: String, + reaction: String, + user: String, + }, + /// A shared channel invite was accepted + /// + /// + SharedChannelInviteAccepted { + accepting_user: User, + approval_required: bool, + channel: Channel, + event_ts: String, + invite: Invite, + teams_in_channel: Vec, + }, + /// A shared channel invite was approved + /// + /// + SharedChannelInviteApproved { + approving_user: User, + approving_team_id: String, + channel: Channel, + event_ts: String, + invite: Invite, + teams_in_channel: Vec, + }, + /// A shared channel invite was declined + /// + /// + SharedChannelInviteDeclined { + channel: Channel, + declining: User, + declining_team_id: String, + event_ts: String, + invite: Invite, + teams_in_channel: Vec, + }, + /// A shared channel invite was received + /// + /// + SharedChannelInviteReceived { + channel: Channel, + event_ts: String, + invite: Invite, + }, + /// A member has starred an item + /// + /// + StarAdded { + event_ts: String, + item: Item, + user: String, + }, + /// A member removed a star + /// + /// + StarRemoved { + event_ts: String, + item: Item, + user: String, + }, + /// A User Group has been added to the workspace + /// + /// + SubteamCreated { + subteam: Subteam, + }, + /// The membership of an existing User Group has changed + /// + /// + SubteamMembersChanged { + added_users: Vec, + #[serde(deserialize_with = "Subteam::make_int")] + added_users_count: u64, + date_previous_update: u32, + date_update: u32, + removed_users: Vec, + #[serde(deserialize_with = "Subteam::make_int")] + removed_users_count: u64, + subteam_id: String, + team_id: String, + }, + /// You have been added to a User Group + /// + /// + SubteamSelfAdded { + subteam_id: String, + }, + /// You have been removed from a User Group + /// + /// + SubteamSelfRemoved { + subteam_id: String, + }, + /// An existing User Group has been updated or its members changed + /// + /// + SubteamUpdated { + subteam: Subteam, + }, + /// Access to a set of teams was granted to your org app + /// + /// + TeamAccessGranted { + team_ids: Vec + }, + /// Access to a set of teams was revoked from your org app + /// + /// + TeamAccessRevoked { + team_ids: Vec + }, + /// The workspace domain has changed + /// + /// + TeamAccessRevoked { + domain: String, + team_id: String, + url: String, + }, + /// A new member has joined + /// + /// + TeamAccessRevoked { + user: User, + }, + /// The workspace name has changed + /// + /// + TeamRename { + name: String, + team_id: String, + }, #[serde(other)] Other, } +#[derive(Deserialize, Serialize, Debug, PartialEq)] +pub struct DoNotDisturbStatus { + pub dnd_enabled: bool, + pub next_dnd_start_ts: u32, + pub next_dnd_end_ts: u32, + pub snooze_enabled: Option, + pub snooze_endtime: Option, +} + +#[derive(Deserialize, Serialize, Debug, PartialEq)] +#[serde(tag = "subtype", rename_all = "snake_case")] +pub enum EmojiSubtype { + Add { + name: String, + value: String, + event_ts: String, + }, + Remove { + names: Vec, + event_ts: String, + }, + Rename { + old_name: String, + new_name: String, + value: String, + event_ts: String, + } +} + +#[derive(Deserialize, Serialize, Debug, PartialEq)] +// TODO implement a better type for URL (besides a string) +pub struct Link { + pub domain: String, + pub url: String, +} + +#[derive(Deserialize, Serialize, Debug, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum LinkSource { + Composer, + ConversationsHistory, +} + +/// See for more +#[derive(Deserialize, Serialize, Debug, PartialEq)] +pub enum ChannelType { + #[serde(rename = "C")] + Public, + #[serde(rename = "G")] + Private +} + + #[cfg(test)] mod test { - use super::*; + use assert_json_diff::*; + use crate::event_api::event::{Event, EventType, EmojiSubtype}; #[test] fn deserialize_app_home_opened_event() { @@ -326,23 +696,916 @@ mod test { } }"##; let event = serde_json::from_str::(json).unwrap(); + match event.event { + EventType::AppHomeOpened{..} => assert!(true, "true"), + _ => panic!("Event callback deserialize into incorrect variant"), + } + } + + #[test] + fn it_serializes_event_from_a_struct() { + let json = r##"{ + "token": "bHKJ2n9AW6Ju3MjciOHfbA1b", + "team_id": "T1234567890", + "api_app_id": "A0000000000", + "event_id": "Ev0000000000", + "event_time": 1600000000, + "type": "event_callback", + "event": { + "type": "app_home_opened", + "user": "U061F7AUR", + "channel": "D0LAN2Q65", + "event_ts": "1515449522000016", + "tab": "home" + } + }"##; + + let struct_thing = Event { + token: "bHKJ2n9AW6Ju3MjciOHfbA1b".to_string(), + team_id: "T1234567890".to_string(), + api_app_id: "A0000000000".to_string(), + event_id: "Ev0000000000".to_string(), + event_time: 1600000000, + authed_users: None, + event: EventType::AppHomeOpened { + user: "U061F7AUR".to_string(), + channel: "D0LAN2Q65".to_string(), + event_ts: "1515449522000016".to_string(), + tab: "home".to_string(), + view: None, + } + }; + + let serialized_json = serde_json::to_string(&struct_thing).unwrap(); + let deserialized = serde_json::from_str::(&serialized_json).unwrap(); + let expected = serde_json::from_str::(&json).unwrap(); + assert_json_eq!(deserialized, expected); + } + + #[test] + fn deserializes_accounts_changed() { + let json = r##" + { + "token": "12345FVmRUzNDOAu12345h", + "team_id": "TL1BBBQBD", + "api_app_id": "BBBU04BB4", + "event": { + "type": "accounts_changed" + }, + "type": "event_callback", + "event_id": "EvLLACMB6BB", + "event_time": 1563448153, + "authed_users": ["UBBB1TYR5"] + } + "##; + let event = serde_json::from_str::(json).unwrap(); + match event.event { + EventType::AccountsChanged{..} => assert!(true), + _ => panic!("Did not deserialize into expected variant"), + } + } + + #[test] + fn deserializes_app_rate_limited() { + let json = r##" + { + "token": "Jhj5dZrVaK7ZwHHjRyZWjbDl", + "type": "app_rate_limited", + "team_id": "T123456", + "minute_rate_limited": 1518467820, + "api_app_id": "A123456" + }"##; + let event = serde_json::from_str::(json).unwrap(); match event { - Event::EventCallback(event_callback) => match event_callback.event { - EventCallbackType::AppHomeOpened { - user, - channel, - event_ts, - tab, - view, - } => { - assert_eq!(user, "U061F7AUR"); - assert_eq!(channel, "D0LAN2Q65"); - assert_eq!(event_ts, "1515449522000016"); - assert_eq!(tab, "home"); - assert_eq!(view.id.unwrap(), "VPASKP233"); + EventType::AppRateLimited{..} => assert!(true), + _ => panic!("Did not deserialize into expected variant"), + } + } + + #[test] + fn deserializes_app_mention() { + let json = r##" + { + "type": "app_mention", + "user": "U061F7AUR", + "text": "<@U0LAN0Z89> is it everything a river should be?", + "ts": "1515449522.000016", + "channel": "C0LAN2Q65", + "event_ts": "1515449522000016" + }"##; + let event = serde_json::from_str::(json).unwrap(); + match event { + EventType::AppMention{..} => assert!(true), + _ => panic!("Did not deserialize into expected variant"), + } + } + + #[test] + fn deserializes_call_rejected() { + let json = r##" + { + "token": "12345FVmRUzNDOAu12345h", + "team_id": "TL1BBBQBD", + "api_app_id": "BBBU04BB4", + "event": { + "type": "call_rejected", + "call_id": "RL731AVEF", + "user_id": "ULJS1TYR5", + "channel_id": "DL5JN9K0T", + "external_unique_id": "123-456-7890" + }, + "type": "event_callback", + "event_id": "EvLLACMB6BB", + "event_time": 1563448153, + "authed_users": ["UBBB1TYR5"] + } + "##; + let event = serde_json::from_str::(json).unwrap(); + println!("{:?}", event.event); + match event.event { + EventType::CallRejected{..} => assert!(true), + _ => panic!("Did not deserialize into expected variant CallRejected"), + } + } + + #[test] + fn deserializes_app_uninstalled() { + let json = r##" + { + "token": "XXYYZZ", + "team_id": "TXXXXXXXX", + "api_app_id": "AXXXXXXXXX", + "event": { + "type": "app_uninstalled" + }, + "type": "event_callback", + "event_id": "EvXXXXXXXX", + "event_time": 1234567890 + } + "##; + let event = serde_json::from_str::(json).unwrap(); + match event.event { + EventType::AppUninstalled{..} => assert!(true), + _ => panic!("Did not deserialize into expected variant"), + } + } + + #[test] + fn deserializes_channel_archive() { + let json = r##" + { + "token": "XXYYZZ", + "team_id": "TXXXXXXXX", + "api_app_id": "AXXXXXXXXX", + "event": { + "type": "channel_archive", + "channel": "C024BE91L", + "user": "U024BE7LH" + }, + "type": "event_callback", + "event_id": "EvXXXXXXXX", + "event_time": 1234567890 + } + "##; + let event = serde_json::from_str::(json).unwrap(); + match event.event { + EventType::ChannelArchive{..} => assert!(true), + _ => panic!("Did not deserialize into expected variant"), + } + } + + #[test] + fn deserializes_channel_created() { + let json = r##" + { + "token": "XXYYZZ", + "team_id": "TXXXXXXXX", + "api_app_id": "AXXXXXXXXX", + "event": { + "type": "channel_created", + "channel": { + "id": "C024BE91L", + "name": "fun", + "created": 1360782804, + "creator": "U024BE7LH" + } + }, + "type": "event_callback", + "event_id": "EvXXXXXXXX", + "event_time": 1234567890 + } + "##; + let event = serde_json::from_str::(json).unwrap(); + match event.event { + EventType::ChannelCreated{..} => assert!(true), + _ => panic!("Did not deserialize into expected variant"), + } + } + + #[test] + fn deserializes_channel_deleted() { + let json = r##" + { + "token": "XXYYZZ", + "team_id": "TXXXXXXXX", + "api_app_id": "AXXXXXXXXX", + "event": { + "type": "channel_deleted", + "channel": "C024BE91L" + }, + "type": "event_callback", + "event_id": "EvXXXXXXXX", + "event_time": 1234567890 + } + "##; + let event = serde_json::from_str::(json).unwrap(); + match event.event { + EventType::ChannelDeleted{..} => assert!(true), + _ => panic!("Did not deserialize into expected variant ChannelDeleted"), + } + } + + #[test] + fn deserializes_do_not_disturb_updated() { + let json = r##" + { + "token": "XXYYZZ", + "team_id": "TXXXXXXXX", + "api_app_id": "AXXXXXXXXX", + "event": { + "type": "dnd_updated", + "user": "U1234", + "dnd_status": { + "dnd_enabled": true, + "next_dnd_start_ts": 1450387800, + "next_dnd_end_ts": 1450423800, + "snooze_enabled": true, + "snooze_endtime": 1450373897 + } + }, + "type": "event_callback", + "event_id": "EvXXXXXXXX", + "event_time": 1234567890 + } + "##; + let event = serde_json::from_str::(json).unwrap(); + match event.event { + EventType::DoNotDisturbUpdated{..} => assert!(true), + _ => panic!("Did not deserialize into expected variant DoNotDisturbUpdated"), + } + } + + #[test] + fn deserializes_do_not_disturb_updated_user() { + let json = r##" + { + "token": "XXYYZZ", + "team_id": "TXXXXXXXX", + "api_app_id": "AXXXXXXXXX", + "event": { + "type": "dnd_updated_user", + "user": "U1234", + "dnd_status": { + "dnd_enabled": true, + "next_dnd_start_ts": 1450387800, + "next_dnd_end_ts": 1450423800 + } + }, + "type": "event_callback", + "event_id": "EvXXXXXXXX", + "event_time": 1234567890 + } + "##; + let event = serde_json::from_str::(json).unwrap(); + match event.event { + EventType::DoNotDisturbUpdatedUser{..} => assert!(true), + _ => panic!("Did not deserialize into expected variant DoNotDisturbUpdatedUser"), + } + } + + #[test] + fn deserializes_emoji_changed() { + let json = r##" + { + "token": "XXYYZZ", + "team_id": "TXXXXXXXX", + "api_app_id": "AXXXXXXXXX", + "event": { + "type": "emoji_changed", + "subtype": "add", + "name": "picard_facepalm", + "value": "https://my.slack.com/emoji/picard_facepalm/db8e287430eaa459.gif", + "event_ts" : "1361482916.000004" + }, + "type": "event_callback", + "event_id": "EvXXXXXXXX", + "event_time": 1234567890 + } + "##; + let event = serde_json::from_str::(json).unwrap(); + match event.event { + EventType::EmojiChanged(subtype) => { + match subtype { + EmojiSubtype::Add{..} => assert!(true), + _ => panic!("Did not deserialize into expected variant EmojiSubtype::Add") + } + }, + _ => panic!("Did not deserialize into expected variant EmojiChanged"), + } + } + + #[test] + fn deserializes_emoji_changed_removed() { + let json = r##" + { + "token": "XXYYZZ", + "team_id": "TXXXXXXXX", + "api_app_id": "AXXXXXXXXX", + "event": { + "type": "emoji_changed", + "subtype": "remove", + "names": ["picard_facepalm"], + "event_ts" : "1361482916.000004" + }, + "type": "event_callback", + "event_id": "EvXXXXXXXX", + "event_time": 1234567890 + } + "##; + let event = serde_json::from_str::(json).unwrap(); + match event.event { + EventType::EmojiChanged(subtype) => { + match subtype { + EmojiSubtype::Remove{..} => assert!(true), + _ => panic!("Did not deserialize into expected variant EmojiSubtype::Remove") } - _ => panic!("Event callback deserialize into incorrect variant"), }, + _ => panic!("Did not deserialize into expected variant EmojiChanged"), + } + } + + #[test] + fn deserializes_file_change() { + let json = r##" + { + "token": "XXYYZZ", + "team_id": "TXXXXXXXX", + "api_app_id": "AXXXXXXXXX", + "event": { + "type": "file_change", + "file_id": "F2147483862", + "file": { + "id": "F2147483862" + } + }, + "type": "event_callback", + "event_id": "EvXXXXXXXX", + "event_time": 1234567890 + } + "##; + let event = serde_json::from_str::(json).unwrap(); + match event.event { + EventType::FileChange{..} => assert!(true), + _ => panic!("Did not deserialize into expected variant EmojiSubtype::Remove") + } + } + + #[test] + fn deserializes_link_shared() { + let json = r##" + { + "token": "XXYYZZ", + "team_id": "TXXXXXXXX", + "api_app_id": "AXXXXXXXXX", + "event": { + "type": "link_shared", + "channel": "Cxxxxxx", + "is_bot_user_member": true, + "user": "Uxxxxxxx", + "message_ts": "123456789.9875", + "unfurl_id": "C123456.123456789.987501.1b90fa1278528ce6e2f6c5c2bfa1abc9a41d57d02b29d173f40399c9ffdecf4b", + "thread_ts": "123456621.1855", + "source": "conversations_history", + "links": [ + { + "domain": "example.com", + "url": "https://example.com/12345" + }, + { + "domain": "example.com", + "url": "https://example.com/67890" + }, + { + "domain": "another-example.com", + "url": "https://yet.another-example.com/v/abcde" + } + ] + }, + "type": "event_callback", + "authed_users": [ + "UXXXXXXX1", + "UXXXXXXX2" + ], + "event_id": "Ev08MFMKH6", + "event_time": 123456789 + } + "##; + let event = serde_json::from_str::(json).unwrap(); + match event.event { + EventType::LinkShared{..} => assert!(true), + _ => panic!("Did not deserialize into expected variant EventType::LinkShared") + } + } + + #[test] + fn deserializes_member_joined_channel() { + let json = r##" + { + "token": "XXYYZZ", + "team_id": "TXXXXXXXX", + "api_app_id": "AXXXXXXXXX", + "event": { + "type": "member_joined_channel", + "user": "W06GH7XHN", + "channel": "C0698JE0H", + "channel_type": "C", + "team": "T024BE7LD", + "inviter": "U123456789" + }, + "type": "event_callback", + "event_id": "EvXXXXXXXX", + "event_time": 1234567890 + } + "##; + let event = serde_json::from_str::(json).unwrap(); + match event.event { + EventType::MemberJoinedChannel{..} => assert!(true), + _ => panic!("Did not deserialize into expected variant MemberJoinedChannel") + } + } + + #[test] + fn deserializes_message_metadata_deleted() { + let json = r##" + { + "token": "XXYYZZ", + "team_id": "TXXXXXXXX", + "api_app_id": "AXXXXXXXXX", + "event": { + "type": "message_metadata_deleted", + "channel_id": "CJN879K8A", + "event_ts": "1658907498.002500", + "previous_metadata": + { + "event_type": "task_created", + "event_payload": + { + "id": "TK-2135", + "summary": "New issue with the display of mobile element", + "description": "An end user has found a problem with the new mobile container for data entry. It was reproduced in the current version of IOS.", + "priority": "HIGH", + "resource_type": "TASK" + } + }, + "app_id": "AQF4F123M", + "bot_id": "B8241P2B34D", + "user_id": "UA8829BFL", + "team_id": "T12F3JCAP", + "message_ts": "1658905974.587109", + "deleted_ts": "1658907498.002500" + }, + "type": "event_callback", + "event_id": "EvXXXXXXXX", + "event_time": 1234567890 + } + "##; + let event = serde_json::from_str::(json).unwrap(); + match event.event { + EventType::MessageMetadataDeleted{..} => assert!(true), + _ => panic!("Did not deserialize into expected variant MessageMetadataDeleted") + } + } + #[test] + fn deserializes_message_metadata_posted() { + let json = r##" + { + "token": "XXYYZZ", + "team_id": "TXXXXXXXX", + "api_app_id": "AXXXXXXXXX", + "event": { + "type": "message_metadata_posted", + "app_id": "AQF4F123M", + "bot_id": "B8241P2B34D", + "user_id": "UA8829BFL", + "team_id": "T12F3JCAP", + "channel_id": "CJN879K8A", + "metadata": + { + "event_type": "task_created", + "event_payload": + { + "id": "TK-2132", + "summary": "New issue with the display of mobile element", + "description": "An end user has found a problem with the new mobile container for data entry. It was reproduced in the current version of IOS.", + "priority": "HIGH", + "resource_type": "TASK" + } + }, + "message_ts": "1658903885.673769", + "event_ts": "1658903885.673769" + }, + "type": "event_callback", + "event_id": "EvXXXXXXXX", + "event_time": 1234567890 + } + "##; + let event = serde_json::from_str::(json).unwrap(); + match event.event { + EventType::MessageMetadataPosted{..} => assert!(true), + _ => panic!("Did not deserialize into expected variant MessageMetadataPosted") + } + } + + #[test] + fn deserializes_message_metadata_updated() { + let json = r##" + { + "token": "XXYYZZ", + "team_id": "TXXXXXXXX", + "api_app_id": "AXXXXXXXXX", + "event": { + "type": "message_metadata_updated", + "channel_id": "CJN879K8A", + "event_ts": "1658906295.002200", + "previous_metadata": + { + "event_type": "task_created", + "event_payload": + { + "id": "TK-2132", + "summary": "New issue with the display of mobile element", + "description": "An end user has found a problem with the new mobile container for data entry. It was reproduced in the current version of IOS.", + "priority": "HIGH", + "resource_type": "TASK" + } + }, + "app_id": "AQF4F123M", + "bot_id": "B8241P2B34D", + "user_id": "UA8829BFL", + "team_id": "T12F3JCAP", + "message_ts": "1658905974.587109", + "metadata": + { + "event_type": "task_created", + "event_payload": + { + "id": "TK-2135", + "summary": "New issue with the display of mobile element", + "description": "An end user has found a problem with the new mobile container for data entry. It was reproduced in the current version of IOS.", + "priority": "HIGH", + "resource_type": "TASK" + } + } + }, + "type": "event_callback", + "event_id": "EvXXXXXXXX", + "event_time": 1234567890 + } + "##; + let event = serde_json::from_str::(json).unwrap(); + match event.event { + EventType::MessageMetadataUpdated{..} => assert!(true), + _ => panic!("Did not deserialize into expected variant MessageMetadataUpdated") + } + } + + #[test] + fn deserializes_shared_channel_invite_accepted() { + let json = r##" + { + "token": "XXYYZZ", + "team_id": "TXXXXXXXX", + "api_app_id": "AXXXXXXXXX", + "event": { + "type": "shared_channel_invite_accepted", + "approval_required": false, + "invite": { + "id": "I028YDERZSQ", + "date_created": 1626876000, + "date_invalid": 1628085600, + "inviting_team": { + "id": "T12345678", + "name": "Corgis", + "is_verified": false, + "domain": "corgis", + "date_created": 1480946400 + }, + "inviting_user": { + "id": "U12345678", + "team_id": "T12345678", + "name": "crus", + "updated": 1608081902, + "profile": { + "real_name": "Corgis Rus", + "display_name": "Corgis Rus", + "real_name_normalized": "Corgis Rus", + "display_name_normalized": "Corgis Rus", + "team": "T12345678", + "avatar_hash": "gcfh83a4c72k", + "email": "corgisrus@slack-corp.com", + "image_24": "https://placekitten.com/24/24", + "image_32": "https://placekitten.com/32/32", + "image_48": "https://placekitten.com/48/48", + "image_72": "https://placekitten.com/72/72", + "image_192": "https://placekitten.com/192/192", + "image_512": "https://placekitten.com/512/512" + } + }, + "recipient_email": "golden@doodle.com", + "recipient_user_id": "U87654321" + }, + "channel": { + "id": "C12345678", + "is_private": false, + "is_im": false, + "name": "test-slack-connect" + }, + "teams_in_channel": [ + { + "id": "T12345678", + "name": "Corgis", + "is_verified": false, + "domain": "corgis", + "date_created": 1626789600 + } + ], + "accepting_user": { + "id": "U87654321", + "team_id": "T87654321", + "name": "golden", + "updated": 1624406113, + "profile": { + "real_name": "Golden Doodle", + "display_name": "Golden", + "real_name_normalized": "Golden Doodle", + "display_name_normalized": "Golden", + "team": "T87654321", + "avatar_hash": "g717728b118x", + "email": "golden@doodle.com", + "image_24": "https://placekitten.com/24/24", + "image_32": "https://placekitten.com/32/32", + "image_48": "https://placekitten.com/48/48", + "image_72": "https://placekitten.com/72/72", + "image_192": "https://placekitten.com/192/192", + "image_512": "https://placekitten.com/512/512" + } + }, + "event_ts": "1626877800.000000" + }, + "type": "event_callback", + "event_id": "EvXXXXXXXX", + "event_time": 1234567890 + } + "##; + let event = serde_json::from_str::(json).unwrap(); + match event.event { + EventType::SharedChannelInviteAccepted{..} => assert!(true), + _ => panic!("Did not deserialize into expected variant SharedChannelInviteAccepted") + } + } + + #[test] + fn deserializes_shared_channel_invite_received() { + let json = r##" + { + "token": "XXYYZZ", + "team_id": "TXXXXXXXX", + "api_app_id": "AXXXXXXXXX", + "event": { + "type": "shared_channel_invite_received", + "invite": { + "id": "I028YDERZSQ", + "date_created": 1626876000, + "date_invalid": 1628085600, + "inviting_team": { + "id": "T12345678", + "name": "Corgis", + "is_verified": false, + "domain": "corgis", + "date_created": 1480946400 + }, + "inviting_user": { + "id": "U12345678", + "team_id": "T12345678", + "name": "crus", + "updated": 1608081902, + "profile": { + "real_name": "Corgis Rus", + "display_name": "Corgis Rus", + "real_name_normalized": "Corgis Rus", + "display_name_normalized": "Corgis Rus", + "team": "T12345678", + "avatar_hash": "gcfh83a4c72k", + "email": "corgisrus@slack-corp.com", + "image_24": "https://placekitten.com/24/24", + "image_32": "https://placekitten.com/32/32", + "image_48": "https://placekitten.com/48/48", + "image_72": "https://placekitten.com/72/72", + "image_192": "https://placekitten.com/192/192", + "image_512": "https://placekitten.com/512/512" + } + }, + "recipient_user_id": "U87654321" + }, + "channel": { + "id": "C12345678", + "is_private": false, + "is_im": false, + "name": "test-slack-connect" + }, + "event_ts": "1626876010.000100" + }, + "type": "event_callback", + "event_id": "EvXXXXXXXX", + "event_time": 1234567890 + } + "##; + let event = serde_json::from_str::(json).unwrap(); + match event.event { + EventType::SharedChannelInviteReceived{..} => assert!(true), + _ => panic!("Did not deserialize into expected variant SharedChannelInviteReceived") + } + } + + #[test] + fn deserializes_reaction_added() { + let json = r##" + { + "token": "XXYYZZ", + "team_id": "TXXXXXXXX", + "api_app_id": "AXXXXXXXXX", + "event": { + "type": "reaction_added", + "user": "U024BE7LH", + "reaction": "thumbsup", + "item_user": "U0G9QF9C6", + "item": { + "type": "message", + "channel": "C0G9QF9GZ", + "ts": "1360782400.498405" + }, + "event_ts": "1360782804.083113" + }, + "type": "event_callback", + "event_id": "EvXXXXXXXX", + "event_time": 1234567890 + } + "##; + let event = serde_json::from_str::(json).unwrap(); + match event.event { + EventType::ReactionAdded{..} => assert!(true), + _ => panic!("Did not deserialize into expected variant MessageMetadataUpdated") + } + } + + #[test] + fn deserializes_subteam_members_changed() { + let json = r##" + { + "token": "XXYYZZ", + "team_id": "TXXXXXXXX", + "api_app_id": "AXXXXXXXXX", + "event": { + "type": "subteam_members_changed", + "subteam_id": "S0614TZR7", + "team_id": "T060RNRCH", + "date_previous_update": 1446670362, + "date_update": 1492906952, + "added_users": [ + "U060RNRCZ", + "U060ULRC0", + "U061309JM" + ], + "added_users_count": "3", + "removed_users": [ + "U06129G2V" + ], + "removed_users_count": "1" + }, + "type": "event_callback", + "event_id": "EvXXXXXXXX", + "event_time": 1234567890 + } + "##; + let event = serde_json::from_str::(json).unwrap(); + match event.event { + EventType::SubteamMembersChanged{..} => assert!(true), + _ => panic!("Did not deserialize into expected variant SubteamMembersChanged") + } + } + + #[test] + fn deserializes_subteam_created() { + let json = r##" + { + "token": "XXYYZZ", + "team_id": "TXXXXXXXX", + "api_app_id": "AXXXXXXXXX", + "event": { + "type": "subteam_created", + "subteam": { + "id": "S0615G0KT", + "team_id": "T060RNRCH", + "is_usergroup": true, + "name": "Marketing Team", + "description": "Marketing gurus, PR experts and product advocates.", + "handle": "marketing-team", + "is_external": false, + "date_create": 1446746793, + "date_update": 1446746793, + "date_delete": 0, + "auto_type": null, + "created_by": "U060RNRCZ", + "updated_by": "U060RNRCZ", + "deleted_by": null, + "prefs": { + "channels": [ + + ], + "groups": [ + + ] + }, + "user_count": "0" + } + }, + "type": "event_callback", + "event_id": "EvXXXXXXXX", + "event_time": 1234567890 + } + "##; + let event = serde_json::from_str::(json).unwrap(); + match event.event { + EventType::SubteamCreated{..} => assert!(true), + _ => panic!("Did not deserialize into expected variant SubteamCreated") + } + } + + #[test] + fn deserializes_subteam_updated() { + let json = r##" + { + "token": "XXYYZZ", + "team_id": "TXXXXXXXX", + "api_app_id": "AXXXXXXXXX", + "event": { + "type": "subteam_updated", + "subteam": { + "id": "S516MPG9X", + "team_id": "T0GMXV71T", + "is_usergroup": true, + "is_subteam": true, + "name": "My User Group Test", + "description": "User Group Test", + "handle": "user-group-test", + "is_external": false, + "date_create": 1492655498, + "date_update": 1595814882, + "date_delete": 0, + "auto_type": null, + "auto_provision": false, + "enterprise_subteam_id": "", + "created_by": "U0GN26UBG", + "updated_by": "U0GN26UBG", + "deleted_by": null, + "prefs": { + "channels": [ + "CJG07GZDK" + ], + "groups": [] + }, + "users": [ + "U0GN26UBG", + "U0GNBSG1G", + "U0K0XMM2R", + "U1FLR7FB8", + "U3S7347ED", + "U5ZR5M0FM", + "U600XRXS9" + ], + "user_count": "7", + "channel_count": 0 + } + }, + "type": "event_callback", + "event_id": "EvXXXXXXXX", + "event_time": 1234567890 + } + "##; + let event = serde_json::from_str::(json).unwrap(); + match event.event { + EventType::SubteamUpdated{..} => assert!(true), + _ => panic!("Did not deserialize into expected variant SubteamUpdated") } } @@ -361,11 +1624,9 @@ mod test { }"##; let event = serde_json::from_str::(json).unwrap(); - match event { - Event::EventCallback(event_callback) => match event_callback.event { - EventCallbackType::Other => assert!(true, "true"), - _ => panic!("Event callback deserialize into incorrect variant"), - }, + match event.event { + EventType::Other => assert!(true, "true"), + _ => panic!("Event callback deserialize into incorrect variant"), } } } diff --git a/src/event_api/messages.rs b/src/event_api/messages.rs new file mode 100644 index 0000000..d269bc6 --- /dev/null +++ b/src/event_api/messages.rs @@ -0,0 +1,130 @@ +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; +use serde_json::Value; + +#[derive(Deserialize, Serialize, Debug, PartialEq)] +#[serde(tag = "subtype")] +pub enum MessageSubtype { + #[serde(rename = "message_updated")] + MessageUpdate, + #[serde(rename = "message_replied")] + MessageReplied { + #[serde(rename = "type", default = "MessageSubtype::default_type")] + _type: String, + channel: String, + event_ts: String, + hidden: bool, + message: MessageReply, + ts: String, + } +} + +impl MessageSubtype { + fn default_type() -> String { + "message".to_string() + } + +} + +#[derive(Deserialize, Serialize, Debug, PartialEq)] +#[skip_serializing_none] +pub struct MessageBasic { + pub channel: String, + pub channel_type: String, + pub edited: Option, + pub event_ts: String, + pub text: String, + pub thread_ts: Option, + pub ts: String, + pub user: String, +} + +// TODO - this could probably be merged with MessageBasic +#[derive(Deserialize, Serialize, Debug, PartialEq)] +pub struct MessageReply { + pub replies: Option>, + pub reply_count: u8, + pub text: String, + pub thread_ts: String, + pub ts: String, + pub user: String, +} + +#[derive(Deserialize, Serialize, Debug, PartialEq)] +#[skip_serializing_none] +pub struct MessageEdit { + pub user: String, + pub ts: String, +} + +#[derive(Deserialize, Serialize, Debug, PartialEq)] +#[skip_serializing_none] +pub struct MessageUpdate { + pub edited: MessageEdit, + pub subtype: String, + pub text: String, + pub ts: String, + pub user: String, +} + +#[derive(Deserialize, Serialize, Debug, PartialEq)] +/// Metadata Events are structured data payloads that contain information about +/// events occurring in your Slack-connected application, in the form of a +/// custom `event_payload` as part of a message's `metadata` property. +/// +/// See for more +/// +/// Note here that we use the `serde_json::Value` since the `event_payload` can +/// be dynamic (user-defined keys and values) which cannot be represented here +pub struct MessageMetadata { + pub event_type: String, + pub event_payload: Value, +} + +#[cfg(test)] +mod tests { + use crate::event_api::messages::*; + use serde_json::Value; + + #[test] + fn serializes_message_replied_correctly() { + let expected = r##" + { + "type": "message", + "message": { + "type": "message", + "user": "U1111111", + "text": "Was there was there was there what was there was there what was there was there there was there.", + "thread_ts": "1482960137.003543", + "reply_count": 1, + "replies": [ + { + "user": "U2222222", + "ts": "1483037603.017503" + } + ], + "ts": "1482960137.003543" + }, + "subtype": "message_replied", + "hidden": true, + "channel": "C12345678", + "event_ts": "1483037604.017506", + "ts": "1483037604.017506" + }"##; + + let deserialized = serde_json::from_str::(&expected).unwrap(); + // Reserialize this for later assertions + let serialized = serde_json::to_string(&deserialized).unwrap(); + match deserialized { + MessageSubtype::MessageReplied{message, _type, ..} => { + assert_eq!(message.user, "U1111111"); + assert_eq!(_type, "message".to_string()); + }, + _ => panic!("Event callback deserialize into incorrect variant"), + } + // Validate that reserialized JSON contains expected fields + let json: Value = serde_json::from_str(&serialized).unwrap(); + assert_eq!(json["subtype"], "message_replied".to_string()); + assert_eq!(json["type"], "message".to_string()); + } +} diff --git a/src/event_api/mod.rs b/src/event_api/mod.rs index b5a6d9d..2b2dc89 100644 --- a/src/event_api/mod.rs +++ b/src/event_api/mod.rs @@ -2,3 +2,4 @@ pub mod app; pub mod event; +pub mod messages; diff --git a/src/invites/invites.rs b/src/invites/invites.rs new file mode 100644 index 0000000..c159a17 --- /dev/null +++ b/src/invites/invites.rs @@ -0,0 +1,15 @@ +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; +use crate::team::teams::{Team}; +use crate::users::user::{User}; + +#[derive(Deserialize, Serialize, Debug, Default, PartialEq)] +pub struct Invite { + pub id: String, + pub date_created: u32, + pub date_invalid: u32, + pub inviting_team: Team, + pub inviting_user: User, + pub recipient_email: Option, + pub recipient_user_id: String, +} diff --git a/src/invites/mod.rs b/src/invites/mod.rs new file mode 100644 index 0000000..ad19db8 --- /dev/null +++ b/src/invites/mod.rs @@ -0,0 +1 @@ +pub mod invites; diff --git a/src/lib.rs b/src/lib.rs index c7b9915..4efc856 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -128,6 +128,7 @@ pub mod error; pub mod event_api; pub mod files; pub mod http_client; +pub mod invites; pub mod items; pub mod payloads; pub mod profiles; diff --git a/src/socket/event.rs b/src/socket/event.rs index a81535a..091a093 100644 --- a/src/socket/event.rs +++ b/src/socket/event.rs @@ -117,7 +117,7 @@ pub struct AcknowledgeMessage<'s> { #[cfg(test)] mod test { use super::*; - use crate::{event_api::event::EventCallbackType, payloads::interactive::InteractiveEventType}; + use crate::payloads::interactive::InteractiveEventType; #[test] fn deserialize_hello_event() { @@ -200,13 +200,9 @@ mod test { assert_eq!(envelope_id, "dbdd0ef3-1543-4f94-bfb4-133d0e6c1545"); assert!(!accepts_response_payload, "false"); - match payload { - Event::EventCallback(event_callback) => match event_callback.event { - EventCallbackType::AppHomeOpened { user, .. } => { - assert_eq!(user, "U061F7AUR"); - } - _ => panic!("Event callback deserialize into incorrect variant"), - }, + match payload.event { + crate::event_api::event::EventType::AppHomeOpened{user, ..} => assert_eq!(user, "U061F7AUR"), + _ => panic!("Event callback deserialize into incorrect variant"), } } _ => panic!("Event deserialize into incorrect variant"), diff --git a/src/socket/socket_mode.rs b/src/socket/socket_mode.rs index b0dd8f1..f9745f6 100644 --- a/src/socket/socket_mode.rs +++ b/src/socket/socket_mode.rs @@ -183,7 +183,7 @@ pub async fn connector_for_ca_file(ca_file_path: &str) -> Result match event_callback.event { - EventCallbackType::AppHomeOpened { user, .. } => { - assert_eq!(user, "U061F7AUR"); - } + Event{event, ..} => match event { + EventType::AppHomeOpened {user, .. } => assert_eq!(user, "U061F7AUR"), _ => panic!("Event callback deserialize into incorrect variant"), }, } diff --git a/src/team/info.rs b/src/team/info.rs index 33a0634..7b19719 100644 --- a/src/team/info.rs +++ b/src/team/info.rs @@ -67,6 +67,7 @@ mod test { team: Some(Team { id: Some("T12345".to_string()), name: Some("My Team".to_string()), + date_created: None, domain: Some("example".to_string()), email_domain: Some("example.com".to_string()), icon: Some(Icon { @@ -78,6 +79,7 @@ mod test { image_132: Some("https://...".to_string()), image_default: Some(true), }), + is_verified: None, enterprise_id: Some("E1234A12AB".to_string()), enterprise_name: Some("Umbrella Corporation".to_string()), }), @@ -150,6 +152,7 @@ mod test { team: Some(Team { id: Some("T12345".to_string()), name: Some("My Team".to_string()), + date_created: None, domain: Some("example".to_string()), email_domain: Some("example.com".to_string()), icon: Some(Icon { @@ -161,6 +164,7 @@ mod test { image_132: Some("https://...".to_string()), image_default: Some(true), }), + is_verified: None, enterprise_id: Some("E1234A12AB".to_string()), enterprise_name: Some("Umbrella Corporation".to_string()), }), diff --git a/src/team/mod.rs b/src/team/mod.rs index 76d9a58..fb927f0 100644 --- a/src/team/mod.rs +++ b/src/team/mod.rs @@ -7,4 +7,5 @@ pub mod integration_logs; pub mod log; pub mod preferences_list; pub mod profile_get; +pub mod subteams; pub mod teams; diff --git a/src/team/subteams.rs b/src/team/subteams.rs new file mode 100644 index 0000000..4bc583d --- /dev/null +++ b/src/team/subteams.rs @@ -0,0 +1,51 @@ +use serde::{Deserialize, Deserializer, Serialize}; +use serde_json::{Value}; + +#[derive(Deserialize, Serialize, Debug, PartialEq)] +pub struct Subteam { + pub auto_type: Value, + pub auto_provision: Option, + pub channel_count: Option, + pub created_by: Value, + pub date_create: u32, + pub date_delete: u32, + pub date_update: u32, + pub deleted_by: Value, + pub description: String, + pub enterprise_subteam_id: Option, + pub handle: String, + pub id: String, + pub is_external: bool, + pub is_usergroup: bool, + pub is_subteam: Option, + pub name: String, + pub prefs: SubteamPrefs, + pub team_id: String, + pub updated_by: Value, + pub users: Option>, + /// Note that the Slack API examples have this as an integer value encoded + /// as a string + #[serde(deserialize_with = "Subteam::make_int")] + pub user_count: u64, +} + +#[derive(Deserialize, Serialize, Debug, PartialEq)] +pub struct SubteamPrefs { + pub channels: Vec, + pub groups: Vec, +} + +impl Subteam { + /// Implement a custom deserializer to handle both string quoted integer values and unquoted + /// integer values + pub fn make_int<'de, D>(deserializer: D) -> Result + where D: Deserializer<'de>, + { + match serde::Deserialize::deserialize(deserializer)? { + Value::String(s) => s.parse().map_err(serde::de::Error::custom), + Value::Number(num) => num.as_u64().ok_or_else(|| serde::de::Error::custom(format!("Could not deserialize {} as number", num))), + _ => return Err(serde::de::Error::custom("invalid type")), + } + } +} + diff --git a/src/team/teams.rs b/src/team/teams.rs index 35a1bc2..33045b2 100644 --- a/src/team/teams.rs +++ b/src/team/teams.rs @@ -6,9 +6,11 @@ use serde_with::skip_serializing_none; pub struct Team { pub id: Option, pub name: Option, + pub date_created: Option, pub domain: Option, pub email_domain: Option, pub icon: Option, + pub is_verified: Option, pub enterprise_id: Option, pub enterprise_name: Option, } diff --git a/src/users/user.rs b/src/users/user.rs index 95aa3bf..c8b1221 100644 --- a/src/users/user.rs +++ b/src/users/user.rs @@ -4,6 +4,7 @@ use serde_with::skip_serializing_none; #[skip_serializing_none] #[derive(Deserialize, Serialize, Debug, Default, PartialEq)] pub struct UserProfile { + pub avatar_hash: Option, pub first_name: Option, pub last_name: Option, pub real_name: Option, @@ -19,7 +20,7 @@ pub struct UserProfile { pub image_72: Option, pub image_192: Option, pub image_512: Option, - pub mage_original: Option, + pub image_original: Option, pub title: Option, pub bot_id: Option, pub api_app_id: Option, @@ -38,6 +39,7 @@ pub struct User { pub team_id: Option, pub name: Option, pub deleted: Option, + pub email: Option, pub color: Option, pub real_name: Option, pub tz: Option,