goodpod 0.1.2

A crate for generating Podcasting 2.0 feeds.
Documentation
use crate::itunes;
use crate::podcast;
use crate::Language;

use url::Url;
use uuid::Uuid;
use xml::attribute::OwnedAttribute;
use xml::namespace::Namespace;

pub struct Rss {
    pub channel: Channel,
}

impl yaserde::YaSerialize for Rss {
    fn serialize<W: std::io::Write>(
        &self,
        writer: &mut yaserde::ser::Serializer<W>,
    ) -> Result<(), String> {
        let data_event = xml::writer::XmlEvent::characters("\n");
        writer.write(data_event).map_err(|e| e.to_string())?;

        let mut rss_el = xml::writer::XmlEvent::start_element("rss");
        rss_el = rss_el.attr("version", "2.0");

        if self.channel.has_itunes_tags() {
            rss_el = rss_el.ns("itunes", "http://www.itunes.com/dtds/podcast-1.0.dtd");
        }

        if self.channel.has_podcast_tags() {
            rss_el = rss_el.ns("podcast", "https://podcastindex.org/namespace/1.0");
        }

        writer.write(rss_el).map_err(|e| e.to_string())?;

        self.channel.serialize(writer)?;

        writer
            .write(xml::writer::XmlEvent::end_element())
            .map_err(|e| e.to_string())?;
        Ok(())
    }

    fn serialize_attributes(
        &self,
        attributes: Vec<OwnedAttribute>,
        namespace: Namespace,
    ) -> Result<(Vec<OwnedAttribute>, Namespace), String> {
        Ok((attributes, namespace))
    }
}

#[derive(Default)]
pub struct Channel {
    pub description: Option<String>,
    pub generator: Option<String>,
    pub language: Option<Language>,
    pub last_build_date: Option<chrono::DateTime<chrono::Utc>>,
    pub title: Option<String>,
    pub link: Option<Url>,

    pub itunes_image: Option<itunes::Image>,

    pub podcast_funding: Vec<podcast::Funding>,
    pub podcast_guid: Option<Uuid>,
    pub podcast_locked: Option<podcast::Locked>,
    pub podcast_medium: Option<podcast::Medium>,
    pub podcast_remote_items: Vec<podcast::RemoteItem>,
}

impl Channel {
    fn has_itunes_tags(&self) -> bool {
        self.itunes_image.is_some()
    }

    fn has_podcast_tags(&self) -> bool {
        !self.podcast_funding.is_empty()
            || self.podcast_guid.is_some()
            || self.podcast_locked.is_some()
            || self.podcast_medium.is_some()
            || !self.podcast_remote_items.is_empty()
    }
}

impl yaserde::YaSerialize for Channel {
    fn serialize<W: std::io::Write>(
        &self,
        writer: &mut yaserde::ser::Serializer<W>,
    ) -> Result<(), String> {
        writer
            .write(xml::writer::XmlEvent::start_element("channel"))
            .map_err(|e| e.to_string())?;

        if let Some(description) = &self.description {
            writer
                .write(xml::writer::XmlEvent::start_element("description"))
                .map_err(|e| e.to_string())?;
            writer
                .write(xml::writer::XmlEvent::characters(description))
                .map_err(|e| e.to_string())?;
            writer
                .write(xml::writer::XmlEvent::end_element())
                .map_err(|e| e.to_string())?;
        }

        if let Some(generator) = &self.generator {
            writer
                .write(xml::writer::XmlEvent::start_element("generator"))
                .map_err(|e| e.to_string())?;
            writer
                .write(xml::writer::XmlEvent::characters(generator))
                .map_err(|e| e.to_string())?;
            writer
                .write(xml::writer::XmlEvent::end_element())
                .map_err(|e| e.to_string())?;
        }

        if let Some(language) = &self.language {
            writer
                .write(xml::writer::XmlEvent::start_element("language"))
                .map_err(|e| e.to_string())?;
            writer
                .write(xml::writer::XmlEvent::characters(
                    language.to_string().as_str(),
                ))
                .map_err(|e| e.to_string())?;
            writer
                .write(xml::writer::XmlEvent::end_element())
                .map_err(|e| e.to_string())?;
        }

        if let Some(last_build_date) = &self.last_build_date {
            writer
                .write(xml::writer::XmlEvent::start_element("lastBuildDate"))
                .map_err(|e| e.to_string())?;
            writer
                .write(xml::writer::XmlEvent::characters(
                    &last_build_date
                        .to_rfc2822()
                        .to_string()
                        .replace("+0000", "GMT"),
                ))
                .map_err(|e| e.to_string())?;
            writer
                .write(xml::writer::XmlEvent::end_element())
                .map_err(|e| e.to_string())?;
        }

        if let Some(title) = &self.title {
            writer
                .write(xml::writer::XmlEvent::start_element("title"))
                .map_err(|e| e.to_string())?;
            writer
                .write(xml::writer::XmlEvent::characters(title))
                .map_err(|e| e.to_string())?;
            writer
                .write(xml::writer::XmlEvent::end_element())
                .map_err(|e| e.to_string())?;
        }

        if let Some(link) = &self.link {
            writer
                .write(xml::writer::XmlEvent::start_element("link"))
                .map_err(|e| e.to_string())?;
            writer
                .write(xml::writer::XmlEvent::characters(
                    link.as_str().trim_end_matches('/'),
                ))
                .map_err(|e| e.to_string())?;
            writer
                .write(xml::writer::XmlEvent::end_element())
                .map_err(|e| e.to_string())?;
        }

        if let Some(itunes_image) = &self.itunes_image {
            itunes_image.serialize(writer)?;
        }

        if let Some(podcast_guid) = &self.podcast_guid {
            crate::podcast::serialize_guid(writer, *podcast_guid)?;
        }

        for funding in &self.podcast_funding {
            funding.serialize(writer)?;
        }

        if let Some(podcast_locked) = &self.podcast_locked {
            podcast_locked.serialize(writer)?;
        }

        if let Some(podcast_medium) = &self.podcast_medium {
            podcast_medium.serialize(writer)?;
        }

        for remote_item in &self.podcast_remote_items {
            remote_item.serialize(writer)?;
        }

        writer
            .write(xml::writer::XmlEvent::end_element())
            .map_err(|e| e.to_string())?;

        Ok(())
    }

    fn serialize_attributes(
        &self,
        attributes: Vec<OwnedAttribute>,
        namespace: Namespace,
    ) -> Result<(Vec<OwnedAttribute>, Namespace), String> {
        Ok((attributes, namespace))
    }
}