feat(api)!: add distinct error type (#1)

* feat(api)!: add distinct error type

The commit adds an `Error` enum to the public API of the opml crate to
make it easier to reason about what went wrong during OPML
parsing/writing.
This commit is contained in:
SirWindfield 2021-03-22 12:41:11 +01:00 committed by GitHub
parent f1a4f57ec2
commit 312627e298
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 57 additions and 36 deletions

37
Cargo.lock generated
View File

@ -121,6 +121,7 @@ dependencies = [
"regex",
"serde",
"strong-xml",
"thiserror",
]
[[package]]
@ -162,9 +163,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.19"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12"
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
dependencies = [
"unicode-xid",
]
@ -235,9 +236,9 @@ dependencies = [
[[package]]
name = "strong-xml"
version = "0.6.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee06e7e5baf4508dea83506a83fcc5b80a404d4c0e9c473c9a4b38b802af3a07"
checksum = "da0e355b33893aa1b6d2f29110e729609c87a4241817a3e53ffba4c31421b1b1"
dependencies = [
"jetscii",
"lazy_static",
@ -248,9 +249,9 @@ dependencies = [
[[package]]
name = "strong-xml-derive"
version = "0.6.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2e4e25fb64e61f55d495134d9e5ac68b1fa4bb2855b5a5b53857b9460e2bfde"
checksum = "e9053670189294a3fa7d7379a58b07ab0d0608debe80f5a16d31e2faa6e13b69"
dependencies = [
"proc-macro2",
"quote",
@ -265,9 +266,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "syn"
version = "1.0.39"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891d8d6567fe7c7f8835a3a98af4208f3846fba258c1bc3c31d6e506239f11f9"
checksum = "3fd9d1e9976102a03c542daa2eff1b43f9d72306342f3f8b3ed5fb8908195d6f"
dependencies = [
"proc-macro2",
"quote",
@ -283,6 +284,26 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thread_local"
version = "1.0.1"

View File

@ -17,4 +17,5 @@ path = "source/lib.rs"
[dependencies]
regex = "1.3"
serde = { version = "1.0", features = ["derive"] }
strong-xml = "0.6"
strong-xml = "0.6.1"
thiserror = "1.0.24"

View File

@ -61,10 +61,23 @@
use regex::Regex;
use serde::{Deserialize, Serialize};
use strong_xml::{XmlError, XmlRead, XmlWrite};
use strong_xml::{XmlRead, XmlWrite};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("OPML body has no <outlines> elements")]
BodyHasNoOutlines,
#[error("Unsupported OPML version: {0:?}")]
UnsupportedVersion(String),
#[error("Failed to process XML file")]
XmlError(#[from] strong_xml::XmlError),
}
/// The top-level [OPML](struct.OPML.html) element.
#[derive(XmlWrite, XmlRead, PartialEq, Debug, Clone, Serialize, Deserialize)]
#[derive(
XmlWrite, XmlRead, PartialEq, Debug, Clone, Serialize, Deserialize,
)]
#[xml(tag = "opml")]
pub struct OPML {
/// The version attribute from the element, valid values are `1.0`, `1.1` and `2.0`.
@ -99,13 +112,8 @@ impl OPML {
///
/// assert_eq!(parsed, expected);
/// ```
pub fn new(xml: &str) -> Result<Self, String> {
let opml: Result<OPML, XmlError> = OPML::from_str(xml);
let opml = match opml {
Ok(value) => value,
Err(err) => return Err(format!("XML parsing error: {:#?}", err)),
};
pub fn new(xml: &str) -> Result<Self, Error> {
let opml = OPML::from_str(xml)?;
let version = &opml.version;
@ -116,15 +124,12 @@ impl OPML {
if !valid_version_regex.is_match(version)
|| !valid_versions.contains(&version.as_str())
{
return Err(format!(
"Unsupported OPML version detected: {}",
opml.version
));
return Err(Error::UnsupportedVersion(opml.version));
}
// SPEC: A `<body>` contains one or more `<outline>` elements.
if opml.body.outlines.is_empty() {
return Err("OPML body has no outlines.".to_string());
return Err(Error::BodyHasNoOutlines);
}
Ok(opml)
@ -172,13 +177,8 @@ impl OPML {
/// let expected = r#"<opml version="2.0"><head/><body/></opml>"#;
/// assert_eq!(xml, expected);
/// ```
pub fn to_xml(&self) -> Result<String, String> {
let result: Result<String, XmlError> = self.to_string();
match result {
Ok(value) => Ok(value),
Err(err) => Err(format!("XML writing error: {:#?}", err)),
}
pub fn to_xml(&self) -> Result<String, Error> {
Ok(self.to_string()?)
}
}

View File

@ -1,6 +1,5 @@
use std::fs::read_to_string as read;
use opml::*;
use std::fs::read_to_string as read;
#[test]
#[should_panic]
@ -10,15 +9,15 @@ fn test_invalid_xml() {
}
#[test]
#[should_panic(expected = "Unsupported OPML version detected: invalid")]
fn test_invalid_opml_version() {
let sample = read("tests/samples/invalid_opml_version.opml").unwrap();
OPML::new(sample.as_str()).unwrap();
let res = OPML::new(sample.as_str());
assert!(matches!(res, Err(Error::UnsupportedVersion(e)) if e == "invalid"));
}
#[test]
#[should_panic(expected = "OPML body has no outlines.")]
fn test_invalid_opml_no_outlines() {
let sample = read("tests/samples/invalid_opml_no_outlines.opml").unwrap();
OPML::new(sample.as_str()).unwrap();
let res = OPML::new(sample.as_str());
assert!(matches!(res, Err(Error::BodyHasNoOutlines)));
}