diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 6e24602..98ecc77 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -6,6 +6,10 @@ on: - "*" pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + name: CI jobs: lint: diff --git a/deny.toml b/deny.toml index 052dbad..3ab34f6 100644 --- a/deny.toml +++ b/deny.toml @@ -15,11 +15,8 @@ ignore = [] [bans] multiple-versions = "deny" -deny = [] -skip = [ - # clap uses an old version - { name = "ansi_term", version = "=0.11.0" }, -] +deny = [{ name = "chrono" }] +skip = [] skip-tree = [] [sources] @@ -38,8 +35,7 @@ allow-osi-fsf-free = "neither" confidence-threshold = 0.92 copyleft = "deny" allow = [ - "Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0) - "BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised) - "MIT", # https://tldrlegal.com/license/mit-license + "Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0) + "MIT", # https://tldrlegal.com/license/mit-license ] exceptions = [] diff --git a/examples-shared/Cargo.toml b/examples-shared/Cargo.toml index 7ce20e8..1aec6b2 100644 --- a/examples-shared/Cargo.toml +++ b/examples-shared/Cargo.toml @@ -13,4 +13,4 @@ anyhow = "1.0" discord-sdk = { path = "../sdk" } tokio = { version = "1.8.2", features = ["macros"] } tracing = "0.1" -tracing-subscriber = "0.2" +tracing-subscriber = "0.3" diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 55c33cc..2d11336 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -29,16 +29,17 @@ async-trait = "0.1" # in base64 base64 = "0.13" bitflags = "1.2" -chrono = "0.4" crossbeam-channel = "0.5" num-traits = "0.2" # Better sync primitives -parking_lot = "0.11" +parking_lot = "0.12" serde = { version = "1.0", features = ["derive", "rc"] } # All message payloads are JSON serde_json = "1.0" # Some enums are encoded as numbers in JSON serde_repr = "0.1" +# Datetime types +time = "0.3" # Error helpers thiserror = "1.0" # Tokio is used to drive the IPC I/O as well as provide the core of the overall @@ -65,5 +66,5 @@ winreg = "0.10" [dev-dependencies] # So tests can print out tracing -tracing-subscriber = "0.2" +tracing-subscriber = "0.3" insta = "1.7" diff --git a/sdk/src/activity.rs b/sdk/src/activity.rs index f2e5e20..8eeb2a3 100644 --- a/sdk/src/activity.rs +++ b/sdk/src/activity.rs @@ -43,9 +43,9 @@ impl IntoTimestamp for std::time::SystemTime { } } -impl IntoTimestamp for chrono::DateTime { +impl IntoTimestamp for time::OffsetDateTime { fn into_timestamp(self) -> i64 { - self.timestamp() + self.unix_timestamp() } } @@ -230,13 +230,14 @@ pub struct InviteActivity { pub session_id: String, /// The timestamp the activity was created #[serde(skip_serializing, with = "crate::util::datetime_opt")] - pub created_at: Option>, + pub created_at: Option, /// The usual activity data #[serde(flatten)] pub details: Activity, } #[derive(Debug, Deserialize)] +#[allow(dead_code)] pub struct SetActivity { #[serde(flatten)] activity: Activity, diff --git a/sdk/src/io.rs b/sdk/src/io.rs index a0bb5ae..1f18c0f 100644 --- a/sdk/src/io.rs +++ b/sdk/src/io.rs @@ -283,81 +283,78 @@ pub(crate) fn start_io_task(app_id: i64) -> IoTask { if ready.is_readable() { 'read: loop { - let mut buf = match &valid_header { + let buf = match &valid_header { Some((_, len)) => &mut data_buf[data_cursor..*len as usize], None => &mut header_buf.buf[header_buf.cursor..], }; - match stream.try_read(&mut buf) { + match stream.try_read(buf) { Ok(n) => { if n == 0 { return Err(Error::NoConnection); } - match valid_header { - Some((op, len)) => { - data_cursor += n; - let len = len as usize; - if data_cursor == len { - match op { - OpCode::Close => { - let close: types::CloseFrame<'_> = - serde_json::from_slice(&data_buf)?; - - tracing::debug!("Received close request from Discord: {:?} - {:?}", close.code, close.message); - return Err(Error::Close( - close - .message - .unwrap_or("unknown reason") - .to_owned(), - )); - } - OpCode::Frame => { - if rtx - .send(IoMsg::Frame(data_buf.clone())) - .await - .is_err() - { - tracing::error!( - "Dropped RPC as queue is too full" - ); - } - } - OpCode::Ping => { - let pong_response = - make_message(OpCode::Pong, &data_buf); - tracing::debug!( - "Responding to PING request from Discord" - ); - stx.send(Some(pong_response))?; - } - OpCode::Pong => { - tracing::debug!( - "Received PONG response from Discord" + if let Some((op, len)) = valid_header { + data_cursor += n; + let len = len as usize; + if data_cursor == len { + match op { + OpCode::Close => { + let close: types::CloseFrame<'_> = + serde_json::from_slice(&data_buf)?; + + tracing::debug!("Received close request from Discord: {:?} - {:?}", close.code, close.message); + return Err(Error::Close( + close + .message + .unwrap_or("unknown reason") + .to_owned(), + )); + } + OpCode::Frame => { + if rtx + .send(IoMsg::Frame(data_buf.clone())) + .await + .is_err() + { + tracing::error!( + "Dropped RPC as queue is too full" ); } - OpCode::Handshake => { - tracing::error!("Received a HANDSHAKE request from Discord, the stream is likely corrupt"); - return Err(Error::CorruptConnection); - } } - - valid_header = None; - header_buf.cursor = 0; - data_buf.clear(); - data_cursor = 0; + OpCode::Ping => { + let pong_response = + make_message(OpCode::Pong, &data_buf); + tracing::debug!( + "Responding to PING request from Discord" + ); + stx.send(Some(pong_response))?; + } + OpCode::Pong => { + tracing::debug!( + "Received PONG response from Discord" + ); + } + OpCode::Handshake => { + tracing::error!("Received a HANDSHAKE request from Discord, the stream is likely corrupt"); + return Err(Error::CorruptConnection); + } } + + valid_header = None; + header_buf.cursor = 0; + data_buf.clear(); + data_cursor = 0; } - None => { - header_buf.cursor += n; - if header_buf.cursor == header_buf.buf.len() { - let header = parse_frame_header(header_buf.buf)?; + } else { + header_buf.cursor += n; + if header_buf.cursor == header_buf.buf.len() { + let header = parse_frame_header(header_buf.buf)?; - // Ensure the data buffer has enough space - data_buf.resize(header.1 as usize, 0); + // Ensure the data buffer has enough space + data_buf.resize(header.1 as usize, 0); - valid_header = Some(header); - } + valid_header = Some(header); } } } @@ -374,12 +371,11 @@ pub(crate) fn start_io_task(app_id: i64) -> IoTask { if ready.is_writable() { if top_message.is_none() { if let Ok(msg) = srx.try_recv() { - top_message = match msg { - Some(msg) => Some((msg, 0)), - None => { - tracing::debug!("Discord I/O thread received shutdown signal"); - return Ok(()); - } + top_message = if let Some(msg) = msg { + Some((msg, 0)) + } else { + tracing::debug!("Discord I/O thread received shutdown signal"); + return Ok(()); }; } } diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 65d89d7..f9e0b4d 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -1,5 +1,5 @@ #![doc = include_str!("../README.md")] -// BEGIN - Embark standard lints v0.4 +// BEGIN - Embark standard lints v5 for Rust 1.55+ // do not change or add/remove here, but one can add exceptions after this section // for more info see: #![deny(unsafe_code)] @@ -19,13 +19,17 @@ clippy::explicit_into_iter_loop, clippy::fallible_impl_from, clippy::filter_map_next, + clippy::flat_map_option, clippy::float_cmp_const, clippy::fn_params_excessive_bools, + clippy::from_iter_instead_of_collect, clippy::if_let_mutex, clippy::implicit_clone, clippy::imprecise_flops, clippy::inefficient_to_string, clippy::invalid_upcast_comparisons, + clippy::large_digit_groups, + clippy::large_stack_arrays, clippy::large_types_passed_by_value, clippy::let_unit_value, clippy::linkedlist, @@ -37,20 +41,25 @@ clippy::map_unwrap_or, clippy::match_on_vec_items, clippy::match_same_arms, + clippy::match_wild_err_arm, clippy::match_wildcard_for_single_variants, clippy::mem_forget, clippy::mismatched_target_os, + clippy::missing_enforced_import_renames, clippy::mut_mut, clippy::mutex_integer, clippy::needless_borrow, clippy::needless_continue, + clippy::needless_for_each, clippy::option_option, clippy::path_buf_push_overwrite, clippy::ptr_as_ptr, + clippy::rc_mutex, clippy::ref_option_ref, clippy::rest_pat_in_fully_bound_structs, clippy::same_functions_in_if_condition, clippy::semicolon_if_nothing_returned, + clippy::single_match_else, clippy::string_add_assign, clippy::string_add, clippy::string_lit_as_bytes, @@ -67,9 +76,8 @@ nonstandard_style, rust_2018_idioms )] -// END - Embark standard lints v0.4 +// END - Embark standard lints v0.5 for Rust 1.55+ // crate-specific exceptions: -#![allow()] #[macro_use] mod util; @@ -90,6 +98,7 @@ pub use error::{DiscordApiErr, DiscordErr, Error}; pub use handler::{handlers, wheel, DiscordHandler, DiscordMsg}; pub use proto::event::Event; use proto::{Command, CommandKind}; +pub use time::OffsetDateTime; pub use types::Snowflake; pub type AppId = i64; diff --git a/sdk/src/relations.rs b/sdk/src/relations.rs index 65cd2c2..2a14ed0 100644 --- a/sdk/src/relations.rs +++ b/sdk/src/relations.rs @@ -52,13 +52,13 @@ pub struct RelationshipActivityTimestamps { with = "crate::util::datetime_opt", default )] - pub start: Option>, + pub start: Option, #[serde( skip_serializing_if = "Option::is_none", with = "crate::util::datetime_opt", default )] - pub end: Option>, + pub end: Option, } use crate::activity; @@ -71,7 +71,7 @@ pub struct RelationshipActivity { pub session_id: Option, /// The timestamp the activity was created #[serde(skip_serializing, with = "crate::util::datetime_opt")] - pub created_at: Option>, + pub created_at: Option, /// The player's current party status #[serde(skip_serializing_if = "Option::is_none")] pub state: Option, diff --git a/sdk/src/types.rs b/sdk/src/types.rs index 9fb9b86..6a817ae 100644 --- a/sdk/src/types.rs +++ b/sdk/src/types.rs @@ -12,6 +12,7 @@ pub(crate) struct CloseFrame<'frame> { #[derive(Deserialize, Debug)] #[cfg_attr(test, derive(Serialize))] +#[allow(dead_code)] pub struct ErrorPayload { code: Option, message: Option, @@ -56,13 +57,11 @@ pub struct DiscordConfig { pub struct Snowflake(pub u64); impl Snowflake { - pub fn timestamp(self) -> chrono::DateTime { + pub fn timestamp(self) -> time::OffsetDateTime { let millis = self.0.overflowing_shr(22).0 + 1420070400000; - let ts_seconds = millis / 1000; - let ts_nanos = (millis % 1000) as u32 * 1000000; + let nanos = millis as i128 * 1000000; - use chrono::TimeZone; - chrono::Utc.timestamp(ts_seconds as i64, ts_nanos) + time::OffsetDateTime::from_unix_timestamp_nanos(nanos).unwrap() } } diff --git a/sdk/src/util.rs b/sdk/src/util.rs index d35e3cf..932692e 100644 --- a/sdk/src/util.rs +++ b/sdk/src/util.rs @@ -37,19 +37,17 @@ pub(crate) mod string { pub(crate) mod datetime_opt { use serde::{de, Deserialize, Deserializer, Serializer}; - pub fn deserialize<'de, D>( - deserializer: D, - ) -> Result>, D::Error> + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { Ok(match Option::<&'de str>::deserialize(deserializer)? { Some(s) => { - use chrono::TimeZone; let ts: i64 = s.parse().map_err(de::Error::custom)?; - let dt = chrono::Utc.timestamp_millis(ts); - - Some(dt) + Some( + time::OffsetDateTime::from_unix_timestamp_nanos(ts as i128 * 1000000) + .map_err(de::Error::custom)?, + ) } None => None, }) @@ -57,14 +55,14 @@ pub(crate) mod datetime_opt { #[allow(dead_code)] pub fn serialize( - value: &Option>, + value: &Option, serializer: S, ) -> Result where S: Serializer, { match value { - Some(dt) => serializer.collect_str(&(dt.timestamp_millis())), + Some(dt) => serializer.collect_str(&(dt.unix_timestamp_nanos() / 1000000)), None => serializer.serialize_none(), } } @@ -72,7 +70,6 @@ pub(crate) mod datetime_opt { #[cfg(test)] #[inline] -pub(crate) fn timestamp(ts: i64) -> chrono::DateTime { - use chrono::TimeZone; - chrono::Utc.timestamp_millis(ts) +pub(crate) fn timestamp(ts: i64) -> time::OffsetDateTime { + time::OffsetDateTime::from_unix_timestamp_nanos(ts as i128 * 1000000).unwrap() }