From 5965a514fd83aef30a1fa37d5564008c07f5f6a7 Mon Sep 17 00:00:00 2001 From: Daniel Sockwell Date: Mon, 30 Mar 2020 18:54:00 -0400 Subject: [PATCH] Reorganize code, pt2 (#112) * Cleanup RedisMsg parsing [WIP] * Add tests to Redis parsing * WIP RedisMsg refactor Committing WIP before trying a different approach * WIP * Refactor RedisConn and Receiver * Finish second reorganization --- Cargo.toml | 2 +- benches/parse_redis.rs | 86 +- src/err.rs | 49 +- src/messages.rs | 1023 +++++++++-------- src/parse_client_request/mod.rs | 2 +- src/parse_client_request/subscription.rs | 207 ++-- src/redis_to_client_stream/client_agent.rs | 4 +- src/redis_to_client_stream/event_stream.rs | 172 ++- src/redis_to_client_stream/mod.rs | 13 +- src/redis_to_client_stream/receiver/mod.rs | 143 +-- src/redis_to_client_stream/redis/redis_cmd.rs | 6 +- .../redis/redis_connection.rs | 176 ++- src/redis_to_client_stream/redis/redis_msg.rs | 293 +++-- 13 files changed, 1187 insertions(+), 989 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0b5248e..45b984e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "flodgatt" description = "A blazingly fast drop-in replacement for the Mastodon streaming api server" -version = "0.6.5" +version = "0.6.6" authors = ["Daniel Long Sockwell "] edition = "2018" diff --git a/benches/parse_redis.rs b/benches/parse_redis.rs index c1fd987..6e0badf 100644 --- a/benches/parse_redis.rs +++ b/benches/parse_redis.rs @@ -1,60 +1,46 @@ -use criterion::black_box; -use criterion::criterion_group; -use criterion::criterion_main; -use criterion::Criterion; +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use flodgatt::{ + messages::Event, + parse_client_request::{Content::*, Reach::*, Stream::*, Timeline}, + redis_to_client_stream::redis_msg::{RedisMsg, RedisParseOutput}, +}; +use lru::LruCache; +use std::convert::TryFrom; -/// Parse using Flodgatt's current functions -mod flodgatt_parse_event { - use flodgatt::{ - messages::Event, - parse_client_request::Timeline, - redis_to_client_stream::{process_messages, MessageQueues, MsgQueue}, - }; - use lru::LruCache; - use std::collections::HashMap; - use uuid::Uuid; - - /// One-time setup, not included in testing time. - pub fn setup() -> (LruCache, MessageQueues, Uuid, Timeline) { - let mut cache: LruCache = LruCache::new(1000); - let mut queues_map = HashMap::new(); - let id = Uuid::default(); - let timeline = - Timeline::from_redis_raw_timeline("timeline:1", &mut cache, &None).expect("In test"); - queues_map.insert(id, MsgQueue::new(timeline)); - let queues = MessageQueues(queues_map); - (cache, queues, id, timeline) - } - - pub fn to_event_struct( - input: String, - mut cache: &mut LruCache, - mut queues: &mut MessageQueues, - id: Uuid, - timeline: Timeline, - ) -> Event { - process_messages(&input, &mut cache, &mut None, &mut queues); - queues - .oldest_msg_in_target_queue(id, timeline) - .expect("In test") +fn parse_long_redis_input<'a>(input: &'a str) -> RedisMsg<'a> { + if let RedisParseOutput::Msg(msg) = RedisParseOutput::try_from(input).unwrap() { + assert_eq!(msg.timeline_txt, "timeline:1"); + msg + } else { + panic!() } } +fn parse_to_timeline(msg: RedisMsg) -> Timeline { + let tl = Timeline::from_redis_text(msg.timeline_txt, &mut LruCache::new(1000), &None).unwrap(); + assert_eq!(tl, Timeline(User(1), Federated, All)); + tl +} + +fn parse_to_event(msg: RedisMsg) -> Event { + serde_json::from_str(msg.event_txt).unwrap() +} + fn criterion_benchmark(c: &mut Criterion) { - let input = ONE_MESSAGE_FOR_THE_USER_TIMLINE_FROM_REDIS.to_string(); + let input = ONE_MESSAGE_FOR_THE_USER_TIMLINE_FROM_REDIS; let mut group = c.benchmark_group("Parse redis RESP array"); - let (mut cache, mut queues, id, timeline) = flodgatt_parse_event::setup(); - group.bench_function("parse to Event using Flodgatt functions", |b| { - b.iter(|| { - black_box(flodgatt_parse_event::to_event_struct( - black_box(input.clone()), - black_box(&mut cache), - black_box(&mut queues), - black_box(id), - black_box(timeline), - )) - }) + group.bench_function("parse redis input to RedisMsg", |b| { + b.iter(|| black_box(parse_long_redis_input(input))) + }); + + let msg = parse_long_redis_input(input); + group.bench_function("parse RedisMsg to Timeline", |b| { + b.iter(|| black_box(parse_to_timeline(msg.clone()))) + }); + + group.bench_function("parse RedisMsg to Event", |b| { + b.iter(|| black_box(parse_to_event(msg.clone()))) }); } diff --git a/src/err.rs b/src/err.rs index 2863c5e..80dbdb0 100644 --- a/src/err.rs +++ b/src/err.rs @@ -1,6 +1,6 @@ -use std::fmt::Display; +use std::{error::Error, fmt}; -pub fn die_with_msg(msg: impl Display) -> ! { +pub fn die_with_msg(msg: impl fmt::Display) -> ! { eprintln!("FATAL ERROR: {}", msg); std::process::exit(1); } @@ -16,7 +16,50 @@ macro_rules! log_fatal { #[derive(Debug)] pub enum RedisParseErr { Incomplete, - Unrecoverable, + InvalidNumber(std::num::ParseIntError), + NonNumericInput, + InvalidLineStart(String), + InvalidLineEnd, + IncorrectRedisType, + MissingField, + UnsupportedTimeline, + UnsupportedEvent(serde_json::Error), +} + +impl fmt::Display for RedisParseErr { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "{}", match self { + Self::Incomplete => "The input from Redis does not form a complete message, likely because the input buffer filled partway through a message. Save this input and try again with additional input from Redis.".to_string(), + Self::InvalidNumber(e) => format!( "Redis input cannot be parsed: {}", e), + Self::NonNumericInput => "Received non-numeric input when expecting a Redis number".to_string(), + Self::InvalidLineStart(s) => format!("Got `{}` as a line start from Redis", s), + Self::InvalidLineEnd => "Redis input ended before promised length".to_string(), + Self::IncorrectRedisType => "Received a non-array when expecting a Redis array".to_string(), + Self::MissingField => "Redis input was missing a required field".to_string(), + Self::UnsupportedTimeline => "The raw timeline received from Redis could not be parsed into a supported timeline".to_string(), + Self::UnsupportedEvent(e) => format!("The event text from Redis could not be parsed into a valid event: {}", e) + }) + } +} + +impl Error for RedisParseErr {} + +impl From for RedisParseErr { + fn from(error: std::num::ParseIntError) -> Self { + Self::InvalidNumber(error) + } +} + +impl From for RedisParseErr { + fn from(error: serde_json::Error) -> Self { + Self::UnsupportedEvent(error) + } +} + +impl From for RedisParseErr { + fn from(_: TimelineErr) -> Self { + Self::UnsupportedTimeline + } } #[derive(Debug)] diff --git a/src/messages.rs b/src/messages.rs index a31cade..152d472 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -425,514 +425,515 @@ impl Status { } } -#[cfg(test)] -mod test { - use super::*; - use crate::{ - err::RedisParseErr, - parse_client_request::{Content::*, Reach::*, Stream::*, Timeline}, - redis_to_client_stream::{MessageQueues, MsgQueue, RedisMsg}, - }; - use lru::LruCache; - use std::collections::HashMap; - use uuid::Uuid; - type Err = RedisParseErr; - - /// Set up state shared between multiple tests of Redis parsing - pub fn shared_setup() -> (LruCache, MessageQueues, Uuid, Timeline) { - let mut cache: LruCache = LruCache::new(1000); - let mut queues_map = HashMap::new(); - let id = dbg!(Uuid::default()); - - let timeline = dbg!( - Timeline::from_redis_raw_timeline("timeline:4", &mut cache, &None).expect("In test") - ); - queues_map.insert(id, MsgQueue::new(timeline)); - let queues = MessageQueues(queues_map); - (cache, queues, id, timeline) - } - - #[test] - fn accurately_parse_redis_output_into_event() -> Result<(), Err> { - let input ="*3\r\n$7\r\nmessage\r\n$10\r\ntimeline:4\r\n$1386\r\n{\"event\":\"update\",\"payload\":{\"id\":\"102866835379605039\",\"created_at\":\"2019-09-27T22:29:02.590Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"http://localhost:3000/users/admin/statuses/102866835379605039\",\"url\":\"http://localhost:3000/@admin/102866835379605039\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"content\":\"

@susan hi

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"1\",\"username\":\"admin\",\"acct\":\"admin\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"created_at\":\"2019-07-04T00:21:05.890Z\",\"note\":\"

\",\"url\":\"http://localhost:3000/@admin\",\"avatar\":\"http://localhost:3000/avatars/original/missing.png\",\"avatar_static\":\"http://localhost:3000/avatars/original/missing.png\",\"header\":\"http://localhost:3000/headers/original/missing.png\",\"header_static\":\"http://localhost:3000/headers/original/missing.png\",\"followers_count\":3,\"following_count\":3,\"statuses_count\":192,\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[{\"id\":\"4\",\"username\":\"susan\",\"url\":\"http://localhost:3000/@susan\",\"acct\":\"susan\"}],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null},\"queued_at\":1569623342825}\r\n"; - - let (mut cache, mut queues, id, timeline) = shared_setup(); - crate::redis_to_client_stream::process_messages(input, &mut cache, &mut None, &mut queues); - - let parsed_event = queues.oldest_msg_in_target_queue(id, timeline).unwrap(); - let test_event = Event::Update{ payload: Status { - id: "102866835379605039".to_string(), - created_at: "2019-09-27T22:29:02.590Z".to_string(), - in_reply_to_id: None, - in_reply_to_account_id: None, - sensitive: false, - spoiler_text: "".to_string(), - visibility: Visibility::Public, - language: Some("en".to_string()), - uri: "http://localhost:3000/users/admin/statuses/102866835379605039".to_string(), - url: Some("http://localhost:3000/@admin/102866835379605039".to_string()), - replies_count: 0, - reblogs_count: 0, - favourites_count: 0, - favourited: Some(false), - reblogged: Some(false), - muted: Some(false), - bookmarked: None, - pinned: None, - content: "

@susan hi

".to_string(), - reblog: None, - application: Some(Application { - name: "Web".to_string(), - website: None, - vapid_key: None, - client_id: None, - client_secret: None, - }), - account: Account { - id: "1".to_string(), - username: "admin".to_string(), - acct: "admin".to_string(), - display_name: "".to_string(), - locked:false, - bot:Some(false), - created_at: "2019-07-04T00:21:05.890Z".to_string(), - note:"

".to_string(), - url:"http://localhost:3000/@admin".to_string(), - avatar: "http://localhost:3000/avatars/original/missing.png".to_string(), - avatar_static:"http://localhost:3000/avatars/original/missing.png".to_string(), - header: "http://localhost:3000/headers/original/missing.png".to_string(), - header_static:"http://localhost:3000/headers/original/missing.png".to_string(), - followers_count:3, - following_count:3, - statuses_count:192, - emojis:vec![], - fields:Some(vec![]), - moved: None, - group: None, - last_status_at: None, - discoverable: None, - source: None, - }, - media_attachments:vec![], - mentions: vec![ Mention {id:"4".to_string(), - username:"susan".to_string(), - url:"http://localhost:3000/@susan".to_string(), - acct:"susan".to_string()}], - tags:vec![], - emojis:vec![], - card:None,poll:None, - text: None, - }, - queued_at: Some(1569623342825)}; - - assert_eq!(parsed_event, test_event); - Ok(()) - } - - #[test] - fn parse_redis_input_subscription_msgs_and_update() -> Result<(), Err> { - let input = "*3\r\n$9\r\nsubscribe\r\n$11\r\ntimeline:56\r\n:1\r\n*3\r\n$9\r\nsubscribe\r\n$12\r\ntimeline:308\r\n:2\r\n*3\r\n$9\r\nsubscribe\r\n$21\r\ntimeline:hashtag:test\r\n:3\r\n*3\r\n$9\r\nsubscribe\r\n$21\r\ntimeline:public:local\r\n:4\r\n*3\r\n$9\r\nsubscribe\r\n$11\r\ntimeline:55\r\n:5\r\n*3\r\n$7\r\nmessage\r\n$21\r\ntimeline:public:local\r\n$1249\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103881102123251272\",\"created_at\":\"2020-03-25T01:30:24.914Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/bob/statuses/103881102123251272\",\"url\":\"https://instance.codesections.com/@bob/103881102123251272\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"content\":\"

0111

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"55\",\"username\":\"bob\",\"acct\":\"bob\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T03:03:53.068Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@bob\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":1,\"statuses_count\":57,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null}}\r\n*3\r\n$7\r\nmessage\r\n$11\r\ntimeline:55\r\n$1360\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103881102123251272\",\"created_at\":\"2020-03-25T01:30:24.914Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/bob/statuses/103881102123251272\",\"url\":\"https://instance.codesections.com/@bob/103881102123251272\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"bookmarked\":false,\"pinned\":false,\"content\":\"

0111

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"55\",\"username\":\"bob\",\"acct\":\"bob\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T03:03:53.068Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@bob\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":1,\"statuses_count\":57,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null},\"queued_at\":1585099825263}\r\n*3\r\n$7\r\nmessage\r\n$21\r\ntimeline:public:local\r\n$1249\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103881103451006570\",\"created_at\":\"2020-03-25T01:30:45.152Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/bob/statuses/103881103451006570\",\"url\":\"https://instance.codesections.com/@bob/103881103451006570\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"content\":\"

1000

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"55\",\"username\":\"bob\",\"acct\":\"bob\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T03:03:53.068Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@bob\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":1,\"statuses_count\":58,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null}}\r\n*3\r\n$7\r\nmessage\r\n$11\r\ntimeline:55\r\n$1360\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103881103451006570\",\"created_at\":\"2020-03-25T01:30:45.152Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/bob/statuses/103881103451006570\",\"url\":\"https://instance.codesections.com/@bob/103881103451006570\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"bookmarked\":false,\"pinned\":false,\"content\":\"

1000

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"55\",\"username\":\"bob\",\"acct\":\"bob\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T03:03:53.068Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@bob\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":1,\"statuses_count\":58,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null},\"queued_at\":1585099845405}\r\n"; - - let (mut cache, _, _, _) = shared_setup(); - - let (subscription_msg1, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; - assert!(matches!(subscription_msg1, RedisMsg::SubscriptionMsg)); - - let (subscription_msg2, rest) = RedisMsg::from_raw(rest, &mut cache, &None)?; - assert!(matches!(subscription_msg2, RedisMsg::SubscriptionMsg)); - - let (subscription_msg3, rest) = RedisMsg::from_raw(rest, &mut cache, &None)?; - assert!(matches!(subscription_msg3, RedisMsg::SubscriptionMsg)); - - let (subscription_msg4, rest) = RedisMsg::from_raw(rest, &mut cache, &None)?; - assert!(matches!(subscription_msg4, RedisMsg::SubscriptionMsg)); - - let (subscription_msg5, rest) = RedisMsg::from_raw(rest, &mut cache, &None)?; - assert!(matches!(subscription_msg5, RedisMsg::SubscriptionMsg)); - - let (update_msg1, rest) = RedisMsg::from_raw(rest, &mut cache, &None)?; - assert!(matches!( - update_msg1, - RedisMsg::EventMsg(_, Event::Update { .. }) - )); - - let (update_msg2, rest) = RedisMsg::from_raw(rest, &mut cache, &None)?; - assert!(matches!( - update_msg2, - RedisMsg::EventMsg(_, Event::Update { .. }) - )); - - let (update_msg3, rest) = RedisMsg::from_raw(rest, &mut cache, &None)?; - assert!(matches!( - update_msg3, - RedisMsg::EventMsg(_, Event::Update { .. }) - )); - - let (update_msg4, rest) = RedisMsg::from_raw(rest, &mut cache, &None)?; - assert!(matches!( - update_msg4, - RedisMsg::EventMsg(_, Event::Update { .. }) - )); - - assert_eq!(rest, "".to_string()); - - Ok(()) - } - - #[test] - fn parse_redis_input_notification() -> Result<(), Err> { - let input = "*3\r\n$7\r\nmessage\r\n$11\r\ntimeline:55\r\n$2311\r\n{\"event\":\"notification\",\"payload\":{\"id\":\"147\",\"type\":\"mention\",\"created_at\":\"2020-03-25T14:25:09.295Z\",\"account\":{\"id\":\"308\",\"username\":\"ralph\",\"acct\":\"ralph\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T19:55:20.933Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@ralph\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":2,\"statuses_count\":100,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"status\":{\"id\":\"103884148503208016\",\"created_at\":\"2020-03-25T14:25:08.995Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/ralph/statuses/103884148503208016\",\"url\":\"https://instance.codesections.com/@ralph/103884148503208016\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"bookmarked\":false,\"content\":\"

@bob notification test

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"308\",\"username\":\"ralph\",\"acct\":\"ralph\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T19:55:20.933Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@ralph\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":2,\"statuses_count\":100,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[{\"id\":\"55\",\"username\":\"bob\",\"url\":\"https://instance.codesections.com/@bob\",\"acct\":\"bob\"}],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null}}}\r\n"; - - let (mut cache, _, _, _) = shared_setup(); - - let (subscription_msg1, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; - assert!(matches!( - subscription_msg1, - RedisMsg::EventMsg(Timeline(User(id), Federated, All), Event::Notification { .. }) if id == 55 - )); - - assert_eq!(rest, "".to_string()); - - Ok(()) - } - - #[test] - fn parse_redis_input_delete() -> Result<(), Err> { - let input = "*3\r\n$7\r\nmessage\r\n$12\r\ntimeline:308\r\n$49\r\n{\"event\":\"delete\",\"payload\":\"103864778284581232\"}\r\n"; - - let (mut cache, _, _, _) = dbg!(shared_setup()); - - let (subscription_msg1, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; - assert!(matches!( - subscription_msg1, - RedisMsg::EventMsg( - Timeline(User(308), Federated, All), - Event::Delete { payload: DeletedId(id) } - ) if id == "103864778284581232".to_string() - )); - - assert_eq!(rest, "".to_string()); - - Ok(()) - } - - #[test] - fn parse_redis_input_filters_changed() -> Result<(), Err> { - let input = "*3\r\n$7\r\nmessage\r\n$11\r\ntimeline:56\r\n$27\r\n{\"event\":\"filters_changed\"}\r\n"; - - let (mut cache, _, _, _) = shared_setup(); - - let (subscription_msg1, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; - assert!(matches!( - subscription_msg1, - RedisMsg::EventMsg(Timeline(User(id), Federated, All), Event::FiltersChanged) if id == 56 - )); - - assert_eq!(rest, "".to_string()); - - Ok(()) - } - - #[test] - fn parse_redis_input_announcement() -> Result<(), Err> { - let input = "*3\r\n$7\r\nmessage\r\n$12\r\ntimeline:308\r\n$293\r\n{\"event\":\"announcement\",\"payload\":{\"id\":\"2\",\"content\":\"

Test announcement 0010

\",\"starts_at\":null,\"ends_at\":null,\"all_day\":false,\"published_at\":\"2020-03-25T14:57:57.550Z\",\"updated_at\":\"2020-03-25T14:57:57.566Z\",\"mentions\":[],\"tags\":[],\"emojis\":[],\"reactions\":[{\"name\":\"đź‘Ť\",\"count\":2}]}}\r\n"; - - let (mut cache, _, _, _) = shared_setup(); - - let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; - assert!(matches!( - msg, - RedisMsg::EventMsg( - Timeline(User(id), Federated, All), - Event::Announcement { .. }) if id == 308 - )); - - assert_eq!(rest, "".to_string()); - Ok(()) - } - - #[test] - fn parse_redis_input_announcement_reaction() -> Result<(), Err> { - let input = "*3\r\n$7\r\nmessage\r\n$12\r\ntimeline:308\r\n$91\r\n{\"event\":\"announcement.reaction\",\"payload\":{\"name\":\"đź‘˝\",\"count\":2,\"announcement_id\":\"8\"}}\r\n"; - - let (mut cache, _, _, _) = shared_setup(); - - let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; - assert!(matches!( - msg, - RedisMsg::EventMsg( - Timeline(User(id), Federated, All), - Event::AnnouncementReaction{ .. } - ) if id == 308 - )); - - assert_eq!(rest, "".to_string()); - Ok(()) - } - - #[test] - fn parse_redis_input_announcement_delete() -> Result<(), Err> { - let input = "*3\r\n$7\r\nmessage\r\n$12\r\ntimeline:308\r\n$45\r\n{\"event\":\"announcement.delete\",\"payload\":\"5\"}\r\n"; - - let (mut cache, _, _, _) = shared_setup(); - - let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; - assert!(matches!( - msg, - RedisMsg::EventMsg( - Timeline(User(id), Federated, All), - Event::AnnouncementDelete{ - payload: DeletedId(del_id), - - } - ) if id == 308 && del_id == "5".to_string() - )); - - assert_eq!(rest, "".to_string()); - Ok(()) - } - - #[test] - fn parse_redis_input_status_with_attachments() -> Result<(), Err> { - let input = "*3\r\n$7\r\nmessage\r\n$12\r\ntimeline:308\r\n$2049\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103884996729070829\",\"created_at\":\"2020-03-25T18:00:52.026Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/ralph/statuses/103884996729070829\",\"url\":\"https://instance.codesections.com/@ralph/103884996729070829\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"bookmarked\":false,\"pinned\":false,\"content\":\"

Test with media attachment

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"308\",\"username\":\"ralph\",\"acct\":\"ralph\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T19:55:20.933Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@ralph\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":2,\"statuses_count\":103,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[{\"id\":\"3102\",\"type\":\"image\",\"url\":\"https://instance.codesections.com/system/media_attachments/files/000/003/102/original/1753cf5b8edd544a.jpg?1585159208\",\"preview_url\":\"https://instance.codesections.com/system/media_attachments/files/000/003/102/small/1753cf5b8edd544a.jpg?1585159208\",\"remote_url\":null,\"text_url\":\"https://instance.codesections.com/media/7XPfdkmAIHb3TQcLYII\",\"meta\":{\"original\":{\"width\":828,\"height\":340,\"size\":\"828x340\",\"aspect\":2.4352941176470586},\"small\":{\"width\":623,\"height\":256,\"size\":\"623x256\",\"aspect\":2.43359375},\"focus\":{\"x\":0.0,\"y\":0.0}},\"description\":\"Test image discription\",\"blurhash\":\"UBR{.4M{s;IU0JkBWBWB9bM{ofxu4^WAWBj[\"}],\"mentions\":[],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null},\"queued_at\":1585159252656}\r\n"; - - let (mut cache, _, _, _) = shared_setup(); - - let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; - dbg!(&msg); - assert!(matches!( - msg, - RedisMsg::EventMsg( - Timeline(User(308), Federated, All), - Event::Update{ payload: Status { media_attachments: attachments, .. }, .. } - ) if attachments.len() > 0 - )); - - assert_eq!(rest, "".to_string()); - Ok(()) - } - - #[test] - fn parse_redis_input_status_with_mentions() -> Result<(), Err> { - let input = "*3\r\n$7\r\nmessage\r\n$12\r\ntimeline:308\r\n$2094\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103885034181231245\",\"created_at\":\"2020-03-25T18:10:23.420Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/ralph/statuses/103885034181231245\",\"url\":\"https://instance.codesections.com/@ralph/103885034181231245\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"bookmarked\":false,\"pinned\":false,\"content\":\"

@bob @susan @codesections

Test with mentions

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"308\",\"username\":\"ralph\",\"acct\":\"ralph\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T19:55:20.933Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@ralph\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":2,\"statuses_count\":104,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[{\"id\":\"55\",\"username\":\"bob\",\"url\":\"https://instance.codesections.com/@bob\",\"acct\":\"bob\"},{\"id\":\"56\",\"username\":\"susan\",\"url\":\"https://instance.codesections.com/@susan\",\"acct\":\"susan\"},{\"id\":\"9\",\"username\":\"codesections\",\"url\":\"https://instance.codesections.com/@codesections\",\"acct\":\"codesections\"}],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null},\"queued_at\":1585159824540}\r\n"; - - let (mut cache, _, _, _) = shared_setup(); - - let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; - dbg!(&msg); - assert!(matches!( - msg, - RedisMsg::EventMsg( - Timeline(User(308), Federated, All), - Event::Update{ payload: Status { mentions, .. }, .. } - ) if mentions.len() > 0 - )); - - assert_eq!(rest, "".to_string()); - Ok(()) - } - - #[test] - fn parse_redis_input_status_with_tags() -> Result<(), Err> { - let input = "*3\r\n$7\r\nmessage\r\n$12\r\ntimeline:308\r\n$1770\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103885047114641861\",\"created_at\":\"2020-03-25T18:13:40.741Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/ralph/statuses/103885047114641861\",\"url\":\"https://instance.codesections.com/@ralph/103885047114641861\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"bookmarked\":false,\"pinned\":false,\"content\":\"

#test #hashtag

Test with tags

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"308\",\"username\":\"ralph\",\"acct\":\"ralph\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T19:55:20.933Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@ralph\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":2,\"statuses_count\":105,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[],\"tags\":[{\"name\":\"hashtag\",\"url\":\"https://instance.codesections.com/tags/hashtag\"},{\"name\":\"test\",\"url\":\"https://instance.codesections.com/tags/test\"}],\"emojis\":[],\"card\":null,\"poll\":null},\"queued_at\":1585160021281}\r\n"; - - let (mut cache, _, _, _) = shared_setup(); - - let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; - dbg!(&msg); - assert!(matches!( - msg, - RedisMsg::EventMsg( - Timeline(User(308), Federated, All), - Event::Update{ payload: Status { tags, .. }, .. } - ) if tags.len() > 0 - )); - - assert_eq!(rest, "".to_string()); - Ok(()) - } - - #[test] - fn parse_redis_input_status_with_emojis() -> Result<(), Err> { - let input = "*3\r\n$7\r\nmessage\r\n$12\r\ntimeline:308\r\n$1703\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103885068078872546\",\"created_at\":\"2020-03-25T18:19:00.620Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/ralph/statuses/103885068078872546\",\"url\":\"https://instance.codesections.com/@ralph/103885068078872546\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"bookmarked\":false,\"pinned\":false,\"content\":\"

Test with custom emoji

:patcat:

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"308\",\"username\":\"ralph\",\"acct\":\"ralph\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T19:55:20.933Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@ralph\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":2,\"statuses_count\":106,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[],\"tags\":[],\"emojis\":[{\"shortcode\":\"patcat\",\"url\":\"https://instance.codesections.com/system/custom_emojis/images/000/001/071/original/d87fcdf79ed6fe20.png?1585160295\",\"static_url\":\"https://instance.codesections.com/system/custom_emojis/images/000/001/071/static/d87fcdf79ed6fe20.png?1585160295\",\"visible_in_picker\":true}],\"card\":null,\"poll\":null},\"queued_at\":1585160340991}\r\n"; - - let (mut cache, _, _, _) = shared_setup(); - - let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; - dbg!(&msg); - assert!(matches!( - msg, - RedisMsg::EventMsg( - Timeline(User(308), Federated, All), - Event::Update{ payload: Status { emojis, .. }, .. } - ) if emojis.len() > 0 - )); - - assert_eq!(rest, "".to_string()); - Ok(()) - } - - #[test] - fn parse_redis_input_status_is_reply() -> Result<(), Err> { - let input = "*3\r\n$7\r\nmessage\r\n$12\r\ntimeline:308\r\n$1612\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103885083636011552\",\"created_at\":\"2020-03-25T18:22:57.963Z\",\"in_reply_to_id\":\"103881103451006570\",\"in_reply_to_account_id\":\"55\",\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/ralph/statuses/103885083636011552\",\"url\":\"https://instance.codesections.com/@ralph/103885083636011552\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"bookmarked\":false,\"pinned\":false,\"content\":\"

@bob Test is reply

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"308\",\"username\":\"ralph\",\"acct\":\"ralph\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T19:55:20.933Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@ralph\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":2,\"statuses_count\":107,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[{\"id\":\"55\",\"username\":\"bob\",\"url\":\"https://instance.codesections.com/@bob\",\"acct\":\"bob\"}],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null},\"queued_at\":1585160578486}\r\n*3\r\n$7\r\nmessage\r\n$11\r\ntimeline:55\r\n$2323\r\n{\"event\":\"notification\",\"payload\":{\"id\":\"156\",\"type\":\"mention\",\"created_at\":\"2020-03-25T18:22:58.293Z\",\"account\":{\"id\":\"308\",\"username\":\"ralph\",\"acct\":\"ralph\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T19:55:20.933Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@ralph\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":2,\"statuses_count\":107,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"status\":{\"id\":\"103885083636011552\",\"created_at\":\"2020-03-25T18:22:57.963Z\",\"in_reply_to_id\":\"103881103451006570\",\"in_reply_to_account_id\":\"55\",\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/ralph/statuses/103885083636011552\",\"url\":\"https://instance.codesections.com/@ralph/103885083636011552\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"bookmarked\":false,\"content\":\"

@bob Test is reply

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"308\",\"username\":\"ralph\",\"acct\":\"ralph\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T19:55:20.933Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@ralph\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":2,\"statuses_count\":107,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[{\"id\":\"55\",\"username\":\"bob\",\"url\":\"https://instance.codesections.com/@bob\",\"acct\":\"bob\"}],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null}}}\r\n"; - - let (mut cache, _, _, _) = shared_setup(); - - let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; - dbg!(&msg); - assert!(matches!( - msg, - RedisMsg::EventMsg( - Timeline(User(308), Federated, All), - Event::Update { - payload: - Status { - in_reply_to_id: Some(_), - .. - }, - .. - }, - ) - )); - let (msg2, rest) = RedisMsg::from_raw(rest, &mut cache, &None)?; - dbg!(&msg2); - assert!(matches!( - msg2, - RedisMsg::EventMsg(Timeline(User(55), Federated, All), Event::Notification { .. }) - )); - - assert_eq!(rest, "".to_string()); - Ok(()) - } - - #[test] - fn parse_redis_input_status_is_reblog() -> Result<(), Err> { - let input = "*3\r\n$7\r\nmessage\r\n$12\r\ntimeline:308\r\n$2778\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103885156768039822\",\"created_at\":\"2020-03-25T18:41:33.859Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":null,\"uri\":\"https://instance.codesections.com/users/ralph/statuses/103885156768039822/activity\",\"url\":\"https://instance.codesections.com/users/ralph/statuses/103885156768039822/activity\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":true,\"muted\":false,\"bookmarked\":false,\"content\":\"

RT @bob 0010

\",\"reblog\":{\"id\":\"103881061540314589\",\"created_at\":\"2020-03-25T01:20:05.648Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/bob/statuses/103881061540314589\",\"url\":\"https://instance.codesections.com/@bob/103881061540314589\",\"replies_count\":0,\"reblogs_count\":1,\"favourites_count\":0,\"favourited\":false,\"reblogged\":true,\"muted\":false,\"bookmarked\":false,\"content\":\"

0010

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"55\",\"username\":\"bob\",\"acct\":\"bob\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T03:03:53.068Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@bob\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":1,\"statuses_count\":58,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null},\"application\":null,\"account\":{\"id\":\"308\",\"username\":\"ralph\",\"acct\":\"ralph\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T19:55:20.933Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@ralph\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":2,\"statuses_count\":110,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null},\"queued_at\":1585161694429}\r\n"; - - let (mut cache, _, _, _) = shared_setup(); - - let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; - dbg!(&msg); - assert!(matches!( - msg, - RedisMsg::EventMsg( - Timeline(User(308), Federated, All), - Event::Update { - payload: - Status { - reblogged: Some(t), .. - }, - .. - }, - ) if t - )); - - assert_eq!(rest, "".to_string()); - Ok(()) - } - - #[test] - fn parse_redis_input_status_with_poll() -> Result<(), Err> { - let input = "*3\r\n$7\r\nmessage\r\n$12\r\ntimeline:308\r\n$1663\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103885228849512739\",\"created_at\":\"2020-03-25T18:59:53.788Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/ralph/statuses/103885228849512739\",\"url\":\"https://instance.codesections.com/@ralph/103885228849512739\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"bookmarked\":false,\"pinned\":false,\"content\":\"

test poll:

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"308\",\"username\":\"ralph\",\"acct\":\"ralph\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T19:55:20.933Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@ralph\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":2,\"statuses_count\":112,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":{\"id\":\"46\",\"expires_at\":\"2020-03-26T18:59:53.747Z\",\"expired\":false,\"multiple\":false,\"votes_count\":0,\"voters_count\":0,\"voted\":true,\"own_votes\":[],\"options\":[{\"title\":\"1\",\"votes_count\":0},{\"title\":\"2\",\"votes_count\":0},{\"title\":\"3\",\"votes_count\":0},{\"title\":\"4\",\"votes_count\":0}],\"emojis\":[]}},\"queued_at\":1585162794362}\r\n"; - - let (mut cache, _, _, _) = shared_setup(); - - let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; - dbg!(&msg); - assert!(matches!( - msg, - RedisMsg::EventMsg( - Timeline(User(308), Federated, All), - Event::Update { - payload: Status { poll: Some(_), .. }, - .. - }, - ) - )); - - assert_eq!(rest, "".to_string()); - Ok(()) - } - - #[test] - fn parse_redis_input_status_with_preview_card() -> Result<(), Err> { - let input = "*3\r\n$7\r\nmessage\r\n$11\r\ntimeline:55\r\n$2256\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103885300935387207\",\"created_at\":\"2020-03-25T19:18:13.753Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/ralph/statuses/103885300935387207\",\"url\":\"https://instance.codesections.com/@ralph/103885300935387207\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"bookmarked\":false,\"content\":\"

Test with preview card:

https://www.codesections.com/blog/mastodon-elevator-pitch/

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"308\",\"username\":\"ralph\",\"acct\":\"ralph\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T19:55:20.933Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@ralph\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":2,\"following_count\":2,\"statuses_count\":120,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[],\"tags\":[],\"emojis\":[],\"card\":{\"url\":\"https://www.codesections.com/blog/mastodon-elevator-pitch/\",\"title\":\" Mastodon Is Better than Twitter: Elevator Pitch | CodeSections\",\"description\":\"The personal website and blog of Daniel Long Sockwell, a lawyer-turned-programmer with an interest in web development, open source, and making things as simple as possible.\",\"type\":\"link\",\"author_name\":\"\",\"author_url\":\"\",\"provider_name\":\"\",\"provider_url\":\"\",\"html\":\"\",\"width\":400,\"height\":400,\"image\":\"https://instance.codesections.com/system/preview_cards/images/000/000/002/original/f6e89baa729668e7.png?1585163010\",\"embed_url\":\"\"},\"poll\":null},\"queued_at\":1585163894281}\r\n"; - - let (mut cache, _, _, _) = shared_setup(); - - let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; - dbg!(&msg); - assert!(matches!( - msg, - RedisMsg::EventMsg( - Timeline(User(55), Federated, All), - Event::Update { - payload: Status { card: Some(_), .. }, - .. - }, - ) - )); - - assert_eq!(rest, "".to_string()); - Ok(()) - } - - #[test] - fn parse_redis_input_conversation() -> Result<(), Err> { - let input = "*3\r\n$7\r\nmessage\r\n$17\r\ntimeline:direct:9\r\n$2442\r\n{\"event\":\"conversation\",\"payload\":{\"id\":\"22\",\"unread\":false,\"accounts\":[{\"id\":\"55\",\"username\":\"bob\",\"acct\":\"bob\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T03:03:53.068Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@bob\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":1,\"statuses_count\":58,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]}],\"last_status\":{\"id\":\"103884351200485419\",\"created_at\":\"2020-03-25T15:16:41.915Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"direct\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/codesections/statuses/103884351200485419\",\"url\":\"https://instance.codesections.com/@codesections/103884351200485419\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"bookmarked\":false,\"content\":\"

@bob Test Conversation

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"9\",\"username\":\"codesections\",\"acct\":\"codesections\",\"display_name\":\"TEST ACCOUT for codesections\",\"locked\":false,\"bot\":false,\"discoverable\":false,\"group\":false,\"created_at\":\"2020-03-11T01:17:13.412Z\",\"note\":\"

Used in the testing and development of flodgatt, the WIP streaming server for Mastodon

\",\"url\":\"https://instance.codesections.com/@codesections\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":79,\"following_count\":97,\"statuses_count\":7,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[{\"id\":\"55\",\"username\":\"bob\",\"url\":\"https://instance.codesections.com/@bob\",\"acct\":\"bob\"}],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null}},\"queued_at\":1585149402344}\r\n"; - - let (mut cache, _, _, _) = shared_setup(); - - let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; - dbg!(&msg); - assert!(matches!( - msg, - RedisMsg::EventMsg( - Timeline(Direct(id), Federated, All), - Event::Conversation{ ..} - ) if id == 9 - )); - - assert_eq!(rest, "".to_string()); - Ok(()) - } - - #[test] - fn parse_redis_input_from_live_data_1() -> Result<(), Err> { - let input = "*3\r\n$7\r\nmessage\r\n$15\r\ntimeline:public\r\n$2799\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103880088450458596\",\"created_at\":\"2020-03-24T21:12:37.000Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"es\",\"uri\":\"https://mastodon.social/users/durru/statuses/103880088436492032\",\"url\":\"https://mastodon.social/@durru/103880088436492032\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"content\":\"

¡No puedes salir, loca!

\",\"reblog\":null,\"account\":{\"id\":\"2271\",\"username\":\"durru\",\"acct\":\"durru@mastodon.social\",\"display_name\":\"Cloaca Maxima\",\"locked\":false,\"bot\":false,\"discoverable\":true,\"group\":false,\"created_at\":\"2020-03-24T21:27:31.669Z\",\"note\":\"

Todo pasa, antes o después, por la Cloaca, diría Vitruvio.
También compongo palíndromos.

\",\"url\":\"https://mastodon.social/@durru\",\"avatar\":\"https://instance.codesections.com/system/accounts/avatars/000/002/271/original/d7675a6ff9d9baa7.jpeg?1585085250\",\"avatar_static\":\"https://instance.codesections.com/system/accounts/avatars/000/002/271/original/d7675a6ff9d9baa7.jpeg?1585085250\",\"header\":\"https://instance.codesections.com/system/accounts/headers/000/002/271/original/e3f0a1989b0d8efc.jpeg?1585085250\",\"header_static\":\"https://instance.codesections.com/system/accounts/headers/000/002/271/original/e3f0a1989b0d8efc.jpeg?1585085250\",\"followers_count\":222,\"following_count\":81,\"statuses_count\":5443,\"last_status_at\":\"2020-03-24\",\"emojis\":[],\"fields\":[{\"name\":\"Mis fotos\",\"value\":\"https://pixelfed.de/durru\",\"verified_at\":null},{\"name\":\"diaspora*\",\"value\":\"https://joindiaspora.com/people/75fec0e05114013484870242ac110007\",\"verified_at\":null}]},\"media_attachments\":[{\"id\":\"2864\",\"type\":\"image\",\"url\":\"https://instance.codesections.com/system/media_attachments/files/000/002/864/original/3988312d30936494.jpeg?1585085251\",\"preview_url\":\"https://instance.codesections.com/system/media_attachments/files/000/002/864/small/3988312d30936494.jpeg?1585085251\",\"remote_url\":\"https://files.mastodon.social/media_attachments/files/026/669/690/original/d8171331f956cf38.jpg\",\"text_url\":null,\"meta\":{\"original\":{\"width\":1001,\"height\":662,\"size\":\"1001x662\",\"aspect\":1.512084592145015},\"small\":{\"width\":491,\"height\":325,\"size\":\"491x325\",\"aspect\":1.5107692307692309}},\"description\":null,\"blurhash\":\"UdLqhI4n4TIUIAt7t7ay~qIojtRj?bM{M{of\"}],\"mentions\":[],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null}}\r\n"; - let (mut cache, _, _, _) = shared_setup(); - let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; - assert!(matches!( - msg, - RedisMsg::EventMsg(Timeline(Public, Federated, All), Event::Update { .. }) - )); - assert_eq!(rest, String::new()); - Ok(()) - } - - #[test] - fn parse_redis_input_from_live_data_2() -> Result<(), Err> { - let input = "*3\r\n$7\r\nmessage\r\n$15\r\ntimeline:public\r\n$3888\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103880373579328660\",\"created_at\":\"2020-03-24T22:25:05.000Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://newsbots.eu/users/granma/statuses/103880373417385978\",\"url\":\"https://newsbots.eu/@granma/103880373417385978\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"content\":\"

A total of 11 measures have been established for the pre-epidemic stage of the battle against #Covid-19 in #Cuba
#CubaPorLaSalud
http://en.granma.cu/cuba/2020-03-23/public-health-measures-in-covid-19-pre-epidemic-stage 

\",\"reblog\":null,\"account\":{\"id\":\"717\",\"username\":\"granma\",\"acct\":\"granma@newsbots.eu\",\"display_name\":\"Granma (Unofficial)\",\"locked\":false,\"bot\":true,\"discoverable\":false,\"group\":false,\"created_at\":\"2020-03-13T11:08:08.420Z\",\"note\":\"

\",\"url\":\"https://newsbots.eu/@granma\",\"avatar\":\"https://instance.codesections.com/system/accounts/avatars/000/000/717/original/4a1f9ed090fc36e9.jpeg?1584097687\",\"avatar_static\":\"https://instance.codesections.com/system/accounts/avatars/000/000/717/original/4a1f9ed090fc36e9.jpeg?1584097687\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":57,\"following_count\":1,\"statuses_count\":742,\"last_status_at\":\"2020-03-24\",\"emojis\":[],\"fields\":[{\"name\":\"Source\",\"value\":\"https://twitter.com/Granma_English\",\"verified_at\":null},{\"name\":\"Operator\",\"value\":\"@felix\",\"verified_at\":null},{\"name\":\"Code\",\"value\":\"https://yerbamate.dev/nutomic/tootbot\",\"verified_at\":null}]},\"media_attachments\":[{\"id\":\"2881\",\"type\":\"image\",\"url\":\"https://instance.codesections.com/system/media_attachments/files/000/002/881/original/a1e97908e84efbcd.jpeg?1585088707\",\"preview_url\":\"https://instance.codesections.com/system/media_attachments/files/000/002/881/small/a1e97908e84efbcd.jpeg?1585088707\",\"remote_url\":\"https://newsbots.eu/system/media_attachments/files/000/176/298/original/f30a877d5035f4a6.jpeg\",\"text_url\":null,\"meta\":{\"original\":{\"width\":700,\"height\":795,\"size\":\"700x795\",\"aspect\":0.8805031446540881},\"small\":{\"width\":375,\"height\":426,\"size\":\"375x426\",\"aspect\":0.8802816901408451}},\"description\":null,\"blurhash\":\"UHCY?%sD%1t6}snOxuxu#7rrx]xu$*i_NFNF\"}],\"mentions\":[],\"tags\":[{\"name\":\"covid\",\"url\":\"https://instance.codesections.com/tags/covid\"},{\"name\":\"cuba\",\"url\":\"https://instance.codesections.com/tags/cuba\"},{\"name\":\"CubaPorLaSalud\",\"url\":\"https://instance.codesections.com/tags/CubaPorLaSalud\"}],\"emojis\":[],\"card\":null,\"poll\":null}}\r\n"; - let (mut cache, _, _, _) = shared_setup(); - let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; - assert!(matches!( - msg, - RedisMsg::EventMsg(Timeline(Public, Federated, All), Event::Update { .. }) - )); - assert_eq!(rest, String::new()); - Ok(()) - } - - #[test] - fn parse_redis_input_from_live_data_3() -> Result<(), Err> { - let input = "*3\r\n$7\r\nmessage\r\n$15\r\ntimeline:public\r\n$4803\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103880453908763088\",\"created_at\":\"2020-03-24T22:45:33.000Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://mstdn.social/users/stux/statuses/103880453855603541\",\"url\":\"https://mstdn.social/@stux/103880453855603541\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"content\":\"

When they say lockdown. LOCKDOWN.

\",\"reblog\":null,\"account\":{\"id\":\"806\",\"username\":\"stux\",\"acct\":\"stux@mstdn.social\",\"display_name\":\"sтυx⚡\",\"locked\":false,\"bot\":false,\"discoverable\":true,\"group\":false,\"created_at\":\"2020-03-13T23:02:29.970Z\",\"note\":\"

Hi, Stux here! I am running the mstdn.social :mastodon: instance!

For questions and help or just for fun you can always send me a toot♥\u{fe0f}

Oh and no, I am not really a cat! Or am I?

\",\"url\":\"https://mstdn.social/@stux\",\"avatar\":\"https://instance.codesections.com/system/accounts/avatars/000/000/806/original/dae8d9d01d57d7f8.gif?1584140547\",\"avatar_static\":\"https://instance.codesections.com/system/accounts/avatars/000/000/806/static/dae8d9d01d57d7f8.png?1584140547\",\"header\":\"https://instance.codesections.com/system/accounts/headers/000/000/806/original/88c874d69f7d6989.gif?1584140548\",\"header_static\":\"https://instance.codesections.com/system/accounts/headers/000/000/806/static/88c874d69f7d6989.png?1584140548\",\"followers_count\":13954,\"following_count\":7600,\"statuses_count\":10207,\"last_status_at\":\"2020-03-24\",\"emojis\":[{\"shortcode\":\"mastodon\",\"url\":\"https://instance.codesections.com/system/custom_emojis/images/000/000/418/original/25ccc64333645735.png?1584140550\",\"static_url\":\"https://instance.codesections.com/system/custom_emojis/images/000/000/418/static/25ccc64333645735.png?1584140550\",\"visible_in_picker\":true},{\"shortcode\":\"patreon\",\"url\":\"https://instance.codesections.com/system/custom_emojis/images/000/000/419/original/3cc463d3dfc1e489.png?1584140550\",\"static_url\":\"https://instance.codesections.com/system/custom_emojis/images/000/000/419/static/3cc463d3dfc1e489.png?1584140550\",\"visible_in_picker\":true},{\"shortcode\":\"liberapay\",\"url\":\"https://instance.codesections.com/system/custom_emojis/images/000/000/420/original/893854353dfa9706.png?1584140551\",\"static_url\":\"https://instance.codesections.com/system/custom_emojis/images/000/000/420/static/893854353dfa9706.png?1584140551\",\"visible_in_picker\":true},{\"shortcode\":\"team_valor\",\"url\":\"https://instance.codesections.com/system/custom_emojis/images/000/000/958/original/96aae26b45292a12.png?1584910917\",\"static_url\":\"https://instance.codesections.com/system/custom_emojis/images/000/000/958/static/96aae26b45292a12.png?1584910917\",\"visible_in_picker\":true}],\"fields\":[{\"name\":\"Patreon :patreon:\",\"value\":\"https://www.patreon.com/mstdn\",\"verified_at\":null},{\"name\":\"LiberaPay :liberapay:\",\"value\":\"https://liberapay.com/mstdn\",\"verified_at\":null},{\"name\":\"Team :team_valor:\",\"value\":\"https://mstdn.social/team\",\"verified_at\":null},{\"name\":\"Support :mastodon:\",\"value\":\"https://mstdn.social/funding\",\"verified_at\":null}]},\"media_attachments\":[{\"id\":\"2886\",\"type\":\"video\",\"url\":\"https://instance.codesections.com/system/media_attachments/files/000/002/886/original/22b3f98a5e8f86d8.mp4?1585090023\",\"preview_url\":\"https://instance.codesections.com/system/media_attachments/files/000/002/886/small/22b3f98a5e8f86d8.png?1585090023\",\"remote_url\":\"https://cdn.mstdn.social/mstdn-social/media_attachments/files/003/338/384/original/c146f62ba86fe63e.mp4\",\"text_url\":null,\"meta\":{\"length\":\"0:00:27.03\",\"duration\":27.03,\"fps\":30,\"size\":\"272x480\",\"width\":272,\"height\":480,\"aspect\":0.5666666666666667,\"audio_encode\":\"aac (LC) (mp4a / 0x6134706D)\",\"audio_bitrate\":\"44100 Hz\",\"audio_channels\":\"stereo\",\"original\":{\"width\":272,\"height\":480,\"frame_rate\":\"30/1\",\"duration\":27.029,\"bitrate\":481885},\"small\":{\"width\":227,\"height\":400,\"size\":\"227x400\",\"aspect\":0.5675}},\"description\":null,\"blurhash\":\"UBF~N@OF-:xv4mM|s+ob9FE2t6tQ9Fs:t8oN\"}],\"mentions\":[],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null}}\r\n"; - let (mut cache, _, _, _) = shared_setup(); - let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; - assert!(matches!( - msg, - RedisMsg::EventMsg(Timeline(Public, Federated, All), Event::Update { .. }) - )); - assert_eq!(rest, String::new()); - Ok(()) - } -} +// #[cfg(test)] +// mod test { +// use super::*; +// use crate::{ +// err::RedisParseErr, +// parse_client_request::{Content::*, Reach::*, Stream::*, Timeline}, +// redis_to_client_stream::*, +// }; +// use lru::LruCache; +// use std::collections::HashMap; +// use uuid::Uuid; +// type Err = RedisParseErr; + +// /// Set up state shared between multiple tests of Redis parsing +// pub fn shared_setup() -> (LruCache, MessageQueues, Uuid, Timeline) { +// let mut cache: LruCache = LruCache::new(1000); +// let mut queues_map = HashMap::new(); +// let id = dbg!(Uuid::default()); + +// let timeline = dbg!( +// Timeline::from_redis_raw_timeline("timeline:4", &mut cache, &None).expect("In test") +// ); +// queues_map.insert(id, MsgQueue::new(timeline)); +// let queues = MessageQueues(queues_map); +// (cache, queues, id, timeline) +// } + +// #[test] +// fn accurately_parse_redis_output_into_event() -> Result<(), Err> { +// let input ="*3\r\n$7\r\nmessage\r\n$10\r\ntimeline:4\r\n$1386\r\n{\"event\":\"update\",\"payload\":{\"id\":\"102866835379605039\",\"created_at\":\"2019-09-27T22:29:02.590Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"http://localhost:3000/users/admin/statuses/102866835379605039\",\"url\":\"http://localhost:3000/@admin/102866835379605039\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"content\":\"

@susan hi

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"1\",\"username\":\"admin\",\"acct\":\"admin\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"created_at\":\"2019-07-04T00:21:05.890Z\",\"note\":\"

\",\"url\":\"http://localhost:3000/@admin\",\"avatar\":\"http://localhost:3000/avatars/original/missing.png\",\"avatar_static\":\"http://localhost:3000/avatars/original/missing.png\",\"header\":\"http://localhost:3000/headers/original/missing.png\",\"header_static\":\"http://localhost:3000/headers/original/missing.png\",\"followers_count\":3,\"following_count\":3,\"statuses_count\":192,\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[{\"id\":\"4\",\"username\":\"susan\",\"url\":\"http://localhost:3000/@susan\",\"acct\":\"susan\"}],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null},\"queued_at\":1569623342825}\r\n"; + +// let (mut cache, mut queues, id, timeline) = shared_setup(); +// crate::redis_to_client_stream::process_msg(input, &mut cache, &mut None, &mut queues); + +// let parsed_event = queues.oldest_msg_in_target_queue(id, timeline).unwrap(); +// let test_event = Event::Update{ payload: Status { +// id: "102866835379605039".to_string(), +// created_at: "2019-09-27T22:29:02.590Z".to_string(), +// in_reply_to_id: None, +// in_reply_to_account_id: None, +// sensitive: false, +// spoiler_text: "".to_string(), +// visibility: Visibility::Public, +// language: Some("en".to_string()), +// uri: "http://localhost:3000/users/admin/statuses/102866835379605039".to_string(), +// url: Some("http://localhost:3000/@admin/102866835379605039".to_string()), +// replies_count: 0, +// reblogs_count: 0, +// favourites_count: 0, +// favourited: Some(false), +// reblogged: Some(false), +// muted: Some(false), +// bookmarked: None, +// pinned: None, +// content: "

@susan hi

".to_string(), +// reblog: None, +// application: Some(Application { +// name: "Web".to_string(), +// website: None, +// vapid_key: None, +// client_id: None, +// client_secret: None, +// }), +// account: Account { +// id: "1".to_string(), +// username: "admin".to_string(), +// acct: "admin".to_string(), +// display_name: "".to_string(), +// locked:false, +// bot:Some(false), +// created_at: "2019-07-04T00:21:05.890Z".to_string(), +// note:"

