mirror of https://github.com/dnaka91/obws
Parse into more concrete types where possible
parent
a96db636f6
commit
c7ec6fa13c
@ -0,0 +1,262 @@
|
||||
//! Custom deserializers that are used in both the [`events`](crate::events) and
|
||||
//! [`responses`](crate::responses) modules.
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::{self, Display};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use chrono::Duration;
|
||||
use serde::de::{Deserializer, Error, Visitor};
|
||||
|
||||
pub fn duration<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_option(OptDurationVisitor)
|
||||
}
|
||||
|
||||
struct OptDurationVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for OptDurationVisitor {
|
||||
type Value = Option<Duration>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("an optional duration formatted as 'HH:MM:SS.mmm'")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
let duration = || -> Result<Duration> {
|
||||
let mut hms = v.splitn(3, ':');
|
||||
let hours = hms.next().context("hours missing")?.parse()?;
|
||||
let minutes = hms.next().context("minutes missing")?.parse()?;
|
||||
let seconds = hms.next().context("seconds missing")?;
|
||||
|
||||
let mut sm = seconds.splitn(2, '.');
|
||||
let seconds = sm.next().context("seconds missing")?.parse()?;
|
||||
let millis = sm.next().context("milliseconds missing")?.parse()?;
|
||||
|
||||
Ok(Duration::hours(hours)
|
||||
+ Duration::minutes(minutes)
|
||||
+ Duration::seconds(seconds)
|
||||
+ Duration::milliseconds(millis))
|
||||
};
|
||||
|
||||
duration().map(Some).map_err(Error::custom)
|
||||
}
|
||||
|
||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_str(Self)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn duration_millis_opt<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_i64(OptDurationMillisVisitor)
|
||||
}
|
||||
|
||||
struct OptDurationMillisVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for OptDurationMillisVisitor {
|
||||
type Value = Option<Duration>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a duration in milliseconds where -1 means a fixed duration")
|
||||
}
|
||||
|
||||
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
Ok(if v < 0 {
|
||||
None
|
||||
} else {
|
||||
Some(Duration::milliseconds(v))
|
||||
})
|
||||
}
|
||||
|
||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
match i64::try_from(v) {
|
||||
Ok(value) => self.visit_i64(value),
|
||||
Err(e) => Err(Error::custom(e)),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_i64(Self)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn duration_millis<'de, D>(deserializer: D) -> Result<Duration, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_i64(DurationMillisVisitor)
|
||||
}
|
||||
|
||||
struct DurationMillisVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for DurationMillisVisitor {
|
||||
type Value = Duration;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a duration in milliseconds")
|
||||
}
|
||||
|
||||
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
Ok(Duration::milliseconds(v))
|
||||
}
|
||||
|
||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
match i64::try_from(v) {
|
||||
Ok(value) => self.visit_i64(value),
|
||||
Err(e) => Err(Error::custom(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn duration_nanos<'de, D>(deserializer: D) -> Result<Duration, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_i64(DurationNanosVisitor)
|
||||
}
|
||||
|
||||
struct DurationNanosVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for DurationNanosVisitor {
|
||||
type Value = Duration;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a duration in nanoseconds")
|
||||
}
|
||||
|
||||
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
Ok(Duration::nanoseconds(v))
|
||||
}
|
||||
|
||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
match i64::try_from(v) {
|
||||
Ok(value) => self.visit_i64(value),
|
||||
Err(e) => Err(Error::custom(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bitflags_u8<'de, D, T, TE>(deserializer: D) -> Result<T, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: TryFrom<u8, Error = TE>,
|
||||
TE: Display,
|
||||
{
|
||||
deserializer.deserialize_u8(BitflagsU8Visitor { flags: PhantomData })
|
||||
}
|
||||
|
||||
struct BitflagsU8Visitor<T, TE> {
|
||||
flags: PhantomData<(T, TE)>,
|
||||
}
|
||||
|
||||
impl<'de, T, TE> Visitor<'de> for BitflagsU8Visitor<T, TE>
|
||||
where
|
||||
T: TryFrom<u8, Error = TE>,
|
||||
TE: Display,
|
||||
{
|
||||
type Value = T;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("bitflags encoded as u8 integer")
|
||||
}
|
||||
|
||||
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
u8::try_from(v)
|
||||
.map_err(|_| Error::custom("value doesn't fit into an u8 integer"))
|
||||
.and_then(|v| self.visit_u8(v))
|
||||
}
|
||||
|
||||
fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
T::try_from(v).map_err(Error::custom)
|
||||
}
|
||||
|
||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
u8::try_from(v)
|
||||
.map_err(|_| Error::custom("value doesn't fit into an u8 integer"))
|
||||
.and_then(|v| self.visit_u8(v))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn deser_duration() {
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||
struct SimpleDuration {
|
||||
#[serde(deserialize_with = "duration")]
|
||||
value: Option<Duration>,
|
||||
};
|
||||
|
||||
let input = json! {{ "value": "02:15:04.310" }};
|
||||
let expect = SimpleDuration {
|
||||
value: Some(
|
||||
Duration::hours(2)
|
||||
+ Duration::minutes(15)
|
||||
+ Duration::seconds(4)
|
||||
+ Duration::milliseconds(310),
|
||||
),
|
||||
};
|
||||
assert_eq!(expect, serde_json::from_value(input).unwrap());
|
||||
}
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
//! # OBSWS - The obws (obvious) remote control library for OBS
|
||||
|
||||
#![deny(missing_docs, rust_2018_idioms, clippy::all)]
|
||||
#![warn(missing_docs, rust_2018_idioms, clippy::all)]
|
||||
|
||||
pub mod client;
|
||||
pub mod common;
|
||||
pub mod events;
|
||||
pub mod requests;
|
||||
pub mod responses;
|
||||
|
||||
mod de;
|
||||
|
@ -0,0 +1,42 @@
|
||||
use chrono::Duration;
|
||||
use serde::ser::{Error, Serializer};
|
||||
|
||||
pub fn duration_millis_opt<S>(value: &Option<Duration>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match value {
|
||||
Some(duration) => serializer.serialize_some(&duration.num_milliseconds()),
|
||||
None => serializer.serialize_none(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn duration_millis<S>(value: &Duration, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_i64(value.num_milliseconds())
|
||||
}
|
||||
|
||||
pub fn duration_nanos<S>(value: &Duration, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match value.num_nanoseconds() {
|
||||
Some(nanos) => serializer.serialize_i64(nanos),
|
||||
None => Err(Error::custom(
|
||||
"duration is too big to be serialized as nanoseconds",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bitflags_u8_opt<S, T>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
T: Into<u8> + Copy,
|
||||
{
|
||||
match value {
|
||||
Some(flags) => serializer.serialize_some(&(*flags).into()),
|
||||
None => serializer.serialize_none(),
|
||||
}
|
||||
}
|
@ -1,14 +1,67 @@
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use std::iter::FromIterator;
|
||||
|
||||
use serde::de::{Deserialize, Deserializer};
|
||||
use serde::de::{Deserializer, Error, Visitor};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn string_comma_list<'de, D, T>(deserializer: D) -> Result<T, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: FromIterator<String>,
|
||||
{
|
||||
let s = <&str>::deserialize(deserializer)?;
|
||||
deserializer.deserialize_str(StringListVisitor {
|
||||
sep: ',',
|
||||
container: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
struct StringListVisitor<T> {
|
||||
sep: char,
|
||||
container: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'de, T> Visitor<'de> for StringListVisitor<T>
|
||||
where
|
||||
T: FromIterator<String>,
|
||||
{
|
||||
type Value = T;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
formatter,
|
||||
"a string containing values separated by '{}'",
|
||||
self.sep
|
||||
)
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
Ok(v.split(self.sep).map(|s| s.to_owned()).collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn deser_string_comma_list() {
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||
struct SimpleList {
|
||||
#[serde(deserialize_with = "string_comma_list")]
|
||||
value: Vec<String>,
|
||||
}
|
||||
|
||||
Ok(s.split(',').map(|s| s.to_owned()).collect())
|
||||
let input = json! {{ "value": "a,b,c" }};
|
||||
let expect = SimpleList {
|
||||
value: vec!["a".to_owned(), "b".to_owned(), "c".to_owned()],
|
||||
};
|
||||
assert_eq!(expect, serde_json::from_value(input).unwrap());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue