add player response model
This commit is contained in:
parent
b85b9893a8
commit
030fd9934e
25 changed files with 11765 additions and 121 deletions
113
src/serializer/mime_type.rs
Normal file
113
src/serializer/mime_type.rs
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
use once_cell::sync::Lazy;
|
||||
|
||||
use fancy_regex::Regex;
|
||||
use serde::de::{Deserialize, Deserializer, Error, Unexpected};
|
||||
use serde::ser::{Serialize, Serializer};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct MimeType {
|
||||
pub mime: String,
|
||||
pub codecs: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<MimeType, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
static PATTERN: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r#"(\w+/\w+);\scodecs="([a-zA-Z-0-9.,\s]*)""#).unwrap());
|
||||
|
||||
// deserializing into a &str gives back an error
|
||||
let s = String::deserialize(deserializer)?;
|
||||
|
||||
let captures = PATTERN.captures(&s).ok().flatten().ok_or_else(|| {
|
||||
D::Error::invalid_value(
|
||||
Unexpected::Str(&s),
|
||||
&"a valid mime type with the format <TYPE>/<SUBTYPE>",
|
||||
)
|
||||
})?;
|
||||
let mime = captures.get(1).unwrap().as_str().to_owned();
|
||||
let codecs = captures
|
||||
.get(2)
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.split(", ")
|
||||
.map(str::to_owned)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
Ok(MimeType {
|
||||
mime,
|
||||
codecs,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn serialize<S>(mime_type: &MimeType, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut s = format!(r#"{}; codecs=""#, mime_type.mime,);
|
||||
|
||||
for codec in mime_type.codecs.iter() {
|
||||
s.push_str(codec);
|
||||
s.push(',');
|
||||
s.push(' ');
|
||||
}
|
||||
|
||||
s.pop();
|
||||
s.pop();
|
||||
s.push('"');
|
||||
s.serialize(serializer)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rstest::rstest;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use super::*;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct S {
|
||||
#[serde(with = "crate::serializer::mime_type")]
|
||||
mime: MimeType,
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(
|
||||
r#"{"mime": "video/mp4; codecs=\"avc1.42001E, mp4a.40.2\""}"#,
|
||||
MimeType {
|
||||
mime: "video/mp4".to_owned(),
|
||||
codecs: vec!["avc1.42001E".to_owned(), "mp4a.40.2".to_owned()],
|
||||
}
|
||||
)]
|
||||
#[case(
|
||||
r#"{"mime": "video/webm; codecs=\"vp9\""}"#,
|
||||
MimeType {
|
||||
mime: "video/webm".to_owned(),
|
||||
codecs: vec!["vp9".to_owned()],
|
||||
}
|
||||
)]
|
||||
#[case(
|
||||
r#"{"mime": "audio/webm; codecs=\"opus\""}"#,
|
||||
MimeType {
|
||||
mime: "audio/webm".to_owned(),
|
||||
codecs: vec!["opus".to_owned()],
|
||||
}
|
||||
)]
|
||||
fn t_deserialize(#[case] test_json: &str, #[case] exp: MimeType) {
|
||||
let res = serde_json::from_str::<S>(&test_json).unwrap();
|
||||
assert_eq!(res.mime, exp)
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn t_serialize() {
|
||||
let s = S {
|
||||
mime: MimeType {
|
||||
mime: "video/webm".to_owned(),
|
||||
codecs: vec!["av01.0.08M.08".to_owned()],
|
||||
},
|
||||
};
|
||||
|
||||
let json = serde_json::to_string(&s).unwrap();
|
||||
assert_eq!(json, r#"{"mime":"video/webm; codecs=\"av01.0.08M.08\""}"#)
|
||||
}
|
||||
}
|
||||
3
src/serializer/mod.rs
Normal file
3
src/serializer/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
pub mod range;
|
||||
pub mod mime_type;
|
||||
pub mod text;
|
||||
27
src/serializer/range.rs
Normal file
27
src/serializer/range.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde_with::{DeserializeAs, json::JsonString, serde_as, SerializeAs};
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Range {
|
||||
#[serde_as(as = "JsonString")]
|
||||
start: u32,
|
||||
#[serde_as(as = "JsonString")]
|
||||
end: u32,
|
||||
}
|
||||
|
||||
impl<'de> DeserializeAs<'de, std::ops::Range<u32>> for Range {
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<std::ops::Range<u32>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de> {
|
||||
let range = Range::deserialize(deserializer)?;
|
||||
Ok(std::ops::Range { start: range.start, end: range.end })
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializeAs<std::ops::Range<u32>> for Range {
|
||||
fn serialize_as<S>(&std::ops::Range { start, end }: &std::ops::Range<u32>, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> where
|
||||
S: Serializer {
|
||||
Range { start, end }.serialize(serializer)
|
||||
}
|
||||
}
|
||||
109
src/serializer/text.rs
Normal file
109
src/serializer/text.rs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
use serde::{Deserialize, Deserializer};
|
||||
use serde_with::{serde_as, DeserializeAs};
|
||||
|
||||
/// The YouTube API has multiple ways of outputting text. This deserializer
|
||||
/// is an attempt to unify them.
|
||||
///
|
||||
/// ```json
|
||||
/// {
|
||||
/// "text": "Hello World"
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ```json
|
||||
/// {
|
||||
/// "simpleText": "Hello World"
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Multiple "runs" of text should be joined with spaces
|
||||
/// ```json
|
||||
/// {
|
||||
/// "runs": [
|
||||
/// {"text": "Hello"},
|
||||
/// {"text": "World"},
|
||||
/// ]
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Text {
|
||||
Simple {
|
||||
#[serde(alias = "simpleText")]
|
||||
text: String,
|
||||
},
|
||||
Multiple {
|
||||
#[serde_as(as = "Vec<crate::serializer::text::Text>")]
|
||||
runs: Vec<String>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'de> DeserializeAs<'de, String> for Text {
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let text = Text::deserialize(deserializer)?;
|
||||
match text {
|
||||
Text::Simple { text } => Ok(text),
|
||||
Text::Multiple { runs } => Ok(runs.join("")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rstest::rstest;
|
||||
use serde::Deserialize;
|
||||
use serde_with::serde_as;
|
||||
|
||||
#[rstest]
|
||||
#[case(
|
||||
r#"{
|
||||
"txt": {
|
||||
"text": "Hello World"
|
||||
}
|
||||
}"#,
|
||||
"Hello World"
|
||||
)]
|
||||
#[case(
|
||||
r#"{
|
||||
"txt": {
|
||||
"simpleText": "Hello World"
|
||||
}
|
||||
}"#,
|
||||
"Hello World"
|
||||
)]
|
||||
#[case(
|
||||
r#"{
|
||||
"txt": {
|
||||
"runs": [
|
||||
{
|
||||
"text": "Abo für "
|
||||
},
|
||||
{
|
||||
"text": "MBCkpop"
|
||||
},
|
||||
{
|
||||
"text": " beenden?"
|
||||
}
|
||||
]
|
||||
}
|
||||
}"#,
|
||||
"Abo für MBCkpop beenden?"
|
||||
)]
|
||||
fn t_deserialize(#[case] test_json: &str, #[case] exp: &str) {
|
||||
#[serde_as]
|
||||
#[derive(Deserialize)]
|
||||
struct S {
|
||||
#[serde_as(as = "crate::serializer::text::Text")]
|
||||
txt: String,
|
||||
}
|
||||
|
||||
let res = serde_json::from_str::<S>(&test_json).unwrap();
|
||||
assert_eq!(res.txt, exp)
|
||||
}
|
||||
}
|
||||
Reference in a new issue