".to_string(), +// url:"http://localhost:3000/@admin".to_string(), +// avatar: "http://localhost:3000/avatars/original/missing.png".to_string(), +// avatar_static:"http://localhost:3000/avatars/original/missing.png".to_string(), +// header: "http://localhost:3000/headers/original/missing.png".to_string(), +// header_static:"http://localhost:3000/headers/original/missing.png".to_string(), +// followers_count:3, +// following_count:3, +// statuses_count:192, +// emojis:vec![], +// fields:Some(vec![]), +// moved: None, +// group: None, +// last_status_at: None, +// discoverable: None, +// source: None, +// }, +// media_attachments:vec![], +// mentions: vec![ Mention {id:"4".to_string(), +// username:"susan".to_string(), +// url:"http://localhost:3000/@susan".to_string(), +// acct:"susan".to_string()}], +// tags:vec![], +// emojis:vec![], +// card:None,poll:None, +// text: None, +// }, +// queued_at: Some(1569623342825)}; + +// assert_eq!(parsed_event, test_event); +// Ok(()) +// } + +// #[test] +// fn parse_redis_input_subscription_msgs_and_update() -> Result<(), Err> { +// let input = "*3\r\n$9\r\nsubscribe\r\n$11\r\ntimeline:56\r\n:1\r\n*3\r\n$9\r\nsubscribe\r\n$12\r\ntimeline:308\r\n:2\r\n*3\r\n$9\r\nsubscribe\r\n$21\r\ntimeline:hashtag:test\r\n:3\r\n*3\r\n$9\r\nsubscribe\r\n$21\r\ntimeline:public:local\r\n:4\r\n*3\r\n$9\r\nsubscribe\r\n$11\r\ntimeline:55\r\n:5\r\n*3\r\n$7\r\nmessage\r\n$21\r\ntimeline:public:local\r\n$1249\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103881102123251272\",\"created_at\":\"2020-03-25T01:30:24.914Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/bob/statuses/103881102123251272\",\"url\":\"https://instance.codesections.com/@bob/103881102123251272\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"content\":\"

0111

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"55\",\"username\":\"bob\",\"acct\":\"bob\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T03:03:53.068Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@bob\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":1,\"statuses_count\":57,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null}}\r\n*3\r\n$7\r\nmessage\r\n$11\r\ntimeline:55\r\n$1360\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103881102123251272\",\"created_at\":\"2020-03-25T01:30:24.914Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/bob/statuses/103881102123251272\",\"url\":\"https://instance.codesections.com/@bob/103881102123251272\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"bookmarked\":false,\"pinned\":false,\"content\":\"

0111

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"55\",\"username\":\"bob\",\"acct\":\"bob\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T03:03:53.068Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@bob\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":1,\"statuses_count\":57,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null},\"queued_at\":1585099825263}\r\n*3\r\n$7\r\nmessage\r\n$21\r\ntimeline:public:local\r\n$1249\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103881103451006570\",\"created_at\":\"2020-03-25T01:30:45.152Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/bob/statuses/103881103451006570\",\"url\":\"https://instance.codesections.com/@bob/103881103451006570\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"content\":\"

1000

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"55\",\"username\":\"bob\",\"acct\":\"bob\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T03:03:53.068Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@bob\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":1,\"statuses_count\":58,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null}}\r\n*3\r\n$7\r\nmessage\r\n$11\r\ntimeline:55\r\n$1360\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103881103451006570\",\"created_at\":\"2020-03-25T01:30:45.152Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/bob/statuses/103881103451006570\",\"url\":\"https://instance.codesections.com/@bob/103881103451006570\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"bookmarked\":false,\"pinned\":false,\"content\":\"

1000

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"55\",\"username\":\"bob\",\"acct\":\"bob\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T03:03:53.068Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@bob\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":1,\"statuses_count\":58,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null},\"queued_at\":1585099845405}\r\n"; + +// let (mut cache, _, _, _) = shared_setup(); + +// let (subscription_msg1, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; +// assert!(matches!(subscription_msg1, RedisMsg::SubscriptionMsg)); + +// let (subscription_msg2, rest) = RedisMsg::from_raw(rest, &mut cache, &None)?; +// assert!(matches!(subscription_msg2, RedisMsg::SubscriptionMsg)); + +// let (subscription_msg3, rest) = RedisMsg::from_raw(rest, &mut cache, &None)?; +// assert!(matches!(subscription_msg3, RedisMsg::SubscriptionMsg)); + +// let (subscription_msg4, rest) = RedisMsg::from_raw(rest, &mut cache, &None)?; +// assert!(matches!(subscription_msg4, RedisMsg::SubscriptionMsg)); + +// let (subscription_msg5, rest) = RedisMsg::from_raw(rest, &mut cache, &None)?; +// assert!(matches!(subscription_msg5, RedisMsg::SubscriptionMsg)); + +// let (update_msg1, rest) = RedisMsg::from_raw(rest, &mut cache, &None)?; +// assert!(matches!( +// update_msg1, +// RedisMsg::EventMsg(_, Event::Update { .. }) +// )); + +// let (update_msg2, rest) = RedisMsg::from_raw(rest, &mut cache, &None)?; +// assert!(matches!( +// update_msg2, +// RedisMsg::EventMsg(_, Event::Update { .. }) +// )); + +// let (update_msg3, rest) = RedisMsg::from_raw(rest, &mut cache, &None)?; +// assert!(matches!( +// update_msg3, +// RedisMsg::EventMsg(_, Event::Update { .. }) +// )); + +// let (update_msg4, rest) = RedisMsg::from_raw(rest, &mut cache, &None)?; +// assert!(matches!( +// update_msg4, +// RedisMsg::EventMsg(_, Event::Update { .. }) +// )); + +// assert_eq!(rest, "".to_string()); + +// Ok(()) +// } + +// #[test] +// fn parse_redis_input_notification() -> Result<(), Err> { +// let input = "*3\r\n$7\r\nmessage\r\n$11\r\ntimeline:55\r\n$2311\r\n{\"event\":\"notification\",\"payload\":{\"id\":\"147\",\"type\":\"mention\",\"created_at\":\"2020-03-25T14:25:09.295Z\",\"account\":{\"id\":\"308\",\"username\":\"ralph\",\"acct\":\"ralph\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T19:55:20.933Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@ralph\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":2,\"statuses_count\":100,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"status\":{\"id\":\"103884148503208016\",\"created_at\":\"2020-03-25T14:25:08.995Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/ralph/statuses/103884148503208016\",\"url\":\"https://instance.codesections.com/@ralph/103884148503208016\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"bookmarked\":false,\"content\":\"

@bob notification test

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"308\",\"username\":\"ralph\",\"acct\":\"ralph\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T19:55:20.933Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@ralph\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":2,\"statuses_count\":100,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[{\"id\":\"55\",\"username\":\"bob\",\"url\":\"https://instance.codesections.com/@bob\",\"acct\":\"bob\"}],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null}}}\r\n"; + +// let (mut cache, _, _, _) = shared_setup(); + +// let (subscription_msg1, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; +// assert!(matches!( +// subscription_msg1, +// RedisMsg::EventMsg(Timeline(User(id), Federated, All), Event::Notification { .. }) if id == 55 +// )); + +// assert_eq!(rest, "".to_string()); + +// Ok(()) +// } + +// #[test] +// fn parse_redis_input_delete() -> Result<(), Err> { +// let input = "*3\r\n$7\r\nmessage\r\n$12\r\ntimeline:308\r\n$49\r\n{\"event\":\"delete\",\"payload\":\"103864778284581232\"}\r\n"; + +// let (mut cache, _, _, _) = dbg!(shared_setup()); + +// let (subscription_msg1, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; +// assert!(matches!( +// subscription_msg1, +// RedisMsg::EventMsg( +// Timeline(User(308), Federated, All), +// Event::Delete { payload: DeletedId(id) } +// ) if id == "103864778284581232".to_string() +// )); + +// assert_eq!(rest, "".to_string()); + +// Ok(()) +// } + +// #[test] +// fn parse_redis_input_filters_changed() -> Result<(), Err> { +// let input = "*3\r\n$7\r\nmessage\r\n$11\r\ntimeline:56\r\n$27\r\n{\"event\":\"filters_changed\"}\r\n"; + +// let (mut cache, _, _, _) = shared_setup(); + +// let (subscription_msg1, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; +// assert!(matches!( +// subscription_msg1, +// RedisMsg::EventMsg(Timeline(User(id), Federated, All), Event::FiltersChanged) if id == 56 +// )); + +// assert_eq!(rest, "".to_string()); + +// Ok(()) +// } + +// #[test] +// fn parse_redis_input_announcement() -> Result<(), Err> { +// let input = "*3\r\n$7\r\nmessage\r\n$12\r\ntimeline:308\r\n$293\r\n{\"event\":\"announcement\",\"payload\":{\"id\":\"2\",\"content\":\"

Test announcement 0010

\",\"starts_at\":null,\"ends_at\":null,\"all_day\":false,\"published_at\":\"2020-03-25T14:57:57.550Z\",\"updated_at\":\"2020-03-25T14:57:57.566Z\",\"mentions\":[],\"tags\":[],\"emojis\":[],\"reactions\":[{\"name\":\"đź‘Ť\",\"count\":2}]}}\r\n"; + +// let (mut cache, _, _, _) = shared_setup(); + +// let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; +// assert!(matches!( +// msg, +// RedisMsg::EventMsg( +// Timeline(User(id), Federated, All), +// Event::Announcement { .. }) if id == 308 +// )); + +// assert_eq!(rest, "".to_string()); +// Ok(()) +// } + +// #[test] +// fn parse_redis_input_announcement_reaction() -> Result<(), Err> { +// let input = "*3\r\n$7\r\nmessage\r\n$12\r\ntimeline:308\r\n$91\r\n{\"event\":\"announcement.reaction\",\"payload\":{\"name\":\"đź‘˝\",\"count\":2,\"announcement_id\":\"8\"}}\r\n"; + +// let (mut cache, _, _, _) = shared_setup(); + +// let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; +// assert!(matches!( +// msg, +// RedisMsg::EventMsg( +// Timeline(User(id), Federated, All), +// Event::AnnouncementReaction{ .. } +// ) if id == 308 +// )); + +// assert_eq!(rest, "".to_string()); +// Ok(()) +// } + +// #[test] +// fn parse_redis_input_announcement_delete() -> Result<(), Err> { +// let input = "*3\r\n$7\r\nmessage\r\n$12\r\ntimeline:308\r\n$45\r\n{\"event\":\"announcement.delete\",\"payload\":\"5\"}\r\n"; + +// let (mut cache, _, _, _) = shared_setup(); + +// let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; +// assert!(matches!( +// msg, +// RedisMsg::EventMsg( +// Timeline(User(id), Federated, All), +// Event::AnnouncementDelete{ +// payload: DeletedId(del_id), + +// } +// ) if id == 308 && del_id == "5".to_string() +// )); + +// assert_eq!(rest, "".to_string()); +// Ok(()) +// } + +// #[test] +// fn parse_redis_input_status_with_attachments() -> Result<(), Err> { +// let input = "*3\r\n$7\r\nmessage\r\n$12\r\ntimeline:308\r\n$2049\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103884996729070829\",\"created_at\":\"2020-03-25T18:00:52.026Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/ralph/statuses/103884996729070829\",\"url\":\"https://instance.codesections.com/@ralph/103884996729070829\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"bookmarked\":false,\"pinned\":false,\"content\":\"

Test with media attachment

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"308\",\"username\":\"ralph\",\"acct\":\"ralph\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T19:55:20.933Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@ralph\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":2,\"statuses_count\":103,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[{\"id\":\"3102\",\"type\":\"image\",\"url\":\"https://instance.codesections.com/system/media_attachments/files/000/003/102/original/1753cf5b8edd544a.jpg?1585159208\",\"preview_url\":\"https://instance.codesections.com/system/media_attachments/files/000/003/102/small/1753cf5b8edd544a.jpg?1585159208\",\"remote_url\":null,\"text_url\":\"https://instance.codesections.com/media/7XPfdkmAIHb3TQcLYII\",\"meta\":{\"original\":{\"width\":828,\"height\":340,\"size\":\"828x340\",\"aspect\":2.4352941176470586},\"small\":{\"width\":623,\"height\":256,\"size\":\"623x256\",\"aspect\":2.43359375},\"focus\":{\"x\":0.0,\"y\":0.0}},\"description\":\"Test image discription\",\"blurhash\":\"UBR{.4M{s;IU0JkBWBWB9bM{ofxu4^WAWBj[\"}],\"mentions\":[],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null},\"queued_at\":1585159252656}\r\n"; + +// let (mut cache, _, _, _) = shared_setup(); + +// let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; +// dbg!(&msg); +// assert!(matches!( +// msg, +// RedisMsg::EventMsg( +// Timeline(User(308), Federated, All), +// Event::Update{ payload: Status { media_attachments: attachments, .. }, .. } +// ) if attachments.len() > 0 +// )); + +// assert_eq!(rest, "".to_string()); +// Ok(()) +// } + +// #[test] +// fn parse_redis_input_status_with_mentions() -> Result<(), Err> { +// let input = "*3\r\n$7\r\nmessage\r\n$12\r\ntimeline:308\r\n$2094\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103885034181231245\",\"created_at\":\"2020-03-25T18:10:23.420Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/ralph/statuses/103885034181231245\",\"url\":\"https://instance.codesections.com/@ralph/103885034181231245\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"bookmarked\":false,\"pinned\":false,\"content\":\"

@bob @susan @codesections

Test with mentions

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"308\",\"username\":\"ralph\",\"acct\":\"ralph\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T19:55:20.933Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@ralph\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":2,\"statuses_count\":104,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[{\"id\":\"55\",\"username\":\"bob\",\"url\":\"https://instance.codesections.com/@bob\",\"acct\":\"bob\"},{\"id\":\"56\",\"username\":\"susan\",\"url\":\"https://instance.codesections.com/@susan\",\"acct\":\"susan\"},{\"id\":\"9\",\"username\":\"codesections\",\"url\":\"https://instance.codesections.com/@codesections\",\"acct\":\"codesections\"}],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null},\"queued_at\":1585159824540}\r\n"; + +// let (mut cache, _, _, _) = shared_setup(); + +// let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; +// dbg!(&msg); +// assert!(matches!( +// msg, +// RedisMsg::EventMsg( +// Timeline(User(308), Federated, All), +// Event::Update{ payload: Status { mentions, .. }, .. } +// ) if mentions.len() > 0 +// )); + +// assert_eq!(rest, "".to_string()); +// Ok(()) +// } + +// #[test] +// fn parse_redis_input_status_with_tags() -> Result<(), Err> { +// let input = "*3\r\n$7\r\nmessage\r\n$12\r\ntimeline:308\r\n$1770\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103885047114641861\",\"created_at\":\"2020-03-25T18:13:40.741Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/ralph/statuses/103885047114641861\",\"url\":\"https://instance.codesections.com/@ralph/103885047114641861\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"bookmarked\":false,\"pinned\":false,\"content\":\"

#test #hashtag

Test with tags

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"308\",\"username\":\"ralph\",\"acct\":\"ralph\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T19:55:20.933Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@ralph\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":2,\"statuses_count\":105,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[],\"tags\":[{\"name\":\"hashtag\",\"url\":\"https://instance.codesections.com/tags/hashtag\"},{\"name\":\"test\",\"url\":\"https://instance.codesections.com/tags/test\"}],\"emojis\":[],\"card\":null,\"poll\":null},\"queued_at\":1585160021281}\r\n"; + +// let (mut cache, _, _, _) = shared_setup(); + +// let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; +// dbg!(&msg); +// assert!(matches!( +// msg, +// RedisMsg::EventMsg( +// Timeline(User(308), Federated, All), +// Event::Update{ payload: Status { tags, .. }, .. } +// ) if tags.len() > 0 +// )); + +// assert_eq!(rest, "".to_string()); +// Ok(()) +// } + +// #[test] +// fn parse_redis_input_status_with_emojis() -> Result<(), Err> { +// let input = "*3\r\n$7\r\nmessage\r\n$12\r\ntimeline:308\r\n$1703\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103885068078872546\",\"created_at\":\"2020-03-25T18:19:00.620Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/ralph/statuses/103885068078872546\",\"url\":\"https://instance.codesections.com/@ralph/103885068078872546\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"bookmarked\":false,\"pinned\":false,\"content\":\"

Test with custom emoji

:patcat:

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"308\",\"username\":\"ralph\",\"acct\":\"ralph\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T19:55:20.933Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@ralph\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":2,\"statuses_count\":106,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[],\"tags\":[],\"emojis\":[{\"shortcode\":\"patcat\",\"url\":\"https://instance.codesections.com/system/custom_emojis/images/000/001/071/original/d87fcdf79ed6fe20.png?1585160295\",\"static_url\":\"https://instance.codesections.com/system/custom_emojis/images/000/001/071/static/d87fcdf79ed6fe20.png?1585160295\",\"visible_in_picker\":true}],\"card\":null,\"poll\":null},\"queued_at\":1585160340991}\r\n"; + +// let (mut cache, _, _, _) = shared_setup(); + +// let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; +// dbg!(&msg); +// assert!(matches!( +// msg, +// RedisMsg::EventMsg( +// Timeline(User(308), Federated, All), +// Event::Update{ payload: Status { emojis, .. }, .. } +// ) if emojis.len() > 0 +// )); + +// assert_eq!(rest, "".to_string()); +// Ok(()) +// } + +// #[test] +// fn parse_redis_input_status_is_reply() -> Result<(), Err> { +// let input = "*3\r\n$7\r\nmessage\r\n$12\r\ntimeline:308\r\n$1612\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103885083636011552\",\"created_at\":\"2020-03-25T18:22:57.963Z\",\"in_reply_to_id\":\"103881103451006570\",\"in_reply_to_account_id\":\"55\",\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/ralph/statuses/103885083636011552\",\"url\":\"https://instance.codesections.com/@ralph/103885083636011552\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"bookmarked\":false,\"pinned\":false,\"content\":\"

