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:
parent
f1a4f57ec2
commit
312627e298
|
@ -121,6 +121,7 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"strong-xml",
|
"strong-xml",
|
||||||
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -162,9 +163,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.19"
|
version = "1.0.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12"
|
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
@ -235,9 +236,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strong-xml"
|
name = "strong-xml"
|
||||||
version = "0.6.0"
|
version = "0.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ee06e7e5baf4508dea83506a83fcc5b80a404d4c0e9c473c9a4b38b802af3a07"
|
checksum = "da0e355b33893aa1b6d2f29110e729609c87a4241817a3e53ffba4c31421b1b1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jetscii",
|
"jetscii",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
@ -248,9 +249,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strong-xml-derive"
|
name = "strong-xml-derive"
|
||||||
version = "0.6.0"
|
version = "0.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d2e4e25fb64e61f55d495134d9e5ac68b1fa4bb2855b5a5b53857b9460e2bfde"
|
checksum = "e9053670189294a3fa7d7379a58b07ab0d0608debe80f5a16d31e2faa6e13b69"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -265,9 +266,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.39"
|
version = "1.0.64"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "891d8d6567fe7c7f8835a3a98af4208f3846fba258c1bc3c31d6e506239f11f9"
|
checksum = "3fd9d1e9976102a03c542daa2eff1b43f9d72306342f3f8b3ed5fb8908195d6f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -283,6 +284,26 @@ dependencies = [
|
||||||
"unicode-width",
|
"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]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
|
|
@ -17,4 +17,5 @@ path = "source/lib.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
regex = "1.3"
|
regex = "1.3"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
strong-xml = "0.6"
|
strong-xml = "0.6.1"
|
||||||
|
thiserror = "1.0.24"
|
||||||
|
|
|
@ -61,10 +61,23 @@
|
||||||
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
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.
|
/// 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")]
|
#[xml(tag = "opml")]
|
||||||
pub struct OPML {
|
pub struct OPML {
|
||||||
/// The version attribute from the element, valid values are `1.0`, `1.1` and `2.0`.
|
/// 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);
|
/// assert_eq!(parsed, expected);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn new(xml: &str) -> Result<Self, String> {
|
pub fn new(xml: &str) -> Result<Self, Error> {
|
||||||
let opml: Result<OPML, XmlError> = OPML::from_str(xml);
|
let opml = OPML::from_str(xml)?;
|
||||||
|
|
||||||
let opml = match opml {
|
|
||||||
Ok(value) => value,
|
|
||||||
Err(err) => return Err(format!("XML parsing error: {:#?}", err)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let version = &opml.version;
|
let version = &opml.version;
|
||||||
|
|
||||||
|
@ -116,15 +124,12 @@ impl OPML {
|
||||||
if !valid_version_regex.is_match(version)
|
if !valid_version_regex.is_match(version)
|
||||||
|| !valid_versions.contains(&version.as_str())
|
|| !valid_versions.contains(&version.as_str())
|
||||||
{
|
{
|
||||||
return Err(format!(
|
return Err(Error::UnsupportedVersion(opml.version));
|
||||||
"Unsupported OPML version detected: {}",
|
|
||||||
opml.version
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SPEC: A `<body>` contains one or more `<outline>` elements.
|
// SPEC: A `<body>` contains one or more `<outline>` elements.
|
||||||
if opml.body.outlines.is_empty() {
|
if opml.body.outlines.is_empty() {
|
||||||
return Err("OPML body has no outlines.".to_string());
|
return Err(Error::BodyHasNoOutlines);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(opml)
|
Ok(opml)
|
||||||
|
@ -172,13 +177,8 @@ impl OPML {
|
||||||
/// let expected = r#"<opml version="2.0"><head/><body/></opml>"#;
|
/// let expected = r#"<opml version="2.0"><head/><body/></opml>"#;
|
||||||
/// assert_eq!(xml, expected);
|
/// assert_eq!(xml, expected);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn to_xml(&self) -> Result<String, String> {
|
pub fn to_xml(&self) -> Result<String, Error> {
|
||||||
let result: Result<String, XmlError> = self.to_string();
|
Ok(self.to_string()?)
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(value) => Ok(value),
|
|
||||||
Err(err) => Err(format!("XML writing error: {:#?}", err)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use std::fs::read_to_string as read;
|
|
||||||
|
|
||||||
use opml::*;
|
use opml::*;
|
||||||
|
use std::fs::read_to_string as read;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
|
@ -10,15 +9,15 @@ fn test_invalid_xml() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "Unsupported OPML version detected: invalid")]
|
|
||||||
fn test_invalid_opml_version() {
|
fn test_invalid_opml_version() {
|
||||||
let sample = read("tests/samples/invalid_opml_version.opml").unwrap();
|
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]
|
#[test]
|
||||||
#[should_panic(expected = "OPML body has no outlines.")]
|
|
||||||
fn test_invalid_opml_no_outlines() {
|
fn test_invalid_opml_no_outlines() {
|
||||||
let sample = read("tests/samples/invalid_opml_no_outlines.opml").unwrap();
|
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)));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue