Files
empidee/src/commands/reflection/decoders.rs
h7x4 02e292ffd7 response_tokenizer: rewrite
This commit contains a rewrite of the response tokenizer, which
introduces lazy parsing of the response, handling of binary data, some
tests, as well as just generally more robustness against errors.
2025-11-24 23:49:27 +09:00

144 lines
4.5 KiB
Rust

use serde::{Deserialize, Serialize};
use crate::{
commands::{Command, Request, RequestParserResult, ResponseParserError},
request_tokenizer::RequestTokenizer,
response_tokenizer::{ResponseAttributes, expect_property_type},
};
pub struct Decoders;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Decoder {
pub plugin: String,
pub suffixes: Vec<String>,
pub mime_types: Vec<String>,
}
pub type DecodersResponse = Vec<Decoder>;
impl Command for Decoders {
type Request = ();
type Response = DecodersResponse;
const COMMAND: &'static str = "decoders";
fn serialize_request(&self, _request: Self::Request) -> String {
Self::COMMAND.to_string()
}
fn parse_request(mut parts: RequestTokenizer<'_>) -> RequestParserResult<'_> {
debug_assert!(parts.next().is_none());
Ok((Request::Decoders, ""))
}
fn parse_response(
parts: ResponseAttributes<'_>,
) -> Result<Self::Response, ResponseParserError<'_>> {
let mut result = Vec::new();
let mut current_decoder: Option<Decoder> = None;
for (key, value) in parts.into_vec()?.into_iter() {
match key {
"plugin" => {
if let Some(decoder) = current_decoder.take() {
result.push(decoder);
}
let plugin_name = expect_property_type!(Some(value), key, Text).to_string();
current_decoder = Some(Decoder {
plugin: plugin_name,
suffixes: Vec::new(),
mime_types: Vec::new(),
});
}
"suffix" => {
current_decoder
.as_mut()
.ok_or(ResponseParserError::SyntaxError(0, key))?
.suffixes
.push(expect_property_type!(Some(value), key, Text).to_string());
}
"mime_type" => {
current_decoder
.as_mut()
.ok_or(ResponseParserError::SyntaxError(0, key))?
.mime_types
.push(expect_property_type!(Some(value), key, Text).to_string());
}
k => {
return Err(ResponseParserError::UnexpectedProperty(k));
}
}
}
if let Some(decoder) = current_decoder.take() {
result.push(decoder);
}
Ok(result)
}
}
#[cfg(test)]
mod tests {
use indoc::indoc;
use super::*;
#[test]
fn test_parse_response() {
let input = indoc! {"
plugin: audiofile
suffix: wav
suffix: au
suffix: aiff
suffix: aif
mime_type: audio/wav
mime_type: audio/aiff
mime_type: audio/x-wav
mime_type: audio/x-aiff
plugin: pcm
mime_type: audio/L16
mime_type: audio/L24
mime_type: audio/x-mpd-float
mime_type: audio/x-mpd-cdda-pcm
mime_type: audio/x-mpd-cdda-pcm-reverse
mime_type: audio/x-mpd-alsa-pcm
OK
"};
let result = Decoders::parse_raw_response(input);
assert_eq!(
result,
Ok(vec![
Decoder {
plugin: "audiofile".to_string(),
suffixes: vec![
"wav".to_string(),
"au".to_string(),
"aiff".to_string(),
"aif".to_string()
],
mime_types: vec![
"audio/wav".to_string(),
"audio/aiff".to_string(),
"audio/x-wav".to_string(),
"audio/x-aiff".to_string()
],
},
Decoder {
plugin: "pcm".to_string(),
suffixes: vec![],
mime_types: vec![
"audio/L16".to_string(),
"audio/L24".to_string(),
"audio/x-mpd-float".to_string(),
"audio/x-mpd-cdda-pcm".to_string(),
"audio/x-mpd-cdda-pcm-reverse".to_string(),
"audio/x-mpd-alsa-pcm".to_string(),
],
},
])
);
}
}