use chrono::{DateTime, Utc};
use quick_xml::Writer;
use quick_xml::events::{BytesDecl, BytesText, Event};
use super::error::FeedError;
use super::{Channel, PreparedEntry, RSS_PATH};
const FORMAT: &str = "RSS";
pub fn render(
channel: &Channel,
entries: &[PreparedEntry],
last_build: DateTime<Utc>,
) -> Result<String, FeedError> {
let mut writer: Writer<Vec<u8>> = Writer::new(Vec::new());
write(&mut writer, channel, entries, last_build).map_err(|source| FeedError::Write {
format: FORMAT,
source,
})?;
String::from_utf8(writer.into_inner()).map_err(|_| FeedError::Encoding { format: FORMAT })
}
fn write(
writer: &mut Writer<Vec<u8>>,
channel: &Channel,
entries: &[PreparedEntry],
last_build: DateTime<Utc>,
) -> std::io::Result<()> {
let self_url: String = format!("{}{RSS_PATH}", channel.base_url);
writer.write_event(Event::Decl(BytesDecl::new("1.0", Some("utf-8"), None)))?;
writer
.create_element("rss")
.with_attribute(("version", "2.0"))
.with_attribute(("xmlns:atom", "http://www.w3.org/2005/Atom"))
.with_attribute(("xmlns:content", "http://purl.org/rss/1.0/modules/content/"))
.with_attribute(("xmlns:media", "http://search.yahoo.com/mrss/"))
.write_inner_content(|rss| {
rss.create_element("channel")
.write_inner_content(|element| {
text(element, "title", &channel.title)?;
text(element, "link", &channel.home_url)?;
text(element, "description", &channel.description)?;
text(element, "language", &channel.language)?;
text(element, "copyright", &channel.copyright)?;
text(element, "lastBuildDate", &rfc2822(last_build))?;
element
.create_element("atom:link")
.with_attribute(("href", self_url.as_str()))
.with_attribute(("rel", "self"))
.with_attribute(("type", "application/rss+xml"))
.write_empty()?;
if let Some(icon) = &channel.icon_url {
element
.create_element("image")
.write_inner_content(|image| {
text(image, "url", icon)?;
text(image, "title", &channel.title)?;
text(image, "link", &channel.home_url)
})?;
}
for entry in entries {
element
.create_element("item")
.write_inner_content(|item| self::item(item, entry))?;
}
Ok(())
})?;
Ok(())
})?;
Ok(())
}
fn item(writer: &mut Writer<Vec<u8>>, entry: &PreparedEntry) -> std::io::Result<()> {
text(writer, "title", &entry.title)?;
text(writer, "link", &entry.url)?;
writer
.create_element("guid")
.with_attribute(("isPermaLink", "true"))
.write_text_content(BytesText::new(&entry.url))?;
text(writer, "pubDate", &rfc2822(entry.published))?;
text(writer, "description", &entry.summary)?;
if let Some(content) = &entry.content_html {
text(writer, "content:encoded", content)?;
}
for tag in &entry.tags {
text(writer, "category", tag)?;
}
if let Some(image) = &entry.image_url {
writer
.create_element("media:thumbnail")
.with_attribute(("url", image.as_str()))
.write_empty()?;
}
Ok(())
}
fn text(writer: &mut Writer<Vec<u8>>, name: &str, value: &str) -> std::io::Result<()> {
writer
.create_element(name)
.write_text_content(BytesText::new(value))?;
Ok(())
}
fn rfc2822(moment: DateTime<Utc>) -> String {
moment.to_rfc2822()
}