From c9a3820244df11bb4d3bb3bffb1735d560d6f6e2 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sat, 24 Feb 2024 12:37:47 +0000 Subject: [PATCH] feat: improve erroring --- lib/oxrdf/src/blank_node.rs | 21 ++++---- lib/oxrdf/src/cast_error.rs | 45 ++++++++++++----- lib/oxrdf/src/lib.rs | 2 +- lib/oxrdf/src/literal.rs | 27 +++++----- lib/oxrdf/src/named_node.rs | 27 +++++----- lib/oxrdf/src/triple.rs | 99 ++++++++++++++++++++++++++----------- 6 files changed, 148 insertions(+), 73 deletions(-) diff --git a/lib/oxrdf/src/blank_node.rs b/lib/oxrdf/src/blank_node.rs index eb94e6e1..aeff8c7a 100644 --- a/lib/oxrdf/src/blank_node.rs +++ b/lib/oxrdf/src/blank_node.rs @@ -1,4 +1,4 @@ -use crate::{Term, TermCastError, TermCastErrorKind}; +use crate::{Term, TryFromTermError}; use rand::random; use std::io::Write; use std::{fmt, str}; @@ -234,17 +234,14 @@ impl<'a> From> for BlankNode { } impl TryFrom for BlankNode { - type Error = TermCastError; + type Error = TryFromTermError; #[inline] fn try_from(term: Term) -> Result { if let Term::BlankNode(node) = term { Ok(node) } else { - Err( - TermCastErrorKind::Msg(format!("Cannot convert term to a blank node: {}", term)) - .into(), - ) + Err(TryFromTermError { term, target: "BlankNode" }) } } } @@ -381,17 +378,23 @@ mod tests { #[test] fn casting() { - let bnode: Result = + let bnode: Result = Term::BlankNode(BlankNode::new_from_unique_id(0x42)).try_into(); assert_eq!(bnode.unwrap(), BlankNode::new_from_unique_id(0x42)); - let literal: Result = + let literal: Result = Term::Literal(Literal::new_simple_literal("Hello World!")).try_into(); assert_eq!(literal.is_err(), true); + let err = literal.unwrap_err(); + assert_eq!(err.to_string(), "\"Hello World!\" can not be converted to a BlankNode"); + assert_eq!(Term::from(err), Term::Literal(Literal::new_simple_literal("Hello World!"))); - let named_node: Result = + let named_node: Result = Term::NamedNode(NamedNode::new("http://example.org/test").unwrap()).try_into(); assert_eq!(named_node.is_err(), true); + let named_node_error = named_node.unwrap_err(); + assert_eq!(named_node_error.to_string(), " can not be converted to a BlankNode"); + assert_eq!(Term::from(named_node_error), Term::NamedNode(NamedNode::new("http://example.org/test").unwrap())); } #[test] diff --git a/lib/oxrdf/src/cast_error.rs b/lib/oxrdf/src/cast_error.rs index 944cadbc..007cc22e 100644 --- a/lib/oxrdf/src/cast_error.rs +++ b/lib/oxrdf/src/cast_error.rs @@ -1,15 +1,38 @@ -use std::error::Error; +use std::{fmt, str}; -/// An error return if trying to cast a term as something it cannot be converted to. -#[derive(Debug, thiserror::Error)] -#[error(transparent)] -pub struct TermCastError(#[from] pub TermCastErrorKind); -/// An error return if trying to cast a term as something it cannot be converted to. +use crate::{NamedNode, Subject, Term}; + +// An error return if trying to cast a term as something it cannot be converted to. +#[derive(Debug, Clone, thiserror::Error)] +#[error("{term} can not be converted to a {target}")] +pub struct TryFromTermError { + pub(crate) term: Term, + pub(crate) target: &'static str +} + +impl From for Term { + #[inline] + fn from(error: TryFromTermError) -> Self { + error.term + } +} + +// An error return if trying to construct an invalid triple. #[derive(Debug, thiserror::Error)] -pub enum TermCastErrorKind { - #[error("{0}")] - Msg(String), - #[error("{0}")] - Other(#[source] Box), +pub struct TripleConstructionError { + pub(crate) subject: Result, + pub(crate) predicate: Result, + pub(crate) object: Term, +} + +impl fmt::Display for TripleConstructionError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match (self.subject.clone().err(), self.predicate.clone().err()) { + (Some(e), Some(e2)) => write!(f, "subject: [{}], predicate: [{}]", e, e2), + (Some(e), _) => write!(f, "subject: [{}]", e), + (_, Some(e)) => write!(f, "predicate: [{}]", e), + _ => write!(f, "object: {}", self.object) + } + } } diff --git a/lib/oxrdf/src/lib.rs b/lib/oxrdf/src/lib.rs index b1dc6cef..b038cd5b 100644 --- a/lib/oxrdf/src/lib.rs +++ b/lib/oxrdf/src/lib.rs @@ -17,7 +17,7 @@ mod variable; pub mod vocab; pub use crate::blank_node::{BlankNode, BlankNodeIdParseError, BlankNodeRef}; -pub use crate::cast_error::{TermCastError, TermCastErrorKind}; +pub use crate::cast_error::TryFromTermError; pub use crate::dataset::Dataset; pub use crate::graph::Graph; pub use crate::literal::{Literal, LiteralRef}; diff --git a/lib/oxrdf/src/literal.rs b/lib/oxrdf/src/literal.rs index dec025ba..b12d87fa 100644 --- a/lib/oxrdf/src/literal.rs +++ b/lib/oxrdf/src/literal.rs @@ -1,6 +1,6 @@ use crate::named_node::NamedNode; use crate::vocab::{rdf, xsd}; -use crate::{NamedNodeRef, Term, TermCastError, TermCastErrorKind}; +use crate::{NamedNodeRef, Term, TryFromTermError}; use oxilangtag::{LanguageTag, LanguageTagParseError}; #[cfg(feature = "oxsdatatypes")] use oxsdatatypes::*; @@ -423,17 +423,14 @@ impl From for Literal { } impl TryFrom for Literal { - type Error = TermCastError; + type Error = TryFromTermError; #[inline] fn try_from(term: Term) -> Result { if let Term::Literal(node) = term { Ok(node) } else { - Err( - TermCastErrorKind::Msg(format!("Cannot convert term to a literal: {}", term)) - .into(), - ) + Err(TryFromTermError { term, target: "Literal" }) } } } @@ -678,20 +675,28 @@ mod tests { #[test] fn casting() { - let literal: Result = + let literal: Result = Term::Literal(Literal::new_simple_literal("Hello World!")).try_into(); assert_eq!( literal.unwrap(), Literal::new_simple_literal("Hello World!") ); - let bnode: Result = + let bnode: Result = Term::BlankNode(BlankNode::new_from_unique_id(0x42)).try_into(); - assert_eq!(bnode.is_err(), true); + let bnode_err = bnode.unwrap_err(); + assert_eq!(bnode_err.term, Term::BlankNode(BlankNode::new_from_unique_id(0x42))); + assert_eq!(bnode_err.target, "Literal"); + assert_eq!(bnode_err.to_string(), "_:42 can not be converted to a Literal"); + assert_eq!(Term::from(bnode_err), Term::BlankNode(BlankNode::new_from_unique_id(0x42))); - let named_node: Result = + let named_node: Result = Term::NamedNode(NamedNode::new("http://example.org/test").unwrap()).try_into(); - assert_eq!(named_node.is_err(), true); + let named_node_err = named_node.unwrap_err(); + assert_eq!(named_node_err.term, Term::NamedNode(NamedNode::new("http://example.org/test").unwrap())); + assert_eq!(named_node_err.target, "Literal"); + assert_eq!(named_node_err.to_string(), " can not be converted to a Literal"); + assert_eq!(Term::from(named_node_err), Term::NamedNode(NamedNode::new("http://example.org/test").unwrap())); } #[test] diff --git a/lib/oxrdf/src/named_node.rs b/lib/oxrdf/src/named_node.rs index 8d692f8c..97a7e913 100644 --- a/lib/oxrdf/src/named_node.rs +++ b/lib/oxrdf/src/named_node.rs @@ -1,4 +1,4 @@ -use crate::{Term, TermCastError, TermCastErrorKind}; +use crate::{Term, TryFromTermError}; use oxiri::{Iri, IriParseError}; use std::cmp::Ordering; use std::fmt; @@ -237,17 +237,14 @@ impl<'a> From> for NamedNodeRef<'a> { } impl TryFrom for NamedNode { - type Error = TermCastError; + type Error = TryFromTermError; #[inline] fn try_from(term: Term) -> Result { if let Term::NamedNode(node) = term { Ok(node) } else { - Err( - TermCastErrorKind::Msg(format!("Cannot convert term to a named node: {}", term)) - .into(), - ) + Err(TryFromTermError { term, target: "NamedNode" }) } } } @@ -262,19 +259,27 @@ mod tests { #[test] fn casting() { - let named_node: Result = + let named_node: Result = Term::NamedNode(NamedNode::new("http://example.org/test").unwrap()).try_into(); assert_eq!( named_node.unwrap(), NamedNode::new("http://example.org/test").unwrap() ); - let literal: Result = + let literal: Result = Term::Literal(Literal::new_simple_literal("Hello World!")).try_into(); - assert_eq!(literal.is_err(), true); + let literal_err = literal.unwrap_err(); + assert_eq!(literal_err.term, Term::Literal(Literal::new_simple_literal("Hello World!"))); + assert_eq!(literal_err.target, "NamedNode"); + assert_eq!(literal_err.to_string(), "\"Hello World!\" can not be converted to a NamedNode"); + assert_eq!(Term::from(literal_err), Term::Literal(Literal::new_simple_literal("Hello World!"))); - let bnode: Result = + let bnode: Result = Term::BlankNode(BlankNode::new_from_unique_id(0x42)).try_into(); - assert_eq!(bnode.is_err(), true); + let bnode_err = bnode.unwrap_err(); + assert_eq!(bnode_err.term, Term::BlankNode(BlankNode::new_from_unique_id(0x42))); + assert_eq!(bnode_err.target, "NamedNode"); + assert_eq!(bnode_err.to_string(), "_:42 can not be converted to a NamedNode"); + assert_eq!(Term::from(bnode_err), Term::BlankNode(BlankNode::new_from_unique_id(0x42))); } } diff --git a/lib/oxrdf/src/triple.rs b/lib/oxrdf/src/triple.rs index 524e1755..f8ee18e4 100644 --- a/lib/oxrdf/src/triple.rs +++ b/lib/oxrdf/src/triple.rs @@ -1,8 +1,8 @@ use crate::blank_node::BlankNode; -use crate::cast_error::TermCastErrorKind; +use crate::cast_error::TripleConstructionError; use crate::literal::Literal; use crate::named_node::NamedNode; -use crate::{BlankNodeRef, LiteralRef, NamedNodeRef, TermCastError}; +use crate::{BlankNodeRef, LiteralRef, NamedNodeRef, TryFromTermError}; use std::fmt; /// The owned union of [IRIs](https://www.w3.org/TR/rdf11-concepts/#dfn-iri) and [blank nodes](https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node). @@ -225,7 +225,7 @@ impl From> for Subject { } impl TryFrom for Subject { - type Error = TermCastError; + type Error = TryFromTermError; #[inline] fn try_from(term: Term) -> Result { @@ -234,11 +234,7 @@ impl TryFrom for Subject { Term::BlankNode(node) => Ok(Subject::BlankNode(node)), #[cfg(feature = "rdf-star")] Term::Triple(triple) => Ok(Subject::Triple(triple)), - Term::Literal(literal) => Err(TermCastErrorKind::Msg(format!( - "Cannot convert a literal to a subject: {}", - literal - )) - .into()), + Term::Literal(_) => Err(TryFromTermError { term, target: "Subject" }), } } } @@ -764,12 +760,14 @@ impl Triple { subject: impl Into, predicate: impl Into, object: impl Into, - ) -> Result { - Ok(Self { - subject: TryInto::::try_into(subject.into())?, - predicate: TryInto::::try_into(predicate.into())?, - object: object.into(), - }) + ) -> Result { + let subject: Result = TryInto::::try_into(subject.into()); + let predicate: Result = TryInto::::try_into(predicate.into()); + if let (Ok (subject), Ok (predicate)) = (subject.clone(), predicate.clone()) { + Ok(Self { subject, predicate, object: object.into() }) + } else { + Err(TripleConstructionError { subject, predicate, object: object.into() }) + } } /// Encodes that this triple is in an [RDF dataset](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-dataset). @@ -802,14 +800,14 @@ impl fmt::Display for Triple { #[cfg(feature = "rdf-star")] impl TryFrom for Box { - type Error = TermCastError; + type Error = TryFromTermError; #[inline] fn try_from(term: Term) -> Result { if let Term::Triple(node) = term { Ok(node) } else { - Err(TermCastErrorKind::Msg(format!("Cannot convert term to a triple: {}", term)).into()) + Err(TryFromTermError { term, target: "Box" }) } } } @@ -1289,20 +1287,32 @@ mod tests { }; let triple_box = Box::new(triple); - let t: Result, TermCastError> = Term::Triple(triple_box.clone()).try_into(); + let t: Result, TryFromTermError> = Term::Triple(triple_box.clone()).try_into(); assert_eq!(t.unwrap(), triple_box.clone()); - let literal: Result, TermCastError> = + let literal: Result, TryFromTermError> = Term::Literal(Literal::new_simple_literal("Hello World!")).try_into(); - assert_eq!(literal.is_err(), true); - - let bnode: Result, TermCastError> = + let literal_err = literal.unwrap_err(); + assert_eq!(literal_err.term, Term::Literal(Literal::new_simple_literal("Hello World!"))); + assert_eq!(literal_err.target, "Box"); + assert_eq!(literal_err.to_string(), "\"Hello World!\" can not be converted to a Box"); + assert_eq!(Term::from(literal_err), Term::Literal(Literal::new_simple_literal("Hello World!"))); + + let bnode: Result, TryFromTermError> = Term::BlankNode(BlankNode::new_from_unique_id(0x42)).try_into(); - assert_eq!(bnode.is_err(), true); + let bnode_err = bnode.unwrap_err(); + assert_eq!(bnode_err.term, Term::BlankNode(BlankNode::new_from_unique_id(0x42))); + assert_eq!(bnode_err.target, "Box"); + assert_eq!(bnode_err.to_string(), "_:42 can not be converted to a Box"); + assert_eq!(Term::from(bnode_err), Term::BlankNode(BlankNode::new_from_unique_id(0x42))); - let named_node: Result, TermCastError> = + let named_node: Result, TryFromTermError> = Term::NamedNode(NamedNode::new("http://example.org/test").unwrap()).try_into(); - assert_eq!(named_node.is_err(), true); + let named_node_err = named_node.unwrap_err(); + assert_eq!(named_node_err.term, Term::NamedNode(NamedNode::new("http://example.org/test").unwrap())); + assert_eq!(named_node_err.target, "Box"); + assert_eq!(named_node_err.to_string(), " can not be converted to a Box"); + assert_eq!(Term::from(named_node_err), Term::NamedNode(NamedNode::new("http://example.org/test").unwrap())); } #[test] @@ -1321,7 +1331,7 @@ mod tests { NamedNode::new("http://example.org/test").unwrap(), ); - let bad_triple: Result = Triple::new_from_terms( + let bad_triple: Result = Triple::new_from_terms( Term::Literal(Literal::new_simple_literal("abc123")), Term::NamedNode(NamedNode::new("http://example.org/test").unwrap()), Term::NamedNode(NamedNode::new("http://example.org/test").unwrap()), @@ -1333,6 +1343,12 @@ mod tests { NamedNode::new("http://example.org/test").unwrap(), ); + let bad_triple_3 = Triple::new_from_terms( + Term::Literal(Literal::new_simple_literal("abc123")), + BlankNode::new_from_unique_id(0x42), + NamedNode::new("http://example.org/test").unwrap(), + ); + let triple: Triple = Triple::new( Subject::NamedNode(NamedNode::new("http://example.org/test").unwrap()), NamedNode::new("http://example.org/test").unwrap(), @@ -1350,8 +1366,30 @@ mod tests { assert_eq!(unwrapped, triple); assert_eq!(unwrapped, triple_2.clone()); assert_eq!(optional_triple_2.unwrap(), triple_2); - assert_eq!(bad_triple.is_err(), true); + let bad_triple_err = bad_triple.unwrap_err(); + assert_eq!(bad_triple_err.to_string(), "subject: [\"abc123\" can not be converted to a Subject]"); + assert_eq!(bad_triple_err.subject.clone().unwrap_err().clone().term, Term::Literal(Literal::new_simple_literal("abc123"))); + assert_eq!(bad_triple_err.subject.clone().unwrap_err().clone().target, "Subject"); + assert_eq!(bad_triple_err.subject.unwrap_err().clone().to_string(), "\"abc123\" can not be converted to a Subject"); + assert_eq!(bad_triple_2.is_err(), true); + let bad_triple_2_err = bad_triple_2.unwrap_err(); + assert_eq!(bad_triple_2_err.to_string(), "subject: [\"abc123\" can not be converted to a Subject]"); + assert_eq!(bad_triple_2_err.subject.clone().unwrap_err().clone().term, Term::Literal(Literal::new_simple_literal("abc123"))); + assert_eq!(bad_triple_2_err.subject.clone().unwrap_err().clone().target, "Subject"); + assert_eq!(bad_triple_2_err.subject.unwrap_err().clone().to_string(), "\"abc123\" can not be converted to a Subject"); + assert_eq!(bad_triple_2_err.predicate.unwrap(), NamedNode::new("http://example.org/test").unwrap()); + assert_eq!(bad_triple_2_err.object, Term::NamedNode(NamedNode::new("http://example.org/test").unwrap())); + + assert_eq!(bad_triple_3.is_err(), true); + let bad_triple_3_err = bad_triple_3.unwrap_err(); + assert_eq!(bad_triple_3_err.to_string(), "subject: [\"abc123\" can not be converted to a Subject], predicate: [_:42 can not be converted to a NamedNode]"); + assert_eq!(bad_triple_3_err.subject.clone().unwrap_err().clone().term, Term::Literal(Literal::new_simple_literal("abc123"))); + assert_eq!(bad_triple_3_err.subject.clone().unwrap_err().clone().target, "Subject"); + assert_eq!(bad_triple_3_err.subject.unwrap_err().clone().to_string(), "\"abc123\" can not be converted to a Subject"); + assert_eq!(bad_triple_3_err.predicate.clone().unwrap_err().clone().term, Term::BlankNode(BlankNode::new_from_unique_id(0x42))); + assert_eq!(bad_triple_3_err.predicate.clone().unwrap_err().clone().target, "NamedNode"); + assert_eq!(bad_triple_3_err.predicate.unwrap_err().clone().to_string(), "_:42 can not be converted to a NamedNode"); } #[test] @@ -1363,21 +1401,22 @@ mod tests { }; let triple_box = Box::new(triple); - let t: Result = Term::Triple(triple_box.clone()).try_into(); + let t: Result = Term::Triple(triple_box.clone()).try_into(); assert_eq!(t.unwrap(), Subject::Triple(triple_box.clone())); - let literal: Result = + let literal: Result = Term::Literal(Literal::new_simple_literal("Hello World!")).try_into(); assert_eq!(literal.is_err(), true); + assert_eq!(literal.is_err(), true); - let bnode: Result = + let bnode: Result = Term::BlankNode(BlankNode::new_from_unique_id(0x42)).try_into(); assert_eq!( bnode.unwrap(), Subject::BlankNode(BlankNode::new_from_unique_id(0x42)) ); - let named_node: Result = + let named_node: Result = Term::NamedNode(NamedNode::new("http://example.org/test").unwrap()).try_into(); assert_eq!( named_node.unwrap(),