From a5a0459d7061ce2250e43d2718a2417aae9c1f86 Mon Sep 17 00:00:00 2001 From: Quat3rnion <81202811+Quat3rnion@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:41:50 -0400 Subject: [PATCH] Backend related updates (#501) (by Quat3rnion) Some updates relating to usage with Symfonia: * Using distinct types instead of primitives on some objects * Add sqlx derives and implementations * Make a facade type for Shared to be used in non-client contexts --- Cargo.lock | 214 +++++++++++--- Cargo.toml | 8 +- src/types/config/types/guild_configuration.rs | 6 + .../config/types/register_configuration.rs | 8 +- src/types/entities/guild.rs | 2 +- src/types/entities/guild_member.rs | 3 + src/types/entities/mod.rs | 2 + src/types/entities/user_settings.rs | 4 +- src/types/mod.rs | 7 + src/types/schema/channel.rs | 2 +- src/types/utils/jwt.rs | 4 +- src/types/utils/mod.rs | 2 + src/types/utils/rights.rs | 38 +++ src/types/utils/serde.rs | 262 ++++++++++++++++++ 14 files changed, 501 insertions(+), 61 deletions(-) create mode 100644 src/types/utils/serde.rs diff --git a/Cargo.lock b/Cargo.lock index 0d0cb0c..61d67fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,13 +101,19 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "atomic-write-file" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edcdbedc2236483ab103a53415653d6b4442ea6141baf1ffa85df29635e88436" dependencies = [ - "nix", + "nix 0.27.1", "rand", ] @@ -207,6 +213,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "chorus" version = "0.15.0" @@ -222,7 +234,7 @@ dependencies = [ "futures-util", "getrandom", "hostname", - "http", + "http 0.2.11", "jsonwebtoken", "lazy_static", "log", @@ -725,7 +737,26 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.11", + "indexmap 2.1.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", "indexmap 2.1.0", "slab", "tokio", @@ -760,14 +791,14 @@ dependencies = [ [[package]] name = "headers" -version = "0.3.9" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" dependencies = [ "base64 0.21.7", "bytes", "headers-core", - "http", + "http 1.1.0", "httpdate", "mime", "sha1", @@ -775,11 +806,11 @@ dependencies = [ [[package]] name = "headers-core" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" dependencies = [ - "http", + "http 1.1.0", ] [[package]] @@ -852,6 +883,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -859,7 +901,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.11", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http 1.1.0", + "http-body 1.0.0", "pin-project-lite", ] @@ -885,9 +950,9 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "h2 0.3.26", + "http 0.2.11", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -899,6 +964,26 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.5", + "http 1.1.0", + "http-body 1.0.0", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -906,12 +991,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper", + "hyper 0.14.28", "native-tls", "tokio", "tokio-native-tls", ] +[[package]] +name = "hyper-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "hyper 1.3.1", + "pin-project-lite", + "tokio", +] + [[package]] name = "iana-time-zone" version = "0.1.59" @@ -1014,9 +1114,9 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.66" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -1046,9 +1146,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.152" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libm" @@ -1182,6 +1282,18 @@ dependencies = [ "libc", ] +[[package]] +name = "nix" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +dependencies = [ + "bitflags 2.4.1", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "no-std-net" version = "0.6.0" @@ -1466,18 +1578,19 @@ dependencies = [ [[package]] name = "poem" -version = "1.3.59" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504774c97b0744c1ee108a37e5a65a9745a4725c4c06277521dabc28eb53a904" +checksum = "e88b6912ed1e8833d7c22c9c986c517f4518d7d37e3c04566d917c789aaea591" dependencies = [ - "async-trait", "bytes", "futures-util", "headers", - "http", - "hyper", + "http 1.1.0", + "http-body-util", + "hyper 1.3.1", + "hyper-util", "mime", - "nix", + "nix 0.28.0", "parking_lot", "percent-encoding", "pin-project-lite", @@ -1488,6 +1601,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", + "sync_wrapper", "thiserror", "tokio", "tokio-util", @@ -1497,9 +1611,9 @@ dependencies = [ [[package]] name = "poem-derive" -version = "1.3.59" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ddcf4680d8d867e1e375116203846acb088483fa2070244f90589f458bbb31" +checksum = "c2b961d58a6c53380c20236394381d9292fda03577f902b158f1638932964dcf" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -1532,11 +1646,10 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro-crate" -version = "2.0.1" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97dc5fea232fc28d2f597b37c4876b348a40e33f3b02cc975c8d006d78d94b1a" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "toml_datetime", "toml_edit", ] @@ -1637,10 +1750,10 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", + "h2 0.3.26", + "http 0.2.11", + "http-body 0.4.6", + "hyper 0.14.28", "hyper-tls", "ipnet", "js-sys", @@ -2024,9 +2137,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" @@ -2323,6 +2436,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -2511,15 +2633,15 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" [[package]] name = "toml_edit" -version = "0.20.2" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ "indexmap 2.1.0", "toml_datetime", @@ -2579,7 +2701,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http", + "http 0.2.11", "httparse", "log", "rand", @@ -2752,9 +2874,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.39" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -2793,9 +2915,9 @@ checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wasm-bindgen-test" -version = "0.3.39" +version = "0.3.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cf9242c0d27999b831eae4767b2a146feb0b27d332d553e605864acd2afd403" +checksum = "d9bf62a58e0780af3e852044583deee40983e5886da43a271dd772379987667b" dependencies = [ "console_error_panic_hook", "js-sys", @@ -2807,9 +2929,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.39" +version = "0.3.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "794645f5408c9a039fd09f4d113cdfb2e7eba5ff1956b07bcf701cf4b394fe89" +checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index a31a9da..a3b0b36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ rust-version = "1.67.1" [features] default = ["client", "rt-multi-thread"] -backend = ["dep:poem", "dep:sqlx"] +backend = ["poem", "sqlx"] rt-multi-thread = ["tokio/rt-multi-thread"] rt = ["tokio/rt"] client = [] @@ -38,7 +38,7 @@ http = "0.2.11" base64 = "0.21.7" bitflags = { version = "2.4.1", features = ["serde"] } lazy_static = "1.4.0" -poem = { version = "1.3.59", optional = true } +poem = { version = "3.0.1", optional = true } thiserror = "1.0.56" jsonwebtoken = "8.3.0" log = "0.4.20" @@ -76,5 +76,5 @@ wasmtimer = "0.2.0" [dev-dependencies] lazy_static = "1.4.0" -wasm-bindgen-test = "0.3.39" -wasm-bindgen = "0.2.89" +wasm-bindgen-test = "0.3.42" +wasm-bindgen = "0.2.92" diff --git a/src/types/config/types/guild_configuration.rs b/src/types/config/types/guild_configuration.rs index af40b30..d2b3f16 100644 --- a/src/types/config/types/guild_configuration.rs +++ b/src/types/config/types/guild_configuration.rs @@ -376,6 +376,12 @@ impl FromStr for GuildFeatures { } } +impl From> for GuildFeaturesList { + fn from(features: Vec) -> GuildFeaturesList { + Self(features) + } +} + impl GuildFeatures { pub fn to_str(&self) -> &'static str { match *self { diff --git a/src/types/config/types/register_configuration.rs b/src/types/config/types/register_configuration.rs index a4573bf..19cedfb 100644 --- a/src/types/config/types/register_configuration.rs +++ b/src/types/config/types/register_configuration.rs @@ -4,9 +4,9 @@ use serde::{Deserialize, Serialize}; -use crate::types::config::types::subconfigs::register::{ +use crate::types::{config::types::subconfigs::register::{ DateOfBirthConfiguration, PasswordConfiguration, RegistrationEmailConfiguration, -}; +}, Rights}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -22,7 +22,7 @@ pub struct RegisterConfiguration { pub allow_multiple_accounts: bool, pub block_proxies: bool, pub incrementing_discriminators: bool, - pub default_rights: String, + pub default_rights: Rights, } impl Default for RegisterConfiguration { @@ -39,7 +39,7 @@ impl Default for RegisterConfiguration { allow_multiple_accounts: true, block_proxies: true, incrementing_discriminators: false, - default_rights: String::from("875069521787904"), + default_rights: Rights::from_bits(648540060672).expect("failed to parse default_rights"), } } } diff --git a/src/types/entities/guild.rs b/src/types/entities/guild.rs index 8544b0b..66b9335 100644 --- a/src/types/entities/guild.rs +++ b/src/types/entities/guild.rs @@ -57,7 +57,7 @@ pub struct Guild { #[cfg_attr(feature = "client", observe_vec)] #[serde(default)] pub emojis: Vec>, - pub explicit_content_filter: Option, + pub explicit_content_filter: Option, //#[cfg_attr(feature = "sqlx", sqlx(try_from = "String"))] pub features: Option, pub icon: Option, diff --git a/src/types/entities/guild_member.rs b/src/types/entities/guild_member.rs index 66fad99..df07583 100644 --- a/src/types/entities/guild_member.rs +++ b/src/types/entities/guild_member.rs @@ -9,14 +9,17 @@ use crate::types::Shared; use crate::types::{entities::PublicUser, Snowflake}; #[derive(Debug, Deserialize, Default, Serialize, Clone)] +#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] /// Represents a participating user in a guild. /// /// # Reference /// See pub struct GuildMember { + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub user: Option>, pub nick: Option, pub avatar: Option, + #[cfg_attr(feature = "sqlx", sqlx(skip))] pub roles: Vec, pub joined_at: DateTime, pub premium_since: Option>, diff --git a/src/types/entities/mod.rs b/src/types/entities/mod.rs index d5d70e6..4227e24 100644 --- a/src/types/entities/mod.rs +++ b/src/types/entities/mod.rs @@ -28,6 +28,7 @@ pub use voice_state::*; pub use webhook::*; use crate::types::Shared; +#[cfg(feature = "client")] use std::sync::{Arc, RwLock}; #[cfg(feature = "client")] @@ -134,6 +135,7 @@ pub trait IntoShared { fn into_shared(self) -> Shared; } +#[cfg(feature = "client")] impl IntoShared for T { fn into_shared(self) -> Shared { Arc::new(RwLock::new(self)) diff --git a/src/types/entities/user_settings.rs b/src/types/entities/user_settings.rs index db13efc..0dbce3e 100644 --- a/src/types/entities/user_settings.rs +++ b/src/types/entities/user_settings.rs @@ -2,8 +2,6 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use std::sync::{Arc, RwLock}; - use chrono::{serde::ts_milliseconds_option, Utc}; use serde::{Deserialize, Serialize}; @@ -119,7 +117,7 @@ impl Default for UserSettings { render_reactions: true, restricted_guilds: Default::default(), show_current_game: true, - status: Arc::new(RwLock::new(UserStatus::Online)), + status: Default::default(), stream_notifications_enabled: false, theme: UserTheme::Dark, timezone_offset: 0, diff --git a/src/types/mod.rs b/src/types/mod.rs index c4cc190..f41a083 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -4,6 +4,7 @@ //! All the types, entities, events and interfaces of the Spacebar API. +#[cfg(feature = "client")] use std::sync::{Arc, RwLock}; pub use config::*; @@ -28,4 +29,10 @@ mod utils; /// /// While `T` does not have to implement `Composite` to be used with `Shared`, /// the primary use of `Shared` is with types that implement `Composite`. +/// +/// When the `client` feature is disabled, this does nothing (same as just `T`), +/// since `Composite` structures are disabled. +#[cfg(feature = "client")] pub type Shared = Arc>; +#[cfg(not(feature = "client"))] +pub type Shared = T; diff --git a/src/types/schema/channel.rs b/src/types/schema/channel.rs index 1502f97..851bfda 100644 --- a/src/types/schema/channel.rs +++ b/src/types/schema/channel.rs @@ -36,7 +36,7 @@ pub struct ChannelCreateSchema { #[serde(rename_all = "snake_case")] pub struct ChannelModifySchema { pub name: Option, - pub channel_type: Option, + pub channel_type: Option, pub topic: Option, pub icon: Option, pub bitrate: Option, diff --git a/src/types/utils/jwt.rs b/src/types/utils/jwt.rs index 6addb4c..0919a5a 100644 --- a/src/types/utils/jwt.rs +++ b/src/types/utils/jwt.rs @@ -19,7 +19,7 @@ pub struct Claims { /// When the token was issued pub iat: i64, pub email: String, - pub id: String, + pub id: Snowflake, } impl Claims { @@ -27,7 +27,7 @@ impl Claims { let unix = chrono::Utc::now().timestamp(); Self { exp: unix + (60 * 60 * 24), - id: id.to_string(), + id: *id, iat: unix, email: user.to_string(), } diff --git a/src/types/utils/mod.rs b/src/types/utils/mod.rs index 8879688..5608fe7 100644 --- a/src/types/utils/mod.rs +++ b/src/types/utils/mod.rs @@ -11,3 +11,5 @@ pub mod jwt; mod regexes; mod rights; mod snowflake; +pub mod serde; + diff --git a/src/types/utils/rights.rs b/src/types/utils/rights.rs index 4b1aa13..63978da 100644 --- a/src/types/utils/rights.rs +++ b/src/types/utils/rights.rs @@ -3,6 +3,10 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use bitflags::bitflags; +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "sqlx")] +use sqlx::{{Decode, Encode, MySql}, database::{HasArguments, HasValueRef}, encode::IsNull, error::BoxDynError, mysql::MySqlValueRef}; bitflags! { /// Rights are instance-wide, per-user permissions for everything you may perform on the instance, @@ -14,6 +18,7 @@ bitflags! { /// /// # Reference /// See + #[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] pub struct Rights: u64 { /// All rights const OPERATOR = 1 << 0; @@ -127,6 +132,33 @@ bitflags! { } } +#[cfg(feature = "sqlx")] +impl sqlx::Type for Rights { + fn type_info() -> ::TypeInfo { + u64::type_info() + } + + fn compatible(ty: &::TypeInfo) -> bool { + u64::compatible(ty) + } +} + +#[cfg(feature = "sqlx")] +impl<'q> Encode<'q, MySql> for Rights { + fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + >::encode_by_ref(&self.0.0, buf) + } +} + +#[cfg(feature = "sqlx")] +impl<'r> Decode<'r, MySql> for Rights { + fn decode(value: >::ValueRef) -> Result { + let raw = >::decode(value)?; + Ok(Rights::from_bits(raw).unwrap()) + } +} + + impl Rights { pub fn any(&self, permission: Rights, check_operator: bool) -> bool { (check_operator && self.contains(Rights::OPERATOR)) || self.contains(permission) @@ -151,6 +183,12 @@ impl Rights { } } +impl Default for Rights { + fn default() -> Self { + Self::empty() + } +} + #[allow(dead_code)] // FIXME: Remove this when we use this fn all_rights() -> Rights { Rights::OPERATOR diff --git a/src/types/utils/serde.rs b/src/types/utils/serde.rs new file mode 100644 index 0000000..8584004 --- /dev/null +++ b/src/types/utils/serde.rs @@ -0,0 +1,262 @@ +use core::fmt; +use chrono::{LocalResult, NaiveDateTime}; +use serde::de; + +#[doc(hidden)] +#[derive(Debug)] +pub struct SecondsStringTimestampVisitor; + + +/// Ser/de to/from timestamps in seconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::{TimeZone, DateTime, Utc}; +/// # use serde::{Deserialize, Serialize}; +/// use chorus::types::serde::ts_seconds_str; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_seconds_str")] +/// time: DateTime +/// } +/// +/// let time = Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap(); +/// let my_s = S { +/// time: time.clone(), +/// }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":"1431684000"}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` + +pub mod ts_seconds_str { + use core::fmt; + use chrono::{DateTime, LocalResult, Utc}; + use super::SecondsStringTimestampVisitor; + use serde::{de, ser}; + use chrono::TimeZone; + use super::serde_from; + + /// Serialize a UTC datetime into an integer number of seconds since the epoch + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use serde::Serialize; + /// use chorus::types::serde::ts_seconds_str::serialize as to_ts; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_ts")] + /// time: DateTime + /// } + /// + /// let my_s = S { + /// time: Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap(), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":"1431684000"}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(dt: &DateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_str(&format!("{}", dt.timestamp())) + } + + /// Deserialize a `DateTime` from a seconds timestamp + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, TimeZone, Utc}; + /// # use serde::Deserialize; + /// use chorus::types::serde::ts_seconds_str::deserialize as from_ts; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_ts")] + /// time: DateTime + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": "1431684000" }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1431684000, 0).unwrap() }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_str(SecondsStringTimestampVisitor) + } + + impl<'de> de::Visitor<'de> for SecondsStringTimestampVisitor { + type Value = DateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in seconds") + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + serde_from(Utc.timestamp_opt(value.parse::().map_err(|e| E::custom(e))?, 0), &value) + } + } +} + +/// Ser/de to/from optional timestamps in seconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::{TimeZone, DateTime, Utc}; +/// # use serde::{Deserialize, Serialize}; +/// use chorus::types::serde::ts_seconds_option_str; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "ts_seconds_option_str")] +/// time: Option> +/// } +/// +/// let time = Some(Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap()); +/// let my_s = S { +/// time: time.clone(), +/// }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"time":"1431684000"}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.time, time); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod ts_seconds_option_str { + use core::fmt; + use chrono::{DateTime, Utc}; + use serde::{de, ser}; + use super::SecondsStringTimestampVisitor; + + /// Serialize a UTC datetime into an integer number of seconds since the epoch or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{TimeZone, DateTime, Utc}; + /// # use serde::Serialize; + /// use chorus::types::serde::ts_seconds_option_str::serialize as from_tsopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "from_tsopt")] + /// time: Option> + /// } + /// + /// let my_s = S { + /// time: Some(Utc.with_ymd_and_hms(2015, 5, 15, 10, 0, 0).unwrap()), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"time":"1431684000"}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(opt: &Option>, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref dt) => serializer.serialize_some(&dt.timestamp().to_string()), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `DateTime` from a seconds timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::{DateTime, TimeZone, Utc}; + /// # use serde::Deserialize; + /// use chorus::types::serde::ts_seconds_option_str::deserialize as from_tsopt; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_tsopt")] + /// time: Option> + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "time": "1431684000" }"#)?; + /// assert_eq!(my_s, S { time: Utc.timestamp_opt(1431684000, 0).single() }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionSecondsTimestampVisitor) + } + + struct OptionSecondsTimestampVisitor; + + impl<'de> de::Visitor<'de> for OptionSecondsTimestampVisitor { + type Value = Option>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp in seconds or none") + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_str(SecondsStringTimestampVisitor).map(Some) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +pub(crate) fn serde_from(me: LocalResult, _ts: &V) -> Result + where + E: de::Error, + V: fmt::Display, + T: fmt::Display, +{ + // TODO: Make actual error type + match me { + LocalResult::None => Err(E::custom("value is not a legal timestamp")), + LocalResult::Ambiguous(_min, _max) => { + Err(E::custom("value is an ambiguous timestamp")) + } + LocalResult::Single(val) => Ok(val), + } +} \ No newline at end of file