Sign in

    rss

    RSS syndication format library (port of the Rust rss crate)

    Download zip
    Version
    0.2.1
    License
    Apache-2.0
    Last updated
    3 hours ago
    Downloads
    16

    #marianoguerra/rss

    An idiomatic MoonBit library for reading and writing the RSS web content syndication format — a port of the Rust rss crate, with full Atom 1.0 support via marianoguerra/atom and a built-in streaming XML engine (marianoguerra/xml).

    #Supported versions

    • Reading: RSS 0.90, 0.91, 0.92, 1.0 and 2.0 (plus Atom via from_atom)
    • Writing: RSS 2.0

    #Reading

    ///|
    test {
    let channel = try! @rss.Channel::read_from(
    "<?xml version=\"1.0\"?><rss version=\"2.0\"><channel><title>News</title><link>https://example.com/</link><description>An example feed.</description><item><title>Hello</title></item></channel></rss>",
    )
    inspect(channel.title, content="News")
    inspect(channel.items[0].title.unwrap_or(""), content="Hello")
    }

    As a best effort for invalid feeds, elements declared required by RSS 2.0 default to an empty string.

    #Writing

    ///|
    test {
    let channel = try! @rss.Channel::read_from(
    "<rss version=\"2.0\"><channel><title>T</title><link>https://e.com/</link><description>D</description></channel></rss>",
    )
    let xml = channel.to_xml()
    // Writing then parsing reproduces the same model.
    let again = try! @rss.Channel::read_from(xml)
    assert_true(again == channel)
    }

    #Validation

    ///|
    test {
    let channel = try! @rss.Channel::read_from(
    "<rss version=\"2.0\"><channel><title>T</title><link>notaurl</link><description>D</description></channel></rss>",
    )
    let verdict = try {
    ignore(channel.validate())
    "ok"
    } catch {
    _ => "failed"
    }
    inspect(verdict, content="failed")
    }

    #Extensions

    Elements in non-default namespaces are collected generically into Channel.extensions / Item.extensions, and the well-known namespaces are additionally extracted into typed structs:

    • iTunes podcast metadata (itunes_ext)
    • Dublin Core (dublin_core_ext)
    • Syndication (syndication_ext)
    • Atom links (atom_links)

    #Atom interop

    ///|
    test {
    let channel = try! @rss.Channel::read_from(
    "<rss version=\"2.0\"><channel><title>T</title><link>https://e.com/</link><description>D</description></channel></rss>",
    )
    let feed = channel.to_atom()
    inspect(feed.title.value, content="T")
    let back = @rss.Channel::from_atom(feed)
    assert_true(back.title == channel.title)
    }

    #Testing methodology

    This port is validated against the Rust crate itself:

    • Golden parity tests — every fixture from the crate's own test suite is parsed by both implementations; the resulting models must be identical, and our writer's output must be byte-identical to Channel::to_string.
    • Live differential testing — scripts/oracle-roundtrip.sh runs four round-trip directions between this library and a vendored Rust oracle CLI (oracle/, pinned to rss = "=2.1.0"). Regenerate goldens after changing fixtures with scripts/gen-goldens.sh (needs cargo).
    • Property-based tests — moonbitlang/quickcheck verifies parse(write(c)) == c and writer idempotence over generated channels whose text exercises escaping edge cases.
    • Fuzzing — deterministic mutation fuzzing over the fixture corpus asserts the parser always returns cleanly and never panics.

    The golden files are committed, so moon test runs the full parity suite without a Rust toolchain.

    #License

    Apache-2.0

    ExtensionMap

    type ExtensionMap = Map[String, Map[String, Array[Extension]]]

    A map of extension namespace prefixes to local names to elements.

    Mirrors rss::extension::ExtensionMap.

    RssError

    pub(all) suberror RssError {
    Xml(err~ :
    XmlError
    )
    InvalidStartTag
    Eof
    } derive(Eq,
    Debug
    )

    Errors that occur while reading or writing an RSS channel.
    impl Show for RssError

    RssError::to_string

    fn RssError::to_string(self : RssError) -> String

    ValidationError

    pub(all) suberror ValidationError {
    Invalid(String)
    Validation(String)
    } derive(Eq,
    Debug
    )

    Errors produced when a value fails RSS-spec validation, mirroring rss::validation::ValidationError.

    ValidationError::to_string

    fn ValidationError::to_string(self : ValidationError) -> String

    Category

    pub(all) struct Category {
    domain : String?
    name : String
    } derive(Eq,
    Debug
    )

    Represents a category in an RSS feed.

    Category::validate

    fn Category::validate(self : Category) -> Unit raise ValidationError

    Validate this category against the RSS spec.

    Channel

    pub(all) struct Channel {
    title : String
    link : String
    description : String
    language : String?
    copyright : String?
    managing_editor : String?
    webmaster : String?
    pub_date : String?
    last_build_date : String?
    categories : Array[Category]
    generator : String?
    docs : String?
    cloud : Cloud?
    rating : String?
    ttl : String?
    image : Image?
    text_input : TextInput?
    skip_hours : Array[String]
    skip_days : Array[String]
    items : Array[Item]
    extensions : Map[String, Map[String, Array[Extension]]]
    atom_links : Array[
    Link
    ]
    itunes_ext : ITunesChannelExtension?
    dublin_core_ext : DublinCoreExtension?
    syndication_ext : SyndicationExtension?
    namespaces : Map[String, String]
    } derive(Eq,
    Debug
    )

    Represents the channel of an RSS feed.
    impl Show for Channel

    Channel::from_atom

    Convert an Atom feed into an RSS channel (the inverse of [to_atom]), so Atom feeds can be consumed with the same API.

    Channel::from_canon

    fn Channel::from_canon(json : Json) -> Channel

    Reconstruct a channel from canonical JSON.

    Channel::read_from

    fn Channel::read_from(input : String) -> Channel raise RssError

    Parse an RSS feed from any of the supported versions (RSS 0.90, 0.91, 0.92, 1.0 and 2.0).

    Mirrors Channel::read_from. As a best effort for invalid feeds, elements the RSS 2.0 spec declares required default to an empty string.

    Channel::to_atom

    Convert an RSS channel to an Atom 1.0 feed.

    Mapping (documented, lossy by nature — Atom has no cloud/textInput/etc.):
    • title → <title> (plain text)
    • link → <link rel="alternate">
    • description → <subtitle>
    • item guid → entry <id>, falling back to the item link
    • item title/link/description/categories map 1:1

    Channel::to_canon

    fn Channel::to_canon(self : Channel) -> Json

    Convert a parsed channel to the canonical JSON form.

    Channel::to_string

    fn Channel::to_string(self : Channel) -> String

    Channel::to_xml

    fn Channel::to_xml(self : Channel) -> String

    Serialise the channel as RSS 2.0 XML.

    Channel::validate

    fn Channel::validate(self : Channel) -> Unit raise ValidationError

    Validate this channel against the RSS specification.

    Cloud

    pub(all) struct Cloud {
    domain : String
    port : String
    path : String
    register_procedure : String
    protocol : String
    } derive(Eq,
    Debug
    )

    Represents a cloud in an RSS feed: a registry for update notifications.

    Cloud::validate

    fn Cloud::validate(self : Cloud) -> Unit raise ValidationError

    Validate this cloud against the RSS spec.

    DublinCoreExtension

    pub(all) struct DublinCoreExtension {
    contributors : Array[String]
    coverages : Array[String]
    creators : Array[String]
    dates : Array[String]
    descriptions : Array[String]
    formats : Array[String]
    identifiers : Array[String]
    languages : Array[String]
    publishers : Array[String]
    relations : Array[String]
    rights : Array[String]
    sources : Array[String]
    subjects : Array[String]
    titles : Array[String]
    types : Array[String]
    } derive(Eq,
    Debug
    )

    Dublin Core metadata extracted from a channel or item.

    Every field is a list because Dublin Core elements are repeatable.

    DublinCoreExtension::from_map

    fn DublinCoreExtension::from_map(map : Map[String, Array[Extension]]) -> DublinCoreExtension

    Build a Dublin Core extension from parsed generic extensions, mirroring DublinCoreExtension::from_map.

    Enclosure

    pub(all) struct Enclosure {
    url : String
    length : String
    mime_type : String
    } derive(Eq,
    Debug
    )

    Represents an enclosure (media attachment) in an RSS item.

    Enclosure::validate

    fn Enclosure::validate(self : Enclosure) -> Unit raise ValidationError

    Validate this enclosure against the RSS spec.

    Extension

    pub(all) struct Extension {
    name : String
    value : String?
    attrs : Map[String, String]
    children : Map[String, Array[Extension]]
    } derive(Eq,
    Debug
    )

    An element from a non-default namespace, kept generically.

    Guid

    pub(all) struct Guid {
    is_permalink : Bool
    value : String
    } derive(Eq,
    Debug
    )

    Represents the GUID of an RSS item.

    ITunesCategory

    pub(all) struct ITunesCategory {
    text : String
    subcategory : ITunesCategory?
    } derive(Eq,
    Debug
    )

    An iTunes category with an optional nested subcategory.

    ITunesChannelExtension

    pub(all) struct ITunesChannelExtension {
    author : String?
    block : String?
    categories : Array[ITunesCategory]
    image : String?
    explicit : String?
    complete : String?
    new_feed_url : String?
    owner : ITunesOwner?
    subtitle : String?
    summary : String?
    keywords : String?
    itunes_type : String?
    } derive(Eq,
    Debug
    )

    iTunes metadata extracted from a channel.

    ITunesChannelExtension::from_map

    Build a channel extension from parsed generic extensions, mirroring ITunesChannelExtension::from_map.

    ITunesItemExtension

    pub(all) struct ITunesItemExtension {
    author : String?
    block : String?
    image : String?
    duration : String?
    explicit : String?
    closed_captioned : String?
    order : String?
    subtitle : String?
    summary : String?
    keywords : String?
    episode : String?
    season : String?
    episode_type : String?
    } derive(Eq,
    Debug
    )

    iTunes metadata extracted from an item.

    ITunesItemExtension::from_map

    fn ITunesItemExtension::from_map(map : Map[String, Array[Extension]]) -> ITunesItemExtension

    Build an item extension from parsed generic extensions, mirroring ITunesItemExtension::from_map.

    ITunesOwner

    pub(all) struct ITunesOwner {
    name : String?
    email : String?
    } derive(Eq,
    Debug
    )

    Contact information for the owner of a podcast.

    Image

    pub(all) struct Image {
    url : String
    title : String
    link : String
    width : String?
    height : String?
    description : String?
    } derive(Eq,
    Debug
    )

    Represents an image that can be displayed with a channel.

    Image::validate

    fn Image::validate(self : Image) -> Unit raise ValidationError

    Validate this image against the RSS spec.

    Item

    pub(all) struct Item {
    title : String?
    link : String?
    description : String?
    author : String?
    categories : Array[Category]
    comments : String?
    enclosure : Enclosure?
    guid : Guid?
    pub_date : String?
    source : Source?
    content : String?
    extensions : Map[String, Map[String, Array[Extension]]]
    atom_links : Array[
    Link
    ]
    itunes_ext : ITunesItemExtension?
    dublin_core_ext : DublinCoreExtension?
    } derive(Eq,
    Debug
    )

    Represents an item in an RSS feed.

    Item::validate

    fn Item::validate(self : Item) -> Unit raise ValidationError

    Validate this item against the RSS spec.

    Source

    pub(all) struct Source {
    url : String
    title : String?
    } derive(Eq,
    Debug
    )

    Represents the source of an RSS item.

    Source::validate

    fn Source::validate(self : Source) -> Unit raise ValidationError

    Validate this source against the RSS spec.

    SyndicationExtension

    pub(all) struct SyndicationExtension {
    period : UpdatePeriod
    frequency : Int
    base : String
    } derive(Eq,
    Debug
    )

    Syndication metadata extracted from a channel (when to refresh, how often, and relative to which base time).

    SyndicationExtension::from_map

    Build a syndication extension from parsed generic extensions, mirroring SyndicationExtension::from_map.

    TextInput

    pub(all) struct TextInput {
    title : String
    description : String
    name : String
    link : String
    } derive(Eq,
    Debug
    )

    Represents a text input that can be displayed with a channel.

    TextInput::validate

    fn TextInput::validate(self : TextInput) -> Unit raise ValidationError

    Validate this text input against the RSS spec.

    UpdatePeriod

    pub(all) enum UpdatePeriod {
    Hourly
    Daily
    Weekly
    Monthly
    Yearly
    } derive(Eq,
    Debug
    )

    The update period of a syndicated feed.

    UpdatePeriod::to_string

    fn UpdatePeriod::to_string(self : UpdatePeriod) -> String

    Expose to_string as a real method, the inverse of [UpdatePeriod::parse].

    DUBLIN_CORE_NAMESPACE

    let DUBLIN_CORE_NAMESPACE : String

    The Dublin Core namespace.

    ITUNES_NAMESPACE

    let ITUNES_NAMESPACE : String

    The iTunes podcast namespace.

    SYNDICATION_NAMESPACE

    let SYNDICATION_NAMESPACE : String

    The Syndication namespace.

    canonical_stringify

    fn canonical_stringify(j : Json) -> String

    Compact, deterministic serialisation: object keys are emitted in sorted order (matching serde_json over BTreeMaps).

    extension_entry

    fn extension_entry(extensions : Map[String, Map[String, Array[Extension]]], ns : String, name : String) -> Array[Extension]

    Record an extension element under ns / name.

    parse_rfc2822

    fn parse_rfc2822(s : String) -> Bool

    Parse an RFC 822 / RFC 2822 date-time, returning its components. Accepts the common forms used by feeds: [Weekday,] DD Mon YYYY HH:MM[:SS] Zone.

    sort_json_keys

    fn sort_json_keys(j : Json) -> Json

    Recursively sort JSON object keys so that output is byte-identical to serde_json's BTreeMap-based serialisation used by the oracle.

    str_lexicographic

    fn str_lexicographic(a : String, b : String) -> Int

    Lexicographic (code-unit wise) string comparison, matching the byte-wise Ord of Rust strings used by the oracle's BTreeMap ordering.