@bob Test is reply

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"308\",\"username\":\"ralph\",\"acct\":\"ralph\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T19:55:20.933Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@ralph\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":2,\"statuses_count\":107,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[{\"id\":\"55\",\"username\":\"bob\",\"url\":\"https://instance.codesections.com/@bob\",\"acct\":\"bob\"}],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null},\"queued_at\":1585160578486}\r\n*3\r\n$7\r\nmessage\r\n$11\r\ntimeline:55\r\n$2323\r\n{\"event\":\"notification\",\"payload\":{\"id\":\"156\",\"type\":\"mention\",\"created_at\":\"2020-03-25T18:22:58.293Z\",\"account\":{\"id\":\"308\",\"username\":\"ralph\",\"acct\":\"ralph\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T19:55:20.933Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@ralph\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":2,\"statuses_count\":107,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"status\":{\"id\":\"103885083636011552\",\"created_at\":\"2020-03-25T18:22:57.963Z\",\"in_reply_to_id\":\"103881103451006570\",\"in_reply_to_account_id\":\"55\",\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/ralph/statuses/103885083636011552\",\"url\":\"https://instance.codesections.com/@ralph/103885083636011552\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"bookmarked\":false,\"content\":\"

@bob Test is reply

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"308\",\"username\":\"ralph\",\"acct\":\"ralph\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T19:55:20.933Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@ralph\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":2,\"statuses_count\":107,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[{\"id\":\"55\",\"username\":\"bob\",\"url\":\"https://instance.codesections.com/@bob\",\"acct\":\"bob\"}],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null}}}\r\n"; + +// let (mut cache, _, _, _) = shared_setup(); + +// let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; +// dbg!(&msg); +// assert!(matches!( +// msg, +// RedisMsg::EventMsg( +// Timeline(User(308), Federated, All), +// Event::Update { +// payload: +// Status { +// in_reply_to_id: Some(_), +// .. +// }, +// .. +// }, +// ) +// )); +// let (msg2, rest) = RedisMsg::from_raw(rest, &mut cache, &None)?; +// dbg!(&msg2); +// assert!(matches!( +// msg2, +// RedisMsg::EventMsg(Timeline(User(55), Federated, All), Event::Notification { .. }) +// )); + +// assert_eq!(rest, "".to_string()); +// Ok(()) +// } + +// #[test] +// fn parse_redis_input_status_is_reblog() -> Result<(), Err> { +// let input = "*3\r\n$7\r\nmessage\r\n$12\r\ntimeline:308\r\n$2778\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103885156768039822\",\"created_at\":\"2020-03-25T18:41:33.859Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":null,\"uri\":\"https://instance.codesections.com/users/ralph/statuses/103885156768039822/activity\",\"url\":\"https://instance.codesections.com/users/ralph/statuses/103885156768039822/activity\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":true,\"muted\":false,\"bookmarked\":false,\"content\":\"

RT @bob 0010

\",\"reblog\":{\"id\":\"103881061540314589\",\"created_at\":\"2020-03-25T01:20:05.648Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/bob/statuses/103881061540314589\",\"url\":\"https://instance.codesections.com/@bob/103881061540314589\",\"replies_count\":0,\"reblogs_count\":1,\"favourites_count\":0,\"favourited\":false,\"reblogged\":true,\"muted\":false,\"bookmarked\":false,\"content\":\"

0010

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"55\",\"username\":\"bob\",\"acct\":\"bob\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T03:03:53.068Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@bob\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":1,\"statuses_count\":58,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null},\"application\":null,\"account\":{\"id\":\"308\",\"username\":\"ralph\",\"acct\":\"ralph\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T19:55:20.933Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@ralph\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":2,\"statuses_count\":110,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null},\"queued_at\":1585161694429}\r\n"; + +// let (mut cache, _, _, _) = shared_setup(); + +// let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; +// dbg!(&msg); +// assert!(matches!( +// msg, +// RedisMsg::EventMsg( +// Timeline(User(308), Federated, All), +// Event::Update { +// payload: +// Status { +// reblogged: Some(t), .. +// }, +// .. +// }, +// ) if t +// )); + +// assert_eq!(rest, "".to_string()); +// Ok(()) +// } + +// #[test] +// fn parse_redis_input_status_with_poll() -> Result<(), Err> { +// let input = "*3\r\n$7\r\nmessage\r\n$12\r\ntimeline:308\r\n$1663\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103885228849512739\",\"created_at\":\"2020-03-25T18:59:53.788Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/ralph/statuses/103885228849512739\",\"url\":\"https://instance.codesections.com/@ralph/103885228849512739\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"bookmarked\":false,\"pinned\":false,\"content\":\"

