{"id":19179410,"url":"https://github.com/rssblue/badpod","last_synced_at":"2025-05-07T21:46:27.143Z","repository":{"id":61768257,"uuid":"550356020","full_name":"rssblue/badpod","owner":"rssblue","description":"A Rust crate for working with imperfect feeds of podcasts.","archived":false,"fork":false,"pushed_at":"2024-06-30T19:41:14.000Z","size":254,"stargazers_count":3,"open_issues_count":6,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-07T21:46:25.801Z","etag":null,"topics":["podcast","rss","xml"],"latest_commit_sha":null,"homepage":"https://docs.rs/badpod","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rssblue.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-10-12T16:12:25.000Z","updated_at":"2024-06-30T19:41:17.000Z","dependencies_parsed_at":"2024-02-09T00:55:18.289Z","dependency_job_id":"2a26ce27-6cf4-4357-a514-b29b792cdae3","html_url":"https://github.com/rssblue/badpod","commit_stats":{"total_commits":189,"total_committers":2,"mean_commits":94.5,"dds":0.005291005291005346,"last_synced_commit":"0a9fc67386360bfaa12c079b40c8cfb69086b3d6"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rssblue%2Fbadpod","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rssblue%2Fbadpod/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rssblue%2Fbadpod/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rssblue%2Fbadpod/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rssblue","download_url":"https://codeload.github.com/rssblue/badpod/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252961858,"owners_count":21832192,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["podcast","rss","xml"],"created_at":"2024-11-09T10:43:02.836Z","updated_at":"2025-05-07T21:46:27.100Z","avatar_url":"https://github.com/rssblue.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# badpod\n\nA Rust crate for working with imperfect feeds of podcasts.\n\nThis crate *should not* be used for  \n❌ working with proper, processed podcast feeds  \n❌ interfacing with your database\n\nThis crate *can be* used for  \n✅ interpreting external feeds, often from unknown sources  \n✅ providing feedback about the contents of the feed\n\n## Motivation\n\nOn a backend server, which has to communicate with a database, strict schemas are typically used.\nTherefore, if content from an external source that does not conform to the schema is loaded, one may fail to deserialize that content successfully.\n\nThis scenario is very common in podcasting space, where RSS feeds of podcasts may  \n- combine multiple standards (namespaces)\n- have some elements missing\n- have some values in wrong data types\n- etc.\n\nIn that case, we may want a more flexible, intermediate schema: a schema that does *not* throw an error the instant it encounters an unexpected value but rather one that **deserializes the content that it is able to** and **stores the content that it failed to deserialize** for further cleanup or analysis.\n\n## Usage\n\n### Including in a project\n\n```\ncargo add badpod\n```\n\nThis will include the latest version of the crate in your `Cargo.toml` file.\n\n### Deserializing\n\n```rust\nlet rss = match badpod::from_str(feed_str) {\n    Ok(rss) =\u003e rss,\n    Err(_) =\u003e panic!(\"Something went terribly wrong.\"),\n};\n```\n\nIn theory, `badpod::from_str` should only return an error in two cases:  \n- the feed is not valid XML\n- the root element of the feed is not `\u003crss\u003e`\n\n## Features\n\n### Check for presence of tags and attributes\n\nIn `badpod`, every field representing an XML tag is a [Vec](std::vec::Vec)tor, and every field representing an XML attribute is an [Option](std::option::Option).\nThis is to reflect that in XML, tags *can* be repeated, while attributes---*cannot*.\nWe don't enforce any requirements on what or how many tags should be in the feed---that's a decision for you to make!\nLeaving it more flexible (instead of throwing an instant error) also allows providing users with better feedback.\n```rust\nmatch (value_time_split.remote_item.len(), value_time_split.value_recipient.len()) {\n    (0, 0) =\u003e println!(\"Either a single `\u003cpodcast:remoteItem\u003e` element or one or more `\u003cpodcast:valueRecipient\u003e` elements are required.\"),\n    (1, 0) =\u003e println!(\"You are referencing a remote item! Awesome!\"),\n    (_, 0) =\u003e println!(\"Only a single `\u003cpodcast:remoteItem\u003e` element is allowed.\"),\n    (0, _) =\u003e println!(\"You are sharing value with others during this segment! Nice!\"),\n    (_, _) =\u003e println!(\"Either a single `\u003cpodcast:remoteItem\u003e` element or one or more `\u003cpodcast:valueRecipient\u003e` elements can be included.\"),\n};\n```\n\n*Note*: all fields representing tags are named in the singular form, even though they are vectors.\n\n### Deserializing complicated tags\n\n`badpod` converts complex data in text format to something that is easier to work with.\nIf that is not possible, we provide enums with variant `Other`, which is meant to represent data that could not be deserialized and the reason for that failed deserialization.\n```rust\nmatch geo {\n    podcast::Geo::Ok {\n        // f64\n        latitude,\n        // f64\n        longitude,\n        // Option\u003cf64\u003e\n        altitude,\n        // Option\u003cf64\u003e\n        uncertainty,\n    } =\u003e {\n        println!(\"Successfully extracted geographical coordinates!\")\n    }\n    podcast::Geo::Other((s, reason)) =\u003e {\n        println!(\"Could not parse coordinates from \\\"{s}\\\": {reason}.\")\n    }\n};\n```\n\n`Other` variant can be especially useful in enums with many variants.\nIf you don't get a match with `Other`, you know that deserialization yielded something that is reasonably expected.\n```rust\nmatch language {\n    Language::English(region) =\u003e println!(\"A variant of English!\"),\n    Language::Lithuanian =\u003e println!(\"Lithuanian!\"),\n    Language::Other((s, _)) =\u003e println!(\"Unexpected language code \\\"{s}\\\".\"),\n    _ =\u003e println!(\"Some other valid language!\"),\n};\n```\n\nBut just because you match a variant that is not `Other` does not mean that it is a valid value for *you*.\nFor example, [MimeEnclosure](crate::MimeEnclosure) has `AudioOpus` as one of the variants, but if you require that feeds only contain media files with formats supported by Apple Podcasts, then you will want to reject this.\nAgain, `badpod` is only a tool for analyzing feeds; you decide what a \"proper\" feed must look like.\n\n### Tag-aware deserialization\n\nMany tags use the same data types but encode them differently.\nFor example, both `\u003cpodcast:locked\u003e` and `\u003citunes:explicit\u003e` are essentially boolean values, but the former serializes to `\"yes\"`/`\"no\"` and the latter to `\"true\"`/`\"false\"`.\nIn `badpod`, both are deserialized to [Bool](crate::Bool).\n```rust\nmatch channel.itunes_explicit.get(0) {\n    Some(is_explicit) =\u003e {\n        match is_explicit {\n            Bool::Ok(b) =\u003e {\n                println!(\"is explicit? \\\"{b}\\\"\")\n            }\n            Bool::Other((s, reason)) =\u003e println!(\"could not parse \\\"{s}\\\": {reason}\"),\n        }\n    }\n    None =\u003e println!(\"\u003citunes:explicit\u003e not found.\"),\n};\n```\n\n### Printing enums\n\nAlthough this crate is mainly for deserialization, there are cases when enums need to be converted back to strings.\nIn `badpod`, all enums have [Display](std::fmt::Display) trait implemented:\n```rust\n// Outputs \"cs\".\nprintln!(\"{}\", Language::Czech);\n\n// Outputs \"en\".\nprintln!(\"{}\", Language::English(LanguageEnglish::Default));\n\n// Outputs \"en-gb\".\nprintln!(\"{}\", Language::English(LanguageEnglish::UnitedKingdom));\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frssblue%2Fbadpod","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frssblue%2Fbadpod","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frssblue%2Fbadpod/lists"}