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
This commit is contained in:
Quat3rnion 2024-06-04 13:41:50 -04:00 committed by GitHub
parent eb087938ed
commit a5a0459d70
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 501 additions and 61 deletions

214
Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -376,6 +376,12 @@ impl FromStr for GuildFeatures {
}
}
impl From<Vec<GuildFeatures>> for GuildFeaturesList {
fn from(features: Vec<GuildFeatures>) -> GuildFeaturesList {
Self(features)
}
}
impl GuildFeatures {
pub fn to_str(&self) -> &'static str {
match *self {

View File

@ -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"),
}
}
}

View File

@ -57,7 +57,7 @@ pub struct Guild {
#[cfg_attr(feature = "client", observe_vec)]
#[serde(default)]
pub emojis: Vec<Shared<Emoji>>,
pub explicit_content_filter: Option<i32>,
pub explicit_content_filter: Option<ExplicitContentFilterLevel>,
//#[cfg_attr(feature = "sqlx", sqlx(try_from = "String"))]
pub features: Option<GuildFeaturesList>,
pub icon: Option<String>,

View File

@ -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 <https://discord-userdoccers.vercel.app/resources/guild#guild-member-object>
pub struct GuildMember {
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub user: Option<Shared<PublicUser>>,
pub nick: Option<String>,
pub avatar: Option<String>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub roles: Vec<Snowflake>,
pub joined_at: DateTime<Utc>,
pub premium_since: Option<DateTime<Utc>>,

View File

@ -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<Self>;
}
#[cfg(feature = "client")]
impl<T: Sized> IntoShared for T {
fn into_shared(self) -> Shared<Self> {
Arc::new(RwLock::new(self))

View File

@ -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,

View File

@ -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<T> = Arc<RwLock<T>>;
#[cfg(not(feature = "client"))]
pub type Shared<T> = T;

View File

@ -36,7 +36,7 @@ pub struct ChannelCreateSchema {
#[serde(rename_all = "snake_case")]
pub struct ChannelModifySchema {
pub name: Option<String>,
pub channel_type: Option<u8>,
pub channel_type: Option<ChannelType>,
pub topic: Option<String>,
pub icon: Option<String>,
pub bitrate: Option<i32>,

View File

@ -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(),
}

View File

@ -11,3 +11,5 @@ pub mod jwt;
mod regexes;
mod rights;
mod snowflake;
pub mod serde;

View File

@ -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 <https://docs.spacebar.chat/setup/server/security/rights/>
#[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<MySql> for Rights {
fn type_info() -> <sqlx::MySql as sqlx::Database>::TypeInfo {
u64::type_info()
}
fn compatible(ty: &<sqlx::MySql as sqlx::Database>::TypeInfo) -> bool {
u64::compatible(ty)
}
}
#[cfg(feature = "sqlx")]
impl<'q> Encode<'q, MySql> for Rights {
fn encode_by_ref(&self, buf: &mut <MySql as HasArguments<'q>>::ArgumentBuffer) -> IsNull {
<u64 as Encode<MySql>>::encode_by_ref(&self.0.0, buf)
}
}
#[cfg(feature = "sqlx")]
impl<'r> Decode<'r, MySql> for Rights {
fn decode(value: <MySql as HasValueRef<'r>>::ValueRef) -> Result<Self, BoxDynError> {
let raw = <u64 as Decode<MySql>>::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

262
src/types/utils/serde.rs Normal file
View File

@ -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<Utc>
/// }
///
/// 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<Utc>
/// }
///
/// 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<S>(dt: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
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<Utc>
/// }
///
/// 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<DateTime<Utc>, D::Error>
where
D: de::Deserializer<'de>,
{
d.deserialize_str(SecondsStringTimestampVisitor)
}
impl<'de> de::Visitor<'de> for SecondsStringTimestampVisitor {
type Value = DateTime<Utc>;
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<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
serde_from(Utc.timestamp_opt(value.parse::<i64>().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<DateTime<Utc>>
/// }
///
/// 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<DateTime<Utc>>
/// }
///
/// 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<S>(opt: &Option<DateTime<Utc>>, serializer: S) -> Result<S::Ok, S::Error>
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<DateTime<Utc>>
/// }
///
/// 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<Option<DateTime<Utc>>, D::Error>
where
D: de::Deserializer<'de>,
{
d.deserialize_option(OptionSecondsTimestampVisitor)
}
struct OptionSecondsTimestampVisitor;
impl<'de> de::Visitor<'de> for OptionSecondsTimestampVisitor {
type Value = Option<DateTime<Utc>>;
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<D>(self, d: D) -> Result<Self::Value, D::Error>
where
D: de::Deserializer<'de>,
{
d.deserialize_str(SecondsStringTimestampVisitor).map(Some)
}
/// Deserialize a timestamp in seconds since the epoch
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(None)
}
/// Deserialize a timestamp in seconds since the epoch
fn visit_unit<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(None)
}
}
}
pub(crate) fn serde_from<T, E, V>(me: LocalResult<T>, _ts: &V) -> Result<T, E>
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),
}
}