test poll:

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"308\",\"username\":\"ralph\",\"acct\":\"ralph\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T19:55:20.933Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@ralph\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":2,\"statuses_count\":112,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":{\"id\":\"46\",\"expires_at\":\"2020-03-26T18:59:53.747Z\",\"expired\":false,\"multiple\":false,\"votes_count\":0,\"voters_count\":0,\"voted\":true,\"own_votes\":[],\"options\":[{\"title\":\"1\",\"votes_count\":0},{\"title\":\"2\",\"votes_count\":0},{\"title\":\"3\",\"votes_count\":0},{\"title\":\"4\",\"votes_count\":0}],\"emojis\":[]}},\"queued_at\":1585162794362}\r\n"; + +// let (mut cache, _, _, _) = shared_setup(); + +// let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; +// dbg!(&msg); +// assert!(matches!( +// msg, +// RedisMsg::EventMsg( +// Timeline(User(308), Federated, All), +// Event::Update { +// payload: Status { poll: Some(_), .. }, +// .. +// }, +// ) +// )); + +// assert_eq!(rest, "".to_string()); +// Ok(()) +// } + +// #[test] +// fn parse_redis_input_status_with_preview_card() -> Result<(), Err> { +// let input = "*3\r\n$7\r\nmessage\r\n$11\r\ntimeline:55\r\n$2256\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103885300935387207\",\"created_at\":\"2020-03-25T19:18:13.753Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/ralph/statuses/103885300935387207\",\"url\":\"https://instance.codesections.com/@ralph/103885300935387207\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"bookmarked\":false,\"content\":\"

Test with preview card:

https://www.codesections.com/blog/mastodon-elevator-pitch/

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"308\",\"username\":\"ralph\",\"acct\":\"ralph\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T19:55:20.933Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@ralph\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":2,\"following_count\":2,\"statuses_count\":120,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[],\"tags\":[],\"emojis\":[],\"card\":{\"url\":\"https://www.codesections.com/blog/mastodon-elevator-pitch/\",\"title\":\" Mastodon Is Better than Twitter: Elevator Pitch | CodeSections\",\"description\":\"The personal website and blog of Daniel Long Sockwell, a lawyer-turned-programmer with an interest in web development, open source, and making things as simple as possible.\",\"type\":\"link\",\"author_name\":\"\",\"author_url\":\"\",\"provider_name\":\"\",\"provider_url\":\"\",\"html\":\"\",\"width\":400,\"height\":400,\"image\":\"https://instance.codesections.com/system/preview_cards/images/000/000/002/original/f6e89baa729668e7.png?1585163010\",\"embed_url\":\"\"},\"poll\":null},\"queued_at\":1585163894281}\r\n"; + +// let (mut cache, _, _, _) = shared_setup(); + +// let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; +// dbg!(&msg); +// assert!(matches!( +// msg, +// RedisMsg::EventMsg( +// Timeline(User(55), Federated, All), +// Event::Update { +// payload: Status { card: Some(_), .. }, +// .. +// }, +// ) +// )); + +// assert_eq!(rest, "".to_string()); +// Ok(()) +// } + +// #[test] +// fn parse_redis_input_conversation() -> Result<(), Err> { +// let input = "*3\r\n$7\r\nmessage\r\n$17\r\ntimeline:direct:9\r\n$2442\r\n{\"event\":\"conversation\",\"payload\":{\"id\":\"22\",\"unread\":false,\"accounts\":[{\"id\":\"55\",\"username\":\"bob\",\"acct\":\"bob\",\"display_name\":\"\",\"locked\":false,\"bot\":false,\"discoverable\":null,\"group\":false,\"created_at\":\"2020-03-11T03:03:53.068Z\",\"note\":\"

\",\"url\":\"https://instance.codesections.com/@bob\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":1,\"following_count\":1,\"statuses_count\":58,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]}],\"last_status\":{\"id\":\"103884351200485419\",\"created_at\":\"2020-03-25T15:16:41.915Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"direct\",\"language\":\"en\",\"uri\":\"https://instance.codesections.com/users/codesections/statuses/103884351200485419\",\"url\":\"https://instance.codesections.com/@codesections/103884351200485419\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"favourited\":false,\"reblogged\":false,\"muted\":false,\"bookmarked\":false,\"content\":\"

@bob Test Conversation

\",\"reblog\":null,\"application\":{\"name\":\"Web\",\"website\":null},\"account\":{\"id\":\"9\",\"username\":\"codesections\",\"acct\":\"codesections\",\"display_name\":\"TEST ACCOUT for codesections\",\"locked\":false,\"bot\":false,\"discoverable\":false,\"group\":false,\"created_at\":\"2020-03-11T01:17:13.412Z\",\"note\":\"

Used in the testing and development of flodgatt, the WIP streaming server for Mastodon

\",\"url\":\"https://instance.codesections.com/@codesections\",\"avatar\":\"https://instance.codesections.com/avatars/original/missing.png\",\"avatar_static\":\"https://instance.codesections.com/avatars/original/missing.png\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":79,\"following_count\":97,\"statuses_count\":7,\"last_status_at\":\"2020-03-25\",\"emojis\":[],\"fields\":[]},\"media_attachments\":[],\"mentions\":[{\"id\":\"55\",\"username\":\"bob\",\"url\":\"https://instance.codesections.com/@bob\",\"acct\":\"bob\"}],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null}},\"queued_at\":1585149402344}\r\n"; + +// let (mut cache, _, _, _) = shared_setup(); + +// let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; +// dbg!(&msg); +// assert!(matches!( +// msg, +// RedisMsg::EventMsg( +// Timeline(Direct(id), Federated, All), +// Event::Conversation{ ..} +// ) if id == 9 +// )); + +// assert_eq!(rest, "".to_string()); +// Ok(()) +// } + +// #[test] +// fn parse_redis_input_from_live_data_1() -> Result<(), Err> { +// let input = "*3\r\n$7\r\nmessage\r\n$15\r\ntimeline:public\r\n$2799\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103880088450458596\",\"created_at\":\"2020-03-24T21:12:37.000Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"es\",\"uri\":\"https://mastodon.social/users/durru/statuses/103880088436492032\",\"url\":\"https://mastodon.social/@durru/103880088436492032\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"content\":\"

¡No puedes salir, loca!

\",\"reblog\":null,\"account\":{\"id\":\"2271\",\"username\":\"durru\",\"acct\":\"durru@mastodon.social\",\"display_name\":\"Cloaca Maxima\",\"locked\":false,\"bot\":false,\"discoverable\":true,\"group\":false,\"created_at\":\"2020-03-24T21:27:31.669Z\",\"note\":\"

Todo pasa, antes o después, por la Cloaca, diría Vitruvio.
También compongo palíndromos.

\",\"url\":\"https://mastodon.social/@durru\",\"avatar\":\"https://instance.codesections.com/system/accounts/avatars/000/002/271/original/d7675a6ff9d9baa7.jpeg?1585085250\",\"avatar_static\":\"https://instance.codesections.com/system/accounts/avatars/000/002/271/original/d7675a6ff9d9baa7.jpeg?1585085250\",\"header\":\"https://instance.codesections.com/system/accounts/headers/000/002/271/original/e3f0a1989b0d8efc.jpeg?1585085250\",\"header_static\":\"https://instance.codesections.com/system/accounts/headers/000/002/271/original/e3f0a1989b0d8efc.jpeg?1585085250\",\"followers_count\":222,\"following_count\":81,\"statuses_count\":5443,\"last_status_at\":\"2020-03-24\",\"emojis\":[],\"fields\":[{\"name\":\"Mis fotos\",\"value\":\"https://pixelfed.de/durru\",\"verified_at\":null},{\"name\":\"diaspora*\",\"value\":\"https://joindiaspora.com/people/75fec0e05114013484870242ac110007\",\"verified_at\":null}]},\"media_attachments\":[{\"id\":\"2864\",\"type\":\"image\",\"url\":\"https://instance.codesections.com/system/media_attachments/files/000/002/864/original/3988312d30936494.jpeg?1585085251\",\"preview_url\":\"https://instance.codesections.com/system/media_attachments/files/000/002/864/small/3988312d30936494.jpeg?1585085251\",\"remote_url\":\"https://files.mastodon.social/media_attachments/files/026/669/690/original/d8171331f956cf38.jpg\",\"text_url\":null,\"meta\":{\"original\":{\"width\":1001,\"height\":662,\"size\":\"1001x662\",\"aspect\":1.512084592145015},\"small\":{\"width\":491,\"height\":325,\"size\":\"491x325\",\"aspect\":1.5107692307692309}},\"description\":null,\"blurhash\":\"UdLqhI4n4TIUIAt7t7ay~qIojtRj?bM{M{of\"}],\"mentions\":[],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null}}\r\n"; +// let (mut cache, _, _, _) = shared_setup(); +// let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; +// assert!(matches!( +// msg, +// RedisMsg::EventMsg(Timeline(Public, Federated, All), Event::Update { .. }) +// )); +// assert_eq!(rest, String::new()); +// Ok(()) +// } + +// #[test] +// fn parse_redis_input_from_live_data_2() -> Result<(), Err> { +// let input = "*3\r\n$7\r\nmessage\r\n$15\r\ntimeline:public\r\n$3888\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103880373579328660\",\"created_at\":\"2020-03-24T22:25:05.000Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://newsbots.eu/users/granma/statuses/103880373417385978\",\"url\":\"https://newsbots.eu/@granma/103880373417385978\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"content\":\"

A total of 11 measures have been established for the pre-epidemic stage of the battle against #Covid-19 in #Cuba
#CubaPorLaSalud
http://en.granma.cu/cuba/2020-03-23/public-health-measures-in-covid-19-pre-epidemic-stage 

\",\"reblog\":null,\"account\":{\"id\":\"717\",\"username\":\"granma\",\"acct\":\"granma@newsbots.eu\",\"display_name\":\"Granma (Unofficial)\",\"locked\":false,\"bot\":true,\"discoverable\":false,\"group\":false,\"created_at\":\"2020-03-13T11:08:08.420Z\",\"note\":\"

\",\"url\":\"https://newsbots.eu/@granma\",\"avatar\":\"https://instance.codesections.com/system/accounts/avatars/000/000/717/original/4a1f9ed090fc36e9.jpeg?1584097687\",\"avatar_static\":\"https://instance.codesections.com/system/accounts/avatars/000/000/717/original/4a1f9ed090fc36e9.jpeg?1584097687\",\"header\":\"https://instance.codesections.com/headers/original/missing.png\",\"header_static\":\"https://instance.codesections.com/headers/original/missing.png\",\"followers_count\":57,\"following_count\":1,\"statuses_count\":742,\"last_status_at\":\"2020-03-24\",\"emojis\":[],\"fields\":[{\"name\":\"Source\",\"value\":\"https://twitter.com/Granma_English\",\"verified_at\":null},{\"name\":\"Operator\",\"value\":\"@felix\",\"verified_at\":null},{\"name\":\"Code\",\"value\":\"https://yerbamate.dev/nutomic/tootbot\",\"verified_at\":null}]},\"media_attachments\":[{\"id\":\"2881\",\"type\":\"image\",\"url\":\"https://instance.codesections.com/system/media_attachments/files/000/002/881/original/a1e97908e84efbcd.jpeg?1585088707\",\"preview_url\":\"https://instance.codesections.com/system/media_attachments/files/000/002/881/small/a1e97908e84efbcd.jpeg?1585088707\",\"remote_url\":\"https://newsbots.eu/system/media_attachments/files/000/176/298/original/f30a877d5035f4a6.jpeg\",\"text_url\":null,\"meta\":{\"original\":{\"width\":700,\"height\":795,\"size\":\"700x795\",\"aspect\":0.8805031446540881},\"small\":{\"width\":375,\"height\":426,\"size\":\"375x426\",\"aspect\":0.8802816901408451}},\"description\":null,\"blurhash\":\"UHCY?%sD%1t6}snOxuxu#7rrx]xu$*i_NFNF\"}],\"mentions\":[],\"tags\":[{\"name\":\"covid\",\"url\":\"https://instance.codesections.com/tags/covid\"},{\"name\":\"cuba\",\"url\":\"https://instance.codesections.com/tags/cuba\"},{\"name\":\"CubaPorLaSalud\",\"url\":\"https://instance.codesections.com/tags/CubaPorLaSalud\"}],\"emojis\":[],\"card\":null,\"poll\":null}}\r\n"; +// let (mut cache, _, _, _) = shared_setup(); +// let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; +// assert!(matches!( +// msg, +// RedisMsg::EventMsg(Timeline(Public, Federated, All), Event::Update { .. }) +// )); +// assert_eq!(rest, String::new()); +// Ok(()) +// } + +// #[test] +// fn parse_redis_input_from_live_data_3() -> Result<(), Err> { +// let input = "*3\r\n$7\r\nmessage\r\n$15\r\ntimeline:public\r\n$4803\r\n{\"event\":\"update\",\"payload\":{\"id\":\"103880453908763088\",\"created_at\":\"2020-03-24T22:45:33.000Z\",\"in_reply_to_id\":null,\"in_reply_to_account_id\":null,\"sensitive\":false,\"spoiler_text\":\"\",\"visibility\":\"public\",\"language\":\"en\",\"uri\":\"https://mstdn.social/users/stux/statuses/103880453855603541\",\"url\":\"https://mstdn.social/@stux/103880453855603541\",\"replies_count\":0,\"reblogs_count\":0,\"favourites_count\":0,\"content\":\"

When they say lockdown. LOCKDOWN.

\",\"reblog\":null,\"account\":{\"id\":\"806\",\"username\":\"stux\",\"acct\":\"stux@mstdn.social\",\"display_name\":\"sтυx⚡\",\"locked\":false,\"bot\":false,\"discoverable\":true,\"group\":false,\"created_at\":\"2020-03-13T23:02:29.970Z\",\"note\":\"

Hi, Stux here! I am running the mstdn.social :mastodon: instance!

For questions and help or just for fun you can always send me a toot♥\u{fe0f}

Oh and no, I am not really a cat! Or am I?

\",\"url\":\"https://mstdn.social/@stux\",\"avatar\":\"https://instance.codesections.com/system/accounts/avatars/000/000/806/original/dae8d9d01d57d7f8.gif?1584140547\",\"avatar_static\":\"https://instance.codesections.com/system/accounts/avatars/000/000/806/static/dae8d9d01d57d7f8.png?1584140547\",\"header\":\"https://instance.codesections.com/system/accounts/headers/000/000/806/original/88c874d69f7d6989.gif?1584140548\",\"header_static\":\"https://instance.codesections.com/system/accounts/headers/000/000/806/static/88c874d69f7d6989.png?1584140548\",\"followers_count\":13954,\"following_count\":7600,\"statuses_count\":10207,\"last_status_at\":\"2020-03-24\",\"emojis\":[{\"shortcode\":\"mastodon\",\"url\":\"https://instance.codesections.com/system/custom_emojis/images/000/000/418/original/25ccc64333645735.png?1584140550\",\"static_url\":\"https://instance.codesections.com/system/custom_emojis/images/000/000/418/static/25ccc64333645735.png?1584140550\",\"visible_in_picker\":true},{\"shortcode\":\"patreon\",\"url\":\"https://instance.codesections.com/system/custom_emojis/images/000/000/419/original/3cc463d3dfc1e489.png?1584140550\",\"static_url\":\"https://instance.codesections.com/system/custom_emojis/images/000/000/419/static/3cc463d3dfc1e489.png?1584140550\",\"visible_in_picker\":true},{\"shortcode\":\"liberapay\",\"url\":\"https://instance.codesections.com/system/custom_emojis/images/000/000/420/original/893854353dfa9706.png?1584140551\",\"static_url\":\"https://instance.codesections.com/system/custom_emojis/images/000/000/420/static/893854353dfa9706.png?1584140551\",\"visible_in_picker\":true},{\"shortcode\":\"team_valor\",\"url\":\"https://instance.codesections.com/system/custom_emojis/images/000/000/958/original/96aae26b45292a12.png?1584910917\",\"static_url\":\"https://instance.codesections.com/system/custom_emojis/images/000/000/958/static/96aae26b45292a12.png?1584910917\",\"visible_in_picker\":true}],\"fields\":[{\"name\":\"Patreon :patreon:\",\"value\":\"https://www.patreon.com/mstdn\",\"verified_at\":null},{\"name\":\"LiberaPay :liberapay:\",\"value\":\"https://liberapay.com/mstdn\",\"verified_at\":null},{\"name\":\"Team :team_valor:\",\"value\":\"https://mstdn.social/team\",\"verified_at\":null},{\"name\":\"Support :mastodon:\",\"value\":\"https://mstdn.social/funding\",\"verified_at\":null}]},\"media_attachments\":[{\"id\":\"2886\",\"type\":\"video\",\"url\":\"https://instance.codesections.com/system/media_attachments/files/000/002/886/original/22b3f98a5e8f86d8.mp4?1585090023\",\"preview_url\":\"https://instance.codesections.com/system/media_attachments/files/000/002/886/small/22b3f98a5e8f86d8.png?1585090023\",\"remote_url\":\"https://cdn.mstdn.social/mstdn-social/media_attachments/files/003/338/384/original/c146f62ba86fe63e.mp4\",\"text_url\":null,\"meta\":{\"length\":\"0:00:27.03\",\"duration\":27.03,\"fps\":30,\"size\":\"272x480\",\"width\":272,\"height\":480,\"aspect\":0.5666666666666667,\"audio_encode\":\"aac (LC) (mp4a / 0x6134706D)\",\"audio_bitrate\":\"44100 Hz\",\"audio_channels\":\"stereo\",\"original\":{\"width\":272,\"height\":480,\"frame_rate\":\"30/1\",\"duration\":27.029,\"bitrate\":481885},\"small\":{\"width\":227,\"height\":400,\"size\":\"227x400\",\"aspect\":0.5675}},\"description\":null,\"blurhash\":\"UBF~N@OF-:xv4mM|s+ob9FE2t6tQ9Fs:t8oN\"}],\"mentions\":[],\"tags\":[],\"emojis\":[],\"card\":null,\"poll\":null}}\r\n"; +// let (mut cache, _, _, _) = shared_setup(); +// let (msg, rest) = RedisMsg::from_raw(input, &mut cache, &None)?; +// assert!(matches!( +// msg, +// RedisMsg::EventMsg(Timeline(Public, Federated, All), Event::Update { .. }) +// )); +// assert_eq!(rest, String::new()); +// Ok(()) +// } +// } +// TODO: Revise these tests to cover *only* the RedisMessage -> (Timeline, Event) parsing diff --git a/src/parse_client_request/mod.rs b/src/parse_client_request/mod.rs index c076eb2..0ecc5e9 100644 --- a/src/parse_client_request/mod.rs +++ b/src/parse_client_request/mod.rs @@ -9,5 +9,5 @@ pub use self::postgres::PgPool; // TODO consider whether we can remove `Stream` from public API pub use subscription::{Stream, Subscription, Timeline}; -#[cfg(test)] +//#[cfg(test)] pub use subscription::{Content, Reach}; diff --git a/src/parse_client_request/subscription.rs b/src/parse_client_request/subscription.rs index d8e874a..dcf8409 100644 --- a/src/parse_client_request/subscription.rs +++ b/src/parse_client_request/subscription.rs @@ -196,109 +196,120 @@ impl Timeline { } } - pub fn from_redis_raw_timeline( + pub fn from_redis_text( timeline: &str, cache: &mut LruCache, - namespace: &Option, ) -> Result { - use crate::err::TimelineErr::RedisNamespaceMismatch; - use {Content::*, Reach::*, Stream::*}; - let timeline_slice = &timeline.split(":").collect::>()[..]; - - #[rustfmt::skip] - let (stream, reach, content) = if let Some(ns) = namespace { - match timeline_slice { - [n, "timeline", "public"] if n == ns => (Public, Federated, All), - [_, "timeline", "public"] - | ["timeline", "public"] => Err(RedisNamespaceMismatch)?, - - [n, "timeline", "public", "local"] if ns == n => (Public, Local, All), - [_, "timeline", "public", "local"] - | ["timeline", "public", "local"] => Err(RedisNamespaceMismatch)?, - - [n, "timeline", "public", "media"] if ns == n => (Public, Federated, Media), - [_, "timeline", "public", "media"] - | ["timeline", "public", "media"] => Err(RedisNamespaceMismatch)?, - - [n, "timeline", "public", "local", "media"] if ns == n => (Public, Local, Media), - [_, "timeline", "public", "local", "media"] - | ["timeline", "public", "local", "media"] => Err(RedisNamespaceMismatch)?, - - [n, "timeline", "hashtag", tag_name] if ns == n => { - let tag_id = *cache - .get(&tag_name.to_string()) - .unwrap_or_else(|| log_fatal!("No cached id for `{}`", tag_name)); - (Hashtag(tag_id), Federated, All) - } - [_, "timeline", "hashtag", _tag] - | ["timeline", "hashtag", _tag] => Err(RedisNamespaceMismatch)?, - - [n, "timeline", "hashtag", _tag, "local"] if ns == n => (Hashtag(0), Local, All), - [_, "timeline", "hashtag", _tag, "local"] - | ["timeline", "hashtag", _tag, "local"] => Err(RedisNamespaceMismatch)?, - - [n, "timeline", id] if ns == n => (User(id.parse().unwrap()), Federated, All), - [_, "timeline", _id] - | ["timeline", _id] => Err(RedisNamespaceMismatch)?, - - [n, "timeline", id, "notification"] if ns == n => - (User(id.parse()?), Federated, Notification), - - [_, "timeline", _id, "notification"] - | ["timeline", _id, "notification"] => Err(RedisNamespaceMismatch)?, - - - [n, "timeline", "list", id] if ns == n => (List(id.parse()?), Federated, All), - [_, "timeline", "list", _id] - | ["timeline", "list", _id] => Err(RedisNamespaceMismatch)?, - - [n, "timeline", "direct", id] if ns == n => (Direct(id.parse()?), Federated, All), - [_, "timeline", "direct", _id] - | ["timeline", "direct", _id] => Err(RedisNamespaceMismatch)?, - - [..] => log_fatal!("Unexpected channel from Redis: {:?}", timeline_slice), - } - } else { - match timeline_slice { - ["timeline", "public"] => (Public, Federated, All), - [_, "timeline", "public"] => Err(RedisNamespaceMismatch)?, - - ["timeline", "public", "local"] => (Public, Local, All), - [_, "timeline", "public", "local"] => Err(RedisNamespaceMismatch)?, - - ["timeline", "public", "media"] => (Public, Federated, Media), - - [_, "timeline", "public", "media"] => Err(RedisNamespaceMismatch)?, - - ["timeline", "public", "local", "media"] => (Public, Local, Media), - [_, "timeline", "public", "local", "media"] => Err(RedisNamespaceMismatch)?, - - ["timeline", "hashtag", _tag] => (Hashtag(0), Federated, All), - [_, "timeline", "hashtag", _tag] => Err(RedisNamespaceMismatch)?, - - ["timeline", "hashtag", _tag, "local"] => (Hashtag(0), Local, All), - [_, "timeline", "hashtag", _tag, "local"] => Err(RedisNamespaceMismatch)?, - - ["timeline", id] => (User(id.parse().unwrap()), Federated, All), - [_, "timeline", _id] => Err(RedisNamespaceMismatch)?, - - ["timeline", id, "notification"] => { - (User(id.parse().unwrap()), Federated, Notification) - } - [_, "timeline", _id, "notification"] => Err(RedisNamespaceMismatch)?, - - ["timeline", "list", id] => (List(id.parse().unwrap()), Federated, All), - [_, "timeline", "list", _id] => Err(RedisNamespaceMismatch)?, - - ["timeline", "direct", id] => (Direct(id.parse().unwrap()), Federated, All), - [_, "timeline", "direct", _id] => Err(RedisNamespaceMismatch)?, - - // Other endpoints don't exist: - [..] => Err(TimelineErr::InvalidInput)?, - } + let mut id_from_tag = |tag: &str| match cache.get(&tag.to_string()) { + Some(id) => Ok(*id), + None => Err(TimelineErr::InvalidInput), // TODO more specific }; - Ok(Timeline(stream, reach, content)) + use {Content::*, Reach::*, Stream::*}; + Ok(match &timeline.split(":").collect::>()[..] { + ["public"] => Timeline(Public, Federated, All), + ["public", "local"] => Timeline(Public, Local, All), + ["public", "media"] => Timeline(Public, Federated, Media), + ["public", "local", "media"] => Timeline(Public, Local, Media), + ["hashtag", tag] => Timeline(Hashtag(id_from_tag(tag)?), Federated, All), + ["hashtag", tag, "local"] => Timeline(Hashtag(id_from_tag(tag)?), Local, All), + [id] => Timeline(User(id.parse().unwrap()), Federated, All), + [id, "notification"] => Timeline(User(id.parse().unwrap()), Federated, Notification), + ["list", id] => Timeline(List(id.parse().unwrap()), Federated, All), + ["direct", id] => Timeline(Direct(id.parse().unwrap()), Federated, All), + // Other endpoints don't exist: + [..] => Err(TimelineErr::InvalidInput)?, + }) + // let (stream, reach, content) = if let Some(ns) = namespace { + // match timeline_slice { + // [n, "timeline", "public"] if n == ns => (Public, Federated, All), + // [_, "timeline", "public"] + // | ["timeline", "public"] => Err(RedisNamespaceMismatch)?, + + // [n, "timeline", "public", "local"] if ns == n => (Public, Local, All), + // [_, "timeline", "public", "local"] + // | ["timeline", "public", "local"] => Err(RedisNamespaceMismatch)?, + + // [n, "timeline", "public", "media"] if ns == n => (Public, Federated, Media), + // [_, "timeline", "public", "media"] + // | ["timeline", "public", "media"] => Err(RedisNamespaceMismatch)?, + + // [n, "timeline", "public", "local", "media"] if ns == n => (Public, Local, Media), + // [_, "timeline", "public", "local", "media"] + // | ["timeline", "public", "local", "media"] => Err(RedisNamespaceMismatch)?, + + // [n, "timeline", "hashtag", tag_name] if ns == n => { + // let tag_id = *cache + // .get(&tag_name.to_string()) + // .unwrap_or_else(|| log_fatal!("No cached id for `{}`", tag_name)); + // (Hashtag(tag_id), Federated, All) + // } + // [_, "timeline", "hashtag", _tag] + // | ["timeline", "hashtag", _tag] => Err(RedisNamespaceMismatch)?, + + // [n, "timeline", "hashtag", _tag, "local"] if ns == n => (Hashtag(0), Local, All), + // [_, "timeline", "hashtag", _tag, "local"] + // | ["timeline", "hashtag", _tag, "local"] => Err(RedisNamespaceMismatch)?, + + // [n, "timeline", id] if ns == n => (User(id.parse().unwrap()), Federated, All), + // [_, "timeline", _id] + // | ["timeline", _id] => Err(RedisNamespaceMismatch)?, + + // [n, "timeline", id, "notification"] if ns == n => + // (User(id.parse()?), Federated, Notification), + + // [_, "timeline", _id, "notification"] + // | ["timeline", _id, "notification"] => Err(RedisNamespaceMismatch)?, + + // [n, "timeline", "list", id] if ns == n => (List(id.parse()?), Federated, All), + // [_, "timeline", "list", _id] + // | ["timeline", "list", _id] => Err(RedisNamespaceMismatch)?, + + // [n, "timeline", "direct", id] if ns == n => (Direct(id.parse()?), Federated, All), + // [_, "timeline", "direct", _id] + // | ["timeline", "direct", _id] => Err(RedisNamespaceMismatch)?, + + // [..] => log_fatal!("Unexpected channel from Redis: {:?}", timeline_slice), + // } + // } else { + // match timeline_slice { + // ["timeline", "public"] => (Public, Federated, All), + // [_, "timeline", "public"] => Err(RedisNamespaceMismatch)?, + + // ["timeline", "public", "local"] => (Public, Local, All), + // [_, "timeline", "public", "local"] => Err(RedisNamespaceMismatch)?, + + // ["timeline", "public", "media"] => (Public, Federated, Media), + + // [_, "timeline", "public", "media"] => Err(RedisNamespaceMismatch)?, + + // ["timeline", "public", "local", "media"] => (Public, Local, Media), + // [_, "timeline", "public", "local", "media"] => Err(RedisNamespaceMismatch)?, + + // ["timeline", "hashtag", _tag] => (Hashtag(0), Federated, All), + // [_, "timeline", "hashtag", _tag] => Err(RedisNamespaceMismatch)?, + + // ["timeline", "hashtag", _tag, "local"] => (Hashtag(0), Local, All), + // [_, "timeline", "hashtag", _tag, "local"] => Err(RedisNamespaceMismatch)?, + + // ["timeline", id] => (User(id.parse().unwrap()), Federated, All), + // [_, "timeline", _id] => Err(RedisNamespaceMismatch)?, + + // ["timeline", id, "notification"] => { + // (User(id.parse().unwrap()), Federated, Notification) + // } + // [_, "timeline", _id, "notification"] => Err(RedisNamespaceMismatch)?, + + // ["timeline", "list", id] => (List(id.parse().unwrap()), Federated, All), + // [_, "timeline", "list", _id] => Err(RedisNamespaceMismatch)?, + + // ["timeline", "direct", id] => (Direct(id.parse().unwrap()), Federated, All), + // [_, "timeline", "direct", _id] => Err(RedisNamespaceMismatch)?, + + // // Other endpoints don't exist: + // [..] => Err(TimelineErr::InvalidInput)?, + // } + // }; } fn from_query_and_user(q: &Query, user: &UserData, pool: PgPool) -> Result { use {warp::reject::custom, Content::*, Reach::*, Scope::*, Stream::*}; diff --git a/src/redis_to_client_stream/client_agent.rs b/src/redis_to_client_stream/client_agent.rs index 18c79d3..2c6de80 100644 --- a/src/redis_to_client_stream/client_agent.rs +++ b/src/redis_to_client_stream/client_agent.rs @@ -18,6 +18,7 @@ use super::receiver::Receiver; use crate::{ config, + err::RedisParseErr, messages::Event, parse_client_request::{Stream::Public, Subscription, Timeline}, }; @@ -26,7 +27,6 @@ use futures::{ Poll, }; use std::sync::{Arc, Mutex}; -use tokio::io::Error; use uuid::Uuid; /// Struct for managing all Redis streams. @@ -82,7 +82,7 @@ impl ClientAgent { /// The stream that the `ClientAgent` manages. `Poll` is the only method implemented. impl futures::stream::Stream for ClientAgent { type Item = Event; - type Error = Error; + type Error = RedisParseErr; /// Checks for any new messages that should be sent to the client. /// diff --git a/src/redis_to_client_stream/event_stream.rs b/src/redis_to_client_stream/event_stream.rs index 5157120..ddcf1d3 100644 --- a/src/redis_to_client_stream/event_stream.rs +++ b/src/redis_to_client_stream/event_stream.rs @@ -1,103 +1,101 @@ use super::ClientAgent; -use warp::ws::WebSocket; use futures::{future::Future, stream::Stream, Async}; use log; use std::time::{Duration, Instant}; +use warp::{ + reply::Reply, + sse::Sse, + ws::{Message, WebSocket}, +}; pub struct EventStream; impl EventStream { - - -/// Send a stream of replies to a WebSocket client. + /// Send a stream of replies to a WebSocket client. pub fn to_ws( - socket: WebSocket, - mut client_agent: ClientAgent, - update_interval: Duration, -) -> impl Future { - let (ws_tx, mut ws_rx) = socket.split(); - let timeline = client_agent.subscription.timeline; + ws: WebSocket, + mut client_agent: ClientAgent, + interval: Duration, + ) -> impl Future { + let (ws_tx, mut ws_rx) = ws.split(); + let timeline = client_agent.subscription.timeline; - // Create a pipe - let (tx, rx) = futures::sync::mpsc::unbounded(); + // Create a pipe + let (tx, rx) = futures::sync::mpsc::unbounded(); - // Send one end of it to a different thread and tell that end to forward whatever it gets - // on to the websocket client - warp::spawn( - rx.map_err(|()| -> warp::Error { unreachable!() }) - .forward(ws_tx) - .map(|_r| ()) - .map_err(|e| match e.to_string().as_ref() { - "IO error: Broken pipe (os error 32)" => (), // just closed unix socket - _ => log::warn!("websocket send error: {}", e), - }), - ); + // Send one end of it to a different thread and tell that end to forward whatever it gets + // on to the websocket client + warp::spawn( + rx.map_err(|()| -> warp::Error { unreachable!() }) + .forward(ws_tx) + .map(|_r| ()) + .map_err(|e| match e.to_string().as_ref() { + "IO error: Broken pipe (os error 32)" => (), // just closed unix socket + _ => log::warn!("websocket send error: {}", e), + }), + ); - // Yield new events for as long as the client is still connected - let event_stream = tokio::timer::Interval::new(Instant::now(), update_interval).take_while( - move |_| match ws_rx.poll() { - Ok(Async::NotReady) | Ok(Async::Ready(Some(_))) => futures::future::ok(true), - Ok(Async::Ready(None)) => { + // Yield new events for as long as the client is still connected + let event_stream = + tokio::timer::Interval::new(Instant::now(), interval).take_while(move |_| { + match ws_rx.poll() { + Ok(Async::NotReady) | Ok(Async::Ready(Some(_))) => futures::future::ok(true), + Ok(Async::Ready(None)) => { + // TODO: consider whether we should manually drop closed connections here + log::info!("Client closed WebSocket connection for {:?}", timeline); + futures::future::ok(false) + } + Err(e) if e.to_string() == "IO error: Broken pipe (os error 32)" => { + // no err, just closed Unix socket + log::info!("Client closed WebSocket connection for {:?}", timeline); + futures::future::ok(false) + } + Err(e) => { + log::warn!("Error in {:?}: {}", timeline, e); + futures::future::ok(false) + } + } + }); + + let mut time = Instant::now(); + // Every time you get an event from that stream, send it through the pipe + event_stream + .for_each(move |_instant| { + if let Ok(Async::Ready(Some(msg))) = client_agent.poll() { + tx.unbounded_send(Message::text(msg.to_json_string())) + .expect("No send error"); + }; + if time.elapsed() > Duration::from_secs(30) { + tx.unbounded_send(Message::text("{}")).expect("Can ping"); + time = Instant::now(); + } + Ok(()) + }) + .then(move |result| { // TODO: consider whether we should manually drop closed connections here - log::info!("Client closed WebSocket connection for {:?}", timeline); - futures::future::ok(false) - } - Err(e) if e.to_string() == "IO error: Broken pipe (os error 32)" => { - // no err, just closed Unix socket - log::info!("Client closed WebSocket connection for {:?}", timeline); - futures::future::ok(false) - } - Err(e) => { - log::warn!("Error in {:?}: {}", timeline, e); - futures::future::ok(false) - } - }, - ); + log::info!("WebSocket connection for {:?} closed.", timeline); + result + }) + .map_err(move |e| log::warn!("Error sending to {:?}: {}", timeline, e)) + } + pub fn to_sse(mut client_agent: ClientAgent, sse: Sse, interval: Duration) -> impl Reply { + let event_stream = + tokio::timer::Interval::new(Instant::now(), interval).filter_map(move |_| { + match client_agent.poll() { + Ok(Async::Ready(Some(event))) => Some(( + warp::sse::event(event.event_name()), + warp::sse::data(event.payload().unwrap_or_else(String::new)), + )), + _ => None, + } + }); - let mut time = Instant::now(); - // Every time you get an event from that stream, send it through the pipe - event_stream - .for_each(move |_instant| { - if let Ok(Async::Ready(Some(msg))) = client_agent.poll() { - tx.unbounded_send(warp::ws::Message::text(msg.to_json_string())) - .expect("No send error"); - }; - if time.elapsed() > Duration::from_secs(30) { - tx.unbounded_send(warp::ws::Message::text("{}")) - .expect("Can ping"); - time = Instant::now(); - } - Ok(()) - }) - .then(move |result| { - // TODO: consider whether we should manually drop closed connections here - log::info!("WebSocket connection for {:?} closed.", timeline); - result - }) - .map_err(move |e| log::warn!("Error sending to {:?}: {}", timeline, e)) -} - pub fn to_sse( - mut client_agent: ClientAgent, - connection: warp::sse::Sse, - update_interval: Duration, -) ->impl warp::reply::Reply { - let event_stream = - tokio::timer::Interval::new(Instant::now(), update_interval).filter_map(move |_| { - match client_agent.poll() { - Ok(Async::Ready(Some(event))) => Some(( - warp::sse::event(event.event_name()), - warp::sse::data(event.payload().unwrap_or_else(String::new)), - )), - _ => None, - } - }); - - connection.reply( - warp::sse::keep_alive() - .interval(Duration::from_secs(30)) - .text("thump".to_string()) - .stream(event_stream), - ) -} + sse.reply( + warp::sse::keep_alive() + .interval(Duration::from_secs(30)) + .text("thump".to_string()) + .stream(event_stream), + ) + } } diff --git a/src/redis_to_client_stream/mod.rs b/src/redis_to_client_stream/mod.rs index e4cdfb5..ce7e1c5 100644 --- a/src/redis_to_client_stream/mod.rs +++ b/src/redis_to_client_stream/mod.rs @@ -6,9 +6,12 @@ mod redis; pub use {client_agent::ClientAgent, event_stream::EventStream}; -#[cfg(test)] -pub use receiver::process_messages; -#[cfg(test)] +// TODO remove +pub use redis::redis_msg; + +//#[cfg(test)] +//pub use receiver::process_messages; +//#[cfg(test)] pub use receiver::{MessageQueues, MsgQueue}; -#[cfg(test)] -pub use redis::redis_msg::RedisMsg; +//#[cfg(test)] +//pub use redis::redis_msg::{RedisMsg, RedisUtf8}; diff --git a/src/redis_to_client_stream/receiver/mod.rs b/src/redis_to_client_stream/receiver/mod.rs index 87b35ad..23e5948 100644 --- a/src/redis_to_client_stream/receiver/mod.rs +++ b/src/redis_to_client_stream/receiver/mod.rs @@ -10,75 +10,41 @@ use crate::{ err::RedisParseErr, messages::Event, parse_client_request::{Stream, Timeline}, - pubsub_cmd, - redis_to_client_stream::redis::redis_msg::RedisMsg, - redis_to_client_stream::redis::{redis_cmd, RedisConn}, + redis_to_client_stream::redis::RedisConn, }; use futures::{Async, Poll}; use lru::LruCache; -use tokio::io::AsyncRead; - -use std::{ - collections::HashMap, - io::Read, - net, str, - time::{Duration, Instant}, -}; -use tokio::io::Error; +use std::{collections::HashMap, time::Instant}; use uuid::Uuid; /// The item that streams from Redis and is polled by the `ClientAgent` #[derive(Debug)] pub struct Receiver { - pub pubsub_connection: net::TcpStream, - secondary_redis_connection: net::TcpStream, - redis_poll_interval: Duration, - redis_polled_at: Instant, + redis_connection: RedisConn, timeline: Timeline, manager_id: Uuid, pub msg_queues: MessageQueues, clients_per_timeline: HashMap, - cache: Cache, - redis_input: Vec, - redis_namespace: Option, -} - -#[derive(Debug)] -pub struct Cache { + hashtag_cache: LruCache, // TODO: eventually, it might make sense to have Mastodon publish to timelines with // the tag number instead of the tag name. This would save us from dealing // with a cache here and would be consistent with how lists/users are handled. - id_to_hashtag: LruCache, - pub hashtag_to_id: LruCache, } impl Receiver { /// Create a new `Receiver`, with its own Redis connections (but, as yet, no /// active subscriptions). pub fn new(redis_cfg: config::RedisConfig) -> Self { - let redis_namespace = redis_cfg.namespace.clone(); - - let RedisConn { - primary: pubsub_connection, - secondary: secondary_redis_connection, - polling_interval: redis_poll_interval, - } = RedisConn::new(redis_cfg); + let redis_connection = RedisConn::new(redis_cfg); Self { - pubsub_connection, - secondary_redis_connection, - redis_poll_interval, - redis_polled_at: Instant::now(), + redis_connection, timeline: Timeline::empty(), manager_id: Uuid::default(), msg_queues: MessageQueues(HashMap::new()), clients_per_timeline: HashMap::new(), - cache: Cache { - id_to_hashtag: LruCache::new(1000), - hashtag_to_id: LruCache::new(1000), - }, // should these be run-time options? - redis_input: Vec::new(), - redis_namespace, + hashtag_cache: LruCache::new(1000), + // should this be a run-time option? } } @@ -91,8 +57,8 @@ impl Receiver { pub fn manage_new_timeline(&mut self, id: Uuid, tl: Timeline, hashtag: Option) { self.timeline = tl; if let (Some(hashtag), Timeline(Stream::Hashtag(id), _, _)) = (hashtag, tl) { - self.cache.id_to_hashtag.put(id, hashtag.clone()); - self.cache.hashtag_to_id.put(hashtag, id); + self.hashtag_cache.put(id, hashtag.clone()); + self.redis_connection.update_cache(hashtag, id); }; self.msg_queues.insert(id, MsgQueue::new(tl)); @@ -117,7 +83,7 @@ impl Receiver { for change in timelines_to_modify { let timeline = change.timeline; let hashtag = match timeline { - Timeline(Stream::Hashtag(id), _, _) => self.cache.id_to_hashtag.get(&id), + Timeline(Stream::Hashtag(id), _, _) => self.hashtag_cache.get(&id), _non_hashtag_timeline => None, }; @@ -129,9 +95,11 @@ impl Receiver { // If no clients, unsubscribe from the channel if *count_of_subscribed_clients <= 0 { - pubsub_cmd!("unsubscribe", self, timeline.to_redis_raw_timeline(hashtag)); + self.redis_connection + .send_unsubscribe_cmd(&timeline.to_redis_raw_timeline(hashtag)); } else if *count_of_subscribed_clients == 1 && change.in_subscriber_number == 1 { - pubsub_cmd!("subscribe", self, timeline.to_redis_raw_timeline(hashtag)); + self.redis_connection + .send_subscribe_cmd(&timeline.to_redis_raw_timeline(hashtag)); } } if start_time.elapsed().as_millis() > 1 { @@ -143,7 +111,7 @@ impl Receiver { /// The stream that the ClientAgent polls to learn about new messages. impl futures::stream::Stream for Receiver { type Item = Event; - type Error = Error; + type Error = RedisParseErr; /// Returns the oldest message in the `ClientAgent`'s queue (if any). /// @@ -153,27 +121,18 @@ impl futures::stream::Stream for Receiver { /// been polled lately. fn poll(&mut self) -> Poll, Self::Error> { let (timeline, id) = (self.timeline.clone(), self.manager_id); - - if self.redis_polled_at.elapsed() > self.redis_poll_interval { - let mut buffer = vec![0u8; 6000]; - if let Ok(Async::Ready(bytes_read)) = self.poll_read(&mut buffer) { - let binary_input = buffer[..bytes_read].to_vec(); - let (input, extra_bytes) = match str::from_utf8(&binary_input) { - Ok(input) => (input, "".as_bytes()), - Err(e) => { - let (valid, after_valid) = binary_input.split_at(e.valid_up_to()); - let input = str::from_utf8(valid).expect("Guaranteed by `.valid_up_to`"); - (input, after_valid) - } - }; - - let (cache, namespace) = (&mut self.cache.hashtag_to_id, &self.redis_namespace); - - let remaining_input = - process_messages(input, cache, namespace, &mut self.msg_queues); - - self.redis_input.extend_from_slice(remaining_input); - self.redis_input.extend_from_slice(extra_bytes); + loop { + match self.redis_connection.poll_redis() { + Ok(Async::Ready(Some((timeline, event)))) => self + .msg_queues + .values_mut() + .filter(|msg_queue| msg_queue.timeline == timeline) + .for_each(|msg_queue| { + msg_queue.messages.push_back(event.clone()); + }), + Ok(Async::NotReady) => break, + Ok(Async::Ready(None)) => (), + Err(err) => Err(err)?, } } @@ -187,49 +146,3 @@ impl futures::stream::Stream for Receiver { } } } - -impl Read for Receiver { - fn read(&mut self, buffer: &mut [u8]) -> Result { - self.pubsub_connection.read(buffer) - } -} - -impl AsyncRead for Receiver { - fn poll_read(&mut self, buf: &mut [u8]) -> Poll { - match self.read(buf) { - Ok(t) => Ok(Async::Ready(t)), - Err(_) => Ok(Async::NotReady), - } - } -} - -#[must_use] -pub fn process_messages<'a>( - input: &'a str, - mut cache: &mut LruCache, - namespace: &Option, - msg_queues: &mut MessageQueues, -) -> &'a [u8] { - let mut remaining_input = input; - use RedisMsg::*; - loop { - match RedisMsg::from_raw(&mut remaining_input, &mut cache, namespace) { - Ok((EventMsg(timeline, event), rest)) => { - for msg_queue in msg_queues.values_mut() { - if msg_queue.timeline == timeline { - msg_queue.messages.push_back(event.clone()); - } - } - remaining_input = rest; - } - Ok((SubscriptionMsg, rest)) | Ok((MsgForDifferentNamespace, rest)) => { - remaining_input = rest; - } - Err(RedisParseErr::Incomplete) => break, - Err(RedisParseErr::Unrecoverable) => { - panic!("Failed parsing Redis msg: {}", &remaining_input) - } - }; - } - remaining_input.as_bytes() -} diff --git a/src/redis_to_client_stream/redis/redis_cmd.rs b/src/redis_to_client_stream/redis/redis_cmd.rs index f353ffc..f7f24a4 100644 --- a/src/redis_to_client_stream/redis/redis_cmd.rs +++ b/src/redis_to_client_stream/redis/redis_cmd.rs @@ -10,7 +10,7 @@ macro_rules! pubsub_cmd { let namespace = $self.redis_namespace.clone(); $self - .pubsub_connection + .primary .write_all(&redis_cmd::pubsub($cmd, $tl, namespace.clone())) .expect("Can send command to Redis"); // Because we keep track of the number of clients subscribed to a channel on our end, @@ -21,7 +21,7 @@ macro_rules! pubsub_cmd { _ => panic!("Given unacceptable PUBSUB command"), }; $self - .secondary_redis_connection + .secondary .write_all(&redis_cmd::set( format!("subscribed:{}", $tl), subscription_new_number, @@ -29,7 +29,7 @@ macro_rules! pubsub_cmd { )) .expect("Can set Redis"); - log::info!("Now subscribed to: {:#?}", $self.msg_queues); + // TODO: re-enable info logging >>> log::info!("Now subscribed to: {:#?}", $self.msg_queues); }}; } /// Send a `SUBSCRIBE` or `UNSUBSCRIBE` command to a specific timeline diff --git a/src/redis_to_client_stream/redis/redis_connection.rs b/src/redis_to_client_stream/redis/redis_connection.rs index 8261695..2689786 100644 --- a/src/redis_to_client_stream/redis/redis_connection.rs +++ b/src/redis_to_client_stream/redis/redis_connection.rs @@ -1,12 +1,132 @@ -use super::redis_cmd; -use crate::config::RedisConfig; -use crate::err; -use std::{io::Read, io::Write, net, time::Duration}; +use super::{redis_cmd, redis_msg::RedisParseOutput}; +use crate::{ + config::RedisConfig, + err::{self, RedisParseErr}, + messages::Event, + parse_client_request::Timeline, + pubsub_cmd, +}; +use futures::{Async, Poll}; +use lru::LruCache; +use std::{ + convert::TryFrom, + io::Read, + io::Write, + net, str, + time::{Duration, Instant}, +}; +use tokio::io::AsyncRead; + +#[derive(Debug)] pub struct RedisConn { - pub primary: net::TcpStream, - pub secondary: net::TcpStream, - pub polling_interval: Duration, + primary: net::TcpStream, + secondary: net::TcpStream, + redis_poll_interval: Duration, + redis_polled_at: Instant, + redis_namespace: Option, + cache: LruCache, + redis_input: Vec, // TODO: Consider queue internal to RedisConn +} + +impl RedisConn { + pub fn new(redis_cfg: RedisConfig) -> Self { + let addr = format!("{}:{}", *redis_cfg.host, *redis_cfg.port); + let conn_err = |e| { + err::die_with_msg(format!( + "Could not connect to Redis at {}:{}.\n Error detail: {}", + *redis_cfg.host, *redis_cfg.port, e, + )) + }; + let update_conn = |mut conn| { + if let Some(password) = redis_cfg.password.clone() { + conn = send_password(conn, &password); + } + conn = send_test_ping(conn); + conn.set_read_timeout(Some(Duration::from_millis(10))) + .expect("Can set read timeout for Redis connection"); + if let Some(db) = &*redis_cfg.db { + conn = set_db(conn, db); + } + conn + }; + let (primary_conn, secondary_conn) = ( + update_conn(net::TcpStream::connect(addr.clone()).unwrap_or_else(conn_err)), + update_conn(net::TcpStream::connect(addr).unwrap_or_else(conn_err)), + ); + primary_conn + .set_nonblocking(true) + .expect("set_nonblocking call failed"); + + Self { + primary: primary_conn, + secondary: secondary_conn, + cache: LruCache::new(1000), + redis_namespace: redis_cfg.namespace.clone(), + redis_poll_interval: *redis_cfg.polling_interval, + redis_input: Vec::new(), + redis_polled_at: Instant::now(), + } + } + + pub fn poll_redis(&mut self) -> Poll, RedisParseErr> { + let mut buffer = vec![0u8; 6000]; + if self.redis_polled_at.elapsed() > self.redis_poll_interval { + if let Ok(Async::Ready(bytes_read)) = self.poll_read(&mut buffer) { + self.redis_input.extend_from_slice(&buffer[..bytes_read]); + } + } + let input = self.redis_input.clone(); + self.redis_input.clear(); + + let (input, invalid_bytes) = str::from_utf8(&input) + .map(|input| (input, "".as_bytes())) + .unwrap_or_else(|e| { + let (valid, invalid) = input.split_at(e.valid_up_to()); + (str::from_utf8(valid).expect("Guaranteed by ^^^^"), invalid) + }); + + use {Async::*, RedisParseOutput::*}; + let (res, leftover) = match RedisParseOutput::try_from(input) { + Ok(Msg(msg)) => match &self.redis_namespace { + Some(ns) if msg.timeline_txt.starts_with(&format!("{}:timeline:", ns)) => { + let tl = Timeline::from_redis_text( + &msg.timeline_txt[ns.len() + ":timeline:".len()..], + &mut self.cache, + )?; + let event: Event = serde_json::from_str(msg.event_txt)?; + (Ok(Ready(Some((tl, event)))), msg.leftover_input) + } + None => { + let tl = Timeline::from_redis_text( + &msg.timeline_txt["timeline:".len()..], + &mut self.cache, + )?; + + let event: Event = serde_json::from_str(msg.event_txt)?; + (Ok(Ready(Some((tl, event)))), msg.leftover_input) + } + Some(_non_matching_namespace) => (Ok(Ready(None)), msg.leftover_input), + }, + Ok(NonMsg(leftover)) => (Ok(Ready(None)), leftover), + Err(RedisParseErr::Incomplete) => (Ok(NotReady), input), + Err(other) => (Err(other), input), + }; + self.redis_input.extend_from_slice(leftover.as_bytes()); + self.redis_input.extend_from_slice(invalid_bytes); + res + } + + pub fn update_cache(&mut self, hashtag: String, id: i64) { + self.cache.put(hashtag, id); + } + + pub fn send_unsubscribe_cmd(&mut self, timeline: &str) { + pubsub_cmd!("unsubscribe", self, timeline); + } + pub fn send_subscribe_cmd(&mut self, timeline: &str) { + pubsub_cmd!("subscribe", self, timeline); + } } fn send_password(mut conn: net::TcpStream, password: &str) -> net::TcpStream { @@ -53,39 +173,17 @@ fn send_test_ping(mut conn: net::TcpStream) -> net::TcpStream { conn } -impl RedisConn { - pub fn new(redis_cfg: RedisConfig) -> Self { - let addr = format!("{}:{}", *redis_cfg.host, *redis_cfg.port); - let conn_err = |e| { - err::die_with_msg(format!( - "Could not connect to Redis at {}:{}.\n Error detail: {}", - *redis_cfg.host, *redis_cfg.port, e, - )) - }; - let update_conn = |mut conn| { - if let Some(password) = redis_cfg.password.clone() { - conn = send_password(conn, &password); - } - conn = send_test_ping(conn); - conn.set_read_timeout(Some(Duration::from_millis(10))) - .expect("Can set read timeout for Redis connection"); - if let Some(db) = &*redis_cfg.db { - conn = set_db(conn, db); - } - conn - }; - let (primary_conn, secondary_conn) = ( - update_conn(net::TcpStream::connect(addr.clone()).unwrap_or_else(conn_err)), - update_conn(net::TcpStream::connect(addr).unwrap_or_else(conn_err)), - ); - primary_conn - .set_nonblocking(true) - .expect("set_nonblocking call failed"); +impl Read for RedisConn { + fn read(&mut self, buffer: &mut [u8]) -> Result { + self.primary.read(buffer) + } +} - Self { - primary: primary_conn, - secondary: secondary_conn, - polling_interval: *redis_cfg.polling_interval, +impl AsyncRead for RedisConn { + fn poll_read(&mut self, buf: &mut [u8]) -> Poll { + match self.read(buf) { + Ok(t) => Ok(Async::Ready(t)), + Err(_) => Ok(Async::NotReady), } } } diff --git a/src/redis_to_client_stream/redis/redis_msg.rs b/src/redis_to_client_stream/redis/redis_msg.rs index d0ef023..3ce0f65 100644 --- a/src/redis_to_client_stream/redis/redis_msg.rs +++ b/src/redis_to_client_stream/redis/redis_msg.rs @@ -9,8 +9,10 @@ //! //! ```text //! *3\r\n -//! $7\r\nmessage\r\n -//! $10\r\ntimeline:4\r\n +//! $7\r\n +//! message\r\n +//! $10\r\n +//! timeline:4\r\n //! $1386\r\n{\"event\":\"update\",\"payload\"...\"queued_at\":1569623342825}\r\n //! ``` //! @@ -18,93 +20,236 @@ //! three characters, the second is a bulk string with ten characters, and the third is a //! bulk string with 1,386 characters. -use crate::{ - err::{RedisParseErr, TimelineErr}, - messages::Event, - parse_client_request::Timeline, +use self::RedisParseOutput::*; +use crate::err::RedisParseErr; +use std::{ + convert::{TryFrom, TryInto}, + str, }; -use lru::LruCache; -type Parser<'a, Item> = Result<(Item, &'a str), RedisParseErr>; - -/// A message that has been parsed from an incoming raw message from Redis. -#[derive(Debug, Clone)] -pub enum RedisMsg { - EventMsg(Timeline, Event), - SubscriptionMsg, - MsgForDifferentNamespace, +#[derive(Debug, Clone, PartialEq)] +pub enum RedisParseOutput<'a> { + Msg(RedisMsg<'a>), + NonMsg(&'a str), } -use RedisParseErr::*; -type Hashtags = LruCache; -impl RedisMsg { - pub fn from_raw<'a>( - input: &'a str, - cache: &mut Hashtags, - namespace: &Option, - ) -> Parser<'a, Self> { - // No need to parse the Redis Array header, just skip it - let input = input.get("*3\r\n".len()..).ok_or(Incomplete)?; - let (command, rest) = parse_redis_bulk_string(&input)?; - match command { - "message" => { - // Messages look like; - // $10\r\ntimeline:4\r\n - // $1386\r\n{\"event\":\"update\",\"payload\"...\"queued_at\":1569623342825}\r\n - let (timeline, rest) = parse_redis_bulk_string(&rest)?; - let (msg_txt, rest) = parse_redis_bulk_string(&rest)?; - let event: Event = serde_json::from_str(&msg_txt).map_err(|_| Unrecoverable)?; +#[derive(Debug, Clone, PartialEq)] +pub struct RedisMsg<'a> { + pub timeline_txt: &'a str, + pub event_txt: &'a str, + pub leftover_input: &'a str, +} - use TimelineErr::*; - match Timeline::from_redis_raw_timeline(timeline, cache, namespace) { - Ok(timeline) => Ok((Self::EventMsg(timeline, event), rest)), - Err(RedisNamespaceMismatch) => Ok((Self::MsgForDifferentNamespace, rest)), - Err(InvalidInput) => Err(RedisParseErr::Unrecoverable), - } - } - "subscribe" | "unsubscribe" => { - // subscription statuses look like: - // $14\r\ntimeline:local\r\n - // :47\r\n - let (_raw_timeline, rest) = parse_redis_bulk_string(&rest)?; - let (_number_of_subscriptions, rest) = parse_redis_int(&rest)?; - Ok((Self::SubscriptionMsg, &rest)) - } - _cmd => Err(Incomplete)?, - } +impl<'a> TryFrom<&'a str> for RedisParseOutput<'a> { + type Error = RedisParseErr; + fn try_from(utf8: &'a str) -> Result, Self::Error> { + let (structured_txt, leftover_utf8) = utf8_to_redis_data(utf8)?; + let structured_txt = RedisStructuredText { + structured_txt, + leftover_input: leftover_utf8, + }; + Ok(structured_txt.try_into()?) } } +#[derive(Debug, Clone, PartialEq)] +struct RedisStructuredText<'a> { + structured_txt: RedisData<'a>, + leftover_input: &'a str, +} +#[derive(Debug, Clone, PartialEq)] +enum RedisData<'a> { + RedisArray(Vec>), + BulkString(&'a str), + Integer(usize), + Uninitilized, +} + +use RedisData::*; +use RedisParseErr::*; +type RedisParser<'a, Item> = Result; +fn utf8_to_redis_data<'a>(s: &'a str) -> Result<(RedisData, &'a str), RedisParseErr> { + if s.len() < 4 { + Err(Incomplete)? + }; + let (first_char, s) = s.split_at(1); + match first_char { + ":" => parse_redis_int(s), + "$" => parse_redis_bulk_string(s), + "*" => parse_redis_array(s), + e => Err(InvalidLineStart(format!( + "Encountered invalid initial character `{}` in line `{}`", + e, s + ))), + } +} + +fn after_newline_at<'a>(s: &'a str, start: usize) -> RedisParser<'a, &'a str> { + let s = s.get(start..).ok_or(Incomplete)?; + if !s.starts_with("\r\n") { + return Err(RedisParseErr::InvalidLineEnd); + } + Ok(s.get("\r\n".len()..).ok_or(Incomplete)?) +} + +fn parse_number_at<'a>(s: &'a str) -> RedisParser<(usize, &'a str)> { + let len = s + .chars() + .position(|c| !c.is_numeric()) + .ok_or(NonNumericInput)?; + Ok((s[..len].parse()?, after_newline_at(s, len)?)) +} + /// Parse a Redis bulk string and return the content of that string and the unparsed remainder. /// /// All bulk strings have the format `$[LENGTH_OF_ITEM_BODY]\r\n[ITEM_BODY]\r\n` -fn parse_redis_bulk_string(input: &str) -> Parser<&str> { - let input = &input.get("$".len()..).ok_or(Incomplete)?; - let (field_len, rest) = parse_redis_length(input)?; - let field_content = rest.get(..field_len).ok_or(Incomplete)?; - Ok((field_content, &rest[field_len + "\r\n".len()..])) +fn parse_redis_bulk_string<'a>(s: &'a str) -> RedisParser<(RedisData, &'a str)> { + let (len, rest) = parse_number_at(s)?; + let content = rest.get(..len).ok_or(Incomplete)?; + Ok((BulkString(content), after_newline_at(&rest, len)?)) } -fn parse_redis_int(input: &str) -> Parser { - let input = &input.get(":".len()..).ok_or(Incomplete)?; - let (number, rest_with_newline) = parse_number_at(input)?; - let rest = &rest_with_newline.get("\r\n".len()..).ok_or(Incomplete)?; - Ok((number, rest)) +fn parse_redis_int<'a>(s: &'a str) -> RedisParser<(RedisData, &'a str)> { + let (number, rest) = parse_number_at(s)?; + Ok((Integer(number), rest)) } -/// Return the value of a Redis length (for an array or bulk string) and the unparsed remainder -fn parse_redis_length(input: &str) -> Parser { - let (number, rest_with_newline) = parse_number_at(input)?; - let rest = &rest_with_newline.get("\r\n".len()..).ok_or(Incomplete)?; - Ok((number, rest)) +fn parse_redis_array<'a>(s: &'a str) -> RedisParser<(RedisData, &'a str)> { + let (number_of_elements, mut rest) = parse_number_at(s)?; + + let mut inner = Vec::with_capacity(number_of_elements); + inner.resize(number_of_elements, RedisData::Uninitilized); + + for i in (0..number_of_elements).rev() { + let (next_el, new_rest) = utf8_to_redis_data(rest)?; + rest = new_rest; + inner[i] = next_el; + } + Ok((RedisData::RedisArray(inner), rest)) } -fn parse_number_at(input: &str) -> Parser { - let number_len = input - .chars() - .position(|c| !c.is_numeric()) - .ok_or(Unrecoverable)?; - let number = input[..number_len].parse().map_err(|_| Unrecoverable)?; - let rest = &input.get(number_len..).ok_or(Incomplete)?; - Ok((number, rest)) +impl<'a> TryFrom> for &'a str { + type Error = RedisParseErr; + + fn try_from(val: RedisData<'a>) -> Result { + match val { + RedisData::BulkString(inner) => Ok(inner), + _ => Err(IncorrectRedisType), + } + } } + +impl<'a> TryFrom> for RedisParseOutput<'a> { + type Error = RedisParseErr; + + fn try_from(input: RedisStructuredText<'a>) -> Result, Self::Error> { + if let RedisData::RedisArray(mut redis_strings) = input.structured_txt { + let command = redis_strings.pop().ok_or(MissingField)?.try_into()?; + match command { + // subscription statuses look like: + // $14\r\ntimeline:local\r\n + // :47\r\n + "subscribe" | "unsubscribe" => Ok(NonMsg(input.leftover_input)), + // Messages look like; + // $10\r\ntimeline:4\r\n + // $1386\r\n{\"event\":\"update\",\"payload\"...\"queued_at\":1569623342825}\r\n + "message" => Ok(Msg(RedisMsg { + timeline_txt: redis_strings.pop().ok_or(MissingField)?.try_into()?, + event_txt: redis_strings.pop().ok_or(MissingField)?.try_into()?, + leftover_input: input.leftover_input, + })), + _cmd => Err(Incomplete), + } + } else { + Err(IncorrectRedisType) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse_redis_subscribe() -> Result<(), RedisParseErr> { + let input = "*3\r\n$9\r\nsubscribe\r\n$15\r\ntimeline:public\r\n:1\r\n"; + + let r_subscribe = match RedisParseOutput::try_from(input) { + Ok(NonMsg(leftover)) => leftover, + Ok(Msg(msg)) => panic!("unexpectedly got a msg: {:?}", msg), + Err(e) => panic!("Error in parsing subscribe command: {:?}", e), + }; + assert!(r_subscribe.is_empty()); + + Ok(()) + } + + #[test] + fn parse_redis_detects_non_newline() -> Result<(), RedisParseErr> { + let input = + "*3QQ$7\r\nmessage\r\n$12\r\ntimeline:308\r\n$38\r\n{\"event\":\"delete\",\"payload\":\"1038647\"}\r\n"; + + match RedisParseOutput::try_from(input) { + Ok(NonMsg(leftover)) => panic!( + "Parsed an invalid msg as a non-msg.\nInput `{}` parsed to NonMsg({:?})", + &input, leftover + ), + Ok(Msg(msg)) => panic!( + "Parsed an invalid msg as a msg.\nInput `{:?}` parsed to {:?}", + &input, msg + ), + Err(_) => (), // should err + }; + + Ok(()) + } + + fn parse_redis_msg() -> Result<(), RedisParseErr> { + let input = + "*3\r\n$7\r\nmessage\r\n$12\r\ntimeline:308\r\n$38\r\n{\"event\":\"delete\",\"payload\":\"1038647\"}\r\n"; + + let r_msg = match RedisParseOutput::try_from(input) { + Ok(NonMsg(leftover)) => panic!( + "Parsed a msg as a non-msg.\nInput `{}` parsed to NonMsg({:?})", + &input, leftover + ), + Ok(Msg(msg)) => msg, + Err(e) => panic!("Error in parsing subscribe command: {:?}", e), + }; + + assert!(r_msg.leftover_input.is_empty()); + assert_eq!(r_msg.timeline_txt, "timeline:308"); + assert_eq!(r_msg.event_txt, r#"{"event":"delete","payload":"1038647"}"#); + Ok(()) + } +} + +// #[derive(Debug, Clone, PartialEq, Copy)] +// pub struct RedisUtf8<'a> { +// pub valid_utf8: &'a str, +// pub leftover_bytes: &'a [u8], +// } + +// impl<'a> From<&'a [u8]> for RedisUtf8<'a> { +// fn from(bytes: &'a [u8]) -> Self { +// match str::from_utf8(bytes) { +// Ok(valid_utf8) => Self { +// valid_utf8, +// leftover_bytes: "".as_bytes(), +// }, +// Err(e) => { +// let (valid, after_valid) = bytes.split_at(e.valid_up_to()); +// Self { +// valid_utf8: str::from_utf8(valid).expect("Guaranteed by `.valid_up_to`"), +// leftover_bytes: after_valid, +// } +// } +// } +// } +// } + +// impl<'a> Default for RedisUtf8<'a> { +// fn default() -> Self { +// Self::from("".as_bytes()) +// } +// }