xmlrustyoutube-api

Why are these fields in this youtube XML data not being deserialized properly using quick-xml in Rust


I am trying to deserialize youtube data from the endpoint http://www.youtube.com/feeds/videos.xml?channel_id=UCHnyfMqiRRG1u-2MsSQLbXA into the Feed struct in the below Rust program. It is returning None for the media_group field in the struct Entry, even though the raw XML contains information.

NOTE :: PASTE THE http://www.youtube.com/feeds/videos.xml?channel_id=UCHnyfMqiRRG1u-2MsSQLbXA INTO A BROWSER TO VISUALIZE THE XML BETTER

// youtube_data.rs

use hyper::{Body, Client, Uri};
use hyper_tls::HttpsConnector;
use serde::Deserialize;
use quick_xml::de::{from_str, DeError};
use crate::internals::singularity::WidgetError;



impl From<DeError> for WidgetError {
    fn from(err: DeError) -> Self {
        WidgetError::XmlParse(err.to_string())
    }
}



#[derive(Debug, Deserialize)]
#[serde(rename = "feed")]
pub struct Feed {
    id: String,
    #[serde(rename = "channelId", default)]
    yt_channel_id: Option<String>,
    title: String,
    published: String,
    #[serde(default)]
    #[serde(rename = "entry")]
    entries: Vec<Entry>,
}

#[derive(Debug, Deserialize)]
struct Entry {
    id: String,
    title: String,
    published: String,
    #[serde(rename = "mediaGroup")]
    media_group: Option<MediaGroup>,
}

#[derive(Debug, Deserialize)]
struct MediaGroup {
    #[serde(rename = "mediaTitle")]
    title: Option<String>,
    #[serde(rename = "mediaContent")]
    content: Option<MediaContent>,
    #[serde(rename = "mediaThumbnail")]
    thumbnail: Option<MediaThumbnail>,
    #[serde(rename = "mediaCommunity")]
    community: Option<MediaCommunity>,
}

#[derive(Debug, Deserialize)]
struct MediaContent {
    #[serde(rename = "@url")]
    url: String,
    #[serde(rename = "@width")]
    width: u32,
    #[serde(rename = "@height")]
    height: u32,
}

#[derive(Debug, Deserialize)]
struct MediaThumbnail {
    #[serde(rename = "@url")]
    url: String,
    #[serde(rename = "@width")]
    width: u32,
    #[serde(rename = "@height")]
    height: u32,
}

#[derive(Debug, Deserialize)]
struct MediaCommunity {
    #[serde(rename = "mediaStarRating")]
    star_rating: Option<StarRating>,
    #[serde(rename = "mediaStatistics")]
    statistics: Option<Statistics>,
}

#[derive(Debug, Deserialize)]
struct StarRating {
    #[serde(rename = "@count")]
    count: u64,
}

#[derive(Debug, Deserialize)]
struct Statistics {
    #[serde(rename = "@views")]
    views: u64,
}

pub async fn get_youtube_vids_for_a_channel(channel_id: String) -> Result<Feed, WidgetError> {
    let url = format!(
        "https://www.youtube.com/feeds/videos.xml?channel_id={}",
        channel_id
    );

    let uri: Uri = url.parse().unwrap();

    let https = HttpsConnector::new();
    let client = Client::builder().build::<_, Body>(https);
    let res = client.get(uri).await?;
    let body = hyper::body::to_bytes(res.into_body()).await?;
    let response_text = String::from_utf8(body.to_vec())
        .map_err(|e| WidgetError::Utf8Error(e.to_string()))?; 

    // println!("{:#?}", response_text);
    let feed: Feed = from_str(&response_text)?;

    Ok(feed)
}
// singularity.rs

#[derive(Debug, Error)]
pub enum WidgetError {
    #[error("Hyper error: {0}")]
    Hyper(#[from] hyper::Error),
    #[error("No geocoding data found")]
    NoGeocodingData,
    #[error("Error in reading HTML file")]
    NoHtmlToString,
    #[error("Serde JSON error: {0}")]
    SerdeJson(#[from] serde_json::Error),
    #[error("No timezone found")]
    NoTimeZone,
    #[error("XML parsing error: {0}")]
    XmlParse(String),
    #[error("UTF-8 conversion error: {0}")]
    Utf8Error(String),
}

The dependencies used are

[dependencies]
hyper = { version = "0.14", features = ["full"] }
hyper-tls = "0.5"
regex = "1.10.5"
serde = { version = "1.0", features = ["derive"] }
thiserror = "1.0.63"
quick-xml = { version = "0.37", features = ["serialize"] }

Even though Raw XML contains the data why is it not serializing it properly ??


Solution

  • Namespaces like media: aren't currently supported by serde: https://github.com/tafia/quick-xml/issues/218

    As a workaround, since there aren't fields with the same name without the namespace, you can just drop the prefix, e.g., rename to group:

    #[derive(Debug, Deserialize)]
    struct Entry {
        id: String,
        title: String,
        published: String,
        #[serde(rename = "group")]
        media_group: Option<MediaGroup>,
    }
    
    #[derive(Debug, Deserialize)]
    struct MediaGroup {
        #[serde(rename = "title")]
        title: Option<String>,
        #[serde(rename = "content")]
        content: Option<MediaContent>,
        #[serde(rename = "thumbnail")]
        thumbnail: Option<MediaThumbnail>,
        #[serde(rename = "community")]
        community: Option<MediaCommunity>,
    }
    
    #[derive(Debug, Deserialize)]
    struct MediaCommunity {
        #[serde(rename = "starRating")]
        star_rating: Option<StarRating>,
        #[serde(rename = "statistics")]
        statistics: Option<Statistics>,
    }