#[macro_export]
macro_rules! macro_generate_rss {
($writer:expr, $options:expr) => {{
use quick_xml::events::{
BytesDecl, BytesEnd, BytesStart, BytesText, Event,
};
let mut writer = $writer;
writer.write_event(Event::Decl(BytesDecl::new(
"1.0",
Some("utf-8"),
None,
)))?;
let mut rss_start = BytesStart::new("rss");
rss_start.push_attribute(("version", "2.0"));
rss_start.push_attribute((
"xmlns:atom",
"http://www.w3.org/2005/Atom",
));
writer.write_event(Event::Start(rss_start))?;
writer.write_event(Event::Start(BytesStart::new("channel")))?;
macro_write_element!(writer, "title", &$options.title)?;
macro_write_element!(writer, "link", &$options.link)?;
macro_write_element!(
writer,
"description",
&$options.description
)?;
macro_write_element!(writer, "language", &$options.language)?;
macro_write_element!(writer, "pubDate", &$options.pub_date)?;
macro_write_element!(
writer,
"lastBuildDate",
&$options.last_build_date
)?;
macro_write_element!(writer, "docs", &$options.docs)?;
macro_write_element!(writer, "generator", &$options.generator)?;
macro_write_element!(
writer,
"managingEditor",
&$options.managing_editor
)?;
macro_write_element!(writer, "webMaster", &$options.webmaster)?;
macro_write_element!(writer, "category", &$options.category)?;
macro_write_element!(writer, "ttl", &$options.ttl)?;
if !$options.image_url.is_empty() {
writer
.write_event(Event::Start(BytesStart::new("image")))?;
macro_write_element!(writer, "url", &$options.image_url)?;
macro_write_element!(writer, "title", &$options.title)?;
macro_write_element!(writer, "link", &$options.link)?;
writer.write_event(Event::End(BytesEnd::new("image")))?;
}
if !$options.atom_link.is_empty() {
let mut atom_link_start = BytesStart::new("atom:link");
atom_link_start
.push_attribute(("href", $options.atom_link.as_str()));
atom_link_start.push_attribute(("rel", "self"));
atom_link_start
.push_attribute(("type", "application/rss+xml"));
writer.write_event(Event::Empty(atom_link_start))?;
}
writer.write_event(Event::Start(BytesStart::new("item")))?;
macro_write_element!(writer, "title", &$options.title)?;
macro_write_element!(writer, "link", &$options.link)?;
macro_write_element!(
writer,
"description",
&$options.description
)?;
macro_write_element!(writer, "author", &$options.author)?;
macro_write_element!(writer, "guid", &$options.guid)?;
macro_write_element!(writer, "pubDate", &$options.pub_date)?;
writer.write_event(Event::End(BytesEnd::new("item")))?;
writer.write_event(Event::End(BytesEnd::new("channel")))?;
writer.write_event(Event::End(BytesEnd::new("rss")))?;
Ok(writer)
}};
}
#[macro_export]
macro_rules! macro_write_element {
($writer:expr, $name:expr, $content:expr) => {{
if !$content.is_empty() {
$writer
.write_event(Event::Start(BytesStart::new($name)))?;
$writer
.write_event(Event::Text(BytesText::new($content)))?;
$writer.write_event(Event::End(BytesEnd::new($name)))?;
}
Ok::<(), quick_xml::Error>(())
}};
}
#[macro_export]
macro_rules! macro_set_rss_data_fields {
($rss_data:expr, $($field:ident = $value:expr),+ $(,)?) => {
$rss_data = $rss_data $(.set($crate::data::RssDataField::$field, $value))+
};
}
#[macro_export]
macro_rules! macro_get_args {
($matches:ident, $name:expr) => {
$matches.get_one::<String>($name).ok_or(format!(
"❌ Error: A required parameter was omitted. Add the required parameter. \"{}\".",
$name
))?
};
}
#[macro_export]
macro_rules! macro_metadata_option {
($metadata:ident, $key:expr) => {
$metadata.get($key).cloned().unwrap_or_default()
};
}
#[cfg(test)]
mod tests {
use crate::RssData;
use quick_xml::Writer;
use std::collections::HashMap;
use std::io::Cursor;
#[test]
fn test_macro_generate_rss() -> Result<(), quick_xml::Error> {
let options = RssData::new(None)
.title("Test RSS Feed")
.link("https://example.com")
.description("A test RSS feed");
let writer = Writer::new(Cursor::new(Vec::new()));
let result: Result<
Writer<Cursor<Vec<u8>>>,
Box<dyn std::error::Error>,
> = macro_generate_rss!(writer, options);
assert!(result.is_ok());
let writer = result.unwrap();
let content =
String::from_utf8(writer.into_inner().into_inner())
.unwrap();
assert!(content.contains(r#"<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">"#));
assert!(content.contains("<title>Test RSS Feed</title>"));
assert!(content.contains("<link>https://example.com</link>"));
assert!(content
.contains("<description>A test RSS feed</description>"));
Ok(())
}
#[test]
fn test_macro_generate_rss_valid_data(
) -> Result<(), quick_xml::Error> {
let options = RssData::new(None)
.title("Test RSS Feed")
.link("https://example.com")
.description("A test RSS feed");
let writer = Writer::new(Cursor::new(Vec::new()));
let result: Result<
Writer<Cursor<Vec<u8>>>,
Box<dyn std::error::Error>,
> = macro_generate_rss!(writer, options);
assert!(result.is_ok());
let writer = result.unwrap();
let content =
String::from_utf8(writer.into_inner().into_inner())
.unwrap();
assert!(content.contains(r#"<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">"#));
assert!(content.contains("<title>Test RSS Feed</title>"));
assert!(content.contains("<link>https://example.com</link>"));
assert!(content
.contains("<description>A test RSS feed</description>"));
Ok(())
}
#[test]
fn test_macro_generate_rss_missing_fields(
) -> Result<(), quick_xml::Error> {
let options = RssData::new(None)
.title("Test RSS Feed")
.link("https://example.com");
let writer = Writer::new(Cursor::new(Vec::new()));
let result: Result<
Writer<Cursor<Vec<u8>>>,
Box<dyn std::error::Error>,
> = macro_generate_rss!(writer, options);
assert!(result.is_ok());
let writer = result.unwrap();
let content =
String::from_utf8(writer.into_inner().into_inner())
.unwrap();
assert!(content.contains("<title>Test RSS Feed</title>"));
assert!(content.contains("<link>https://example.com</link>"));
assert!(!content.contains("<description>"));
Ok(())
}
#[test]
fn test_macro_set_rss_data_fields() {
let mut rss_data = RssData::new(None);
macro_set_rss_data_fields!(
rss_data,
Title = "My Blog",
Link = "https://example.com",
Description = "A blog about Rust"
);
assert_eq!(rss_data.title, "My Blog");
assert_eq!(rss_data.link, "https://example.com");
assert_eq!(rss_data.description, "A blog about Rust");
}
#[test]
fn test_macro_metadata_option_existing_key() {
let mut metadata = HashMap::new();
metadata.insert("author".to_string(), "John Doe".to_string());
let value = macro_metadata_option!(metadata, "author");
assert_eq!(value, "John Doe");
}
#[test]
fn test_macro_metadata_option_missing_key() {
let mut metadata = HashMap::new();
metadata.insert("title".to_string(), "Rust Blog".to_string());
let value = macro_metadata_option!(metadata, "author"); assert_eq!(value, ""); }
#[test]
fn test_macro_metadata_option_empty_metadata() {
let metadata: HashMap<String, String> = HashMap::new();
let value = macro_metadata_option!(metadata, "nonexistent_key");
assert_eq!(value, "");
}
}