diff --git a/lib/oxrdf/src/blank_node.rs b/lib/oxrdf/src/blank_node.rs index f39574a2..85123a1c 100644 --- a/lib/oxrdf/src/blank_node.rs +++ b/lib/oxrdf/src/blank_node.rs @@ -2,6 +2,7 @@ use rand::random; use std::io::Write; use std::{fmt, str}; +use crate::cast_error::{TermCastError, TermCastErrorKind}; use crate::Term; /// An owned RDF [blank node](https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node). @@ -104,12 +105,19 @@ impl fmt::Display for BlankNode { } } -impl From for Option { +impl TryFrom for BlankNode { + type Error = TermCastError; + #[inline] - fn from(term: Term) -> Self { - match term { - Term::BlankNode(node) => Some(node), - _ => None, + fn try_from(term: Term) -> Result { + if let Term::BlankNode(node) = term { + Ok(node) + } else { + Err(TermCastErrorKind::Msg(format!( + "Cannot convert a term to a blank node: {}", + term + )) + .into()) } } } @@ -376,14 +384,14 @@ mod tests { #[test] fn casting() { - let bnode: Option = Term::BlankNode(BlankNode::new_from_unique_id(0x42)).into(); - assert_eq!(bnode, Some(BlankNode::new_from_unique_id(0x42))); + 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: Option = Term::Literal(Literal::new_simple_literal("Hello World!")).into(); - assert_eq!(literal, None); + let literal: Result = Term::Literal(Literal::new_simple_literal("Hello World!")).try_into(); + assert_eq!(literal.is_err(), true); - let named_node: Option = Term::NamedNode(NamedNode::new("http://example.org/test").unwrap()).into(); - assert_eq!(named_node, None); + let named_node: Result = Term::NamedNode(NamedNode::new("http://example.org/test").unwrap()).try_into(); + assert_eq!(named_node.is_err(), true); } #[test] diff --git a/lib/oxrdf/src/cast_error.rs b/lib/oxrdf/src/cast_error.rs new file mode 100644 index 00000000..c976762c --- /dev/null +++ b/lib/oxrdf/src/cast_error.rs @@ -0,0 +1,15 @@ +use std::error::Error; + +/// An error return if some content in the database is corrupted. +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct TermCastError(#[from] pub TermCastErrorKind); + +/// An error return if some content in the database is corrupted. +#[derive(Debug, thiserror::Error)] +pub enum TermCastErrorKind { + #[error("{0}")] + Msg(String), + #[error("{0}")] + Other(#[source] Box), +} diff --git a/lib/oxrdf/src/lib.rs b/lib/oxrdf/src/lib.rs index aa6f712b..7c0b02c2 100644 --- a/lib/oxrdf/src/lib.rs +++ b/lib/oxrdf/src/lib.rs @@ -13,6 +13,7 @@ mod named_node; mod parser; mod triple; mod variable; +mod cast_error; pub mod vocab; pub use crate::blank_node::{BlankNode, BlankNodeIdParseError, BlankNodeRef}; @@ -26,5 +27,6 @@ pub use crate::triple::{ SubjectRef, Term, TermRef, Triple, TripleRef, }; pub use crate::variable::{Variable, VariableNameParseError, VariableRef}; +pub use crate::cast_error::{TermCastError, TermCastErrorKind}; pub use oxilangtag::LanguageTagParseError; pub use oxiri::IriParseError; diff --git a/lib/oxrdf/src/literal.rs b/lib/oxrdf/src/literal.rs index 78d4e3e1..ee4f4fa9 100644 --- a/lib/oxrdf/src/literal.rs +++ b/lib/oxrdf/src/literal.rs @@ -1,4 +1,5 @@ use crate::named_node::NamedNode; +use crate::cast_error::{TermCastError, TermCastErrorKind}; use crate::vocab::{rdf, xsd}; use crate::{NamedNodeRef, Term}; use oxilangtag::{LanguageTag, LanguageTagParseError}; @@ -161,12 +162,19 @@ impl fmt::Display for Literal { } } -impl From for Option { +impl TryFrom for Literal { + type Error = TermCastError; + #[inline] - fn from(term: Term) -> Self { - match term { - Term::Literal(literal) => Some(literal), - _ => None, + 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()) } } } @@ -674,14 +682,14 @@ mod tests { #[test] fn casting() { - let literal: Option = Term::Literal(Literal::new_simple_literal("Hello World!")).into(); - assert_eq!(literal, Some(Literal::new_simple_literal("Hello World!"))); + 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: Option = Term::BlankNode(BlankNode::new_from_unique_id(0x42)).into(); - assert_eq!(bnode, None); + let bnode: Result = Term::BlankNode(BlankNode::new_from_unique_id(0x42)).try_into(); + assert_eq!(bnode.is_err(), true); - let named_node: Option = Term::NamedNode(NamedNode::new("http://example.org/test").unwrap()).into(); - assert_eq!(named_node, None); + let named_node: Result = Term::NamedNode(NamedNode::new("http://example.org/test").unwrap()).try_into(); + assert_eq!(named_node.is_err(), true); } #[test] diff --git a/lib/oxrdf/src/named_node.rs b/lib/oxrdf/src/named_node.rs index 95c299bf..ff8bc566 100644 --- a/lib/oxrdf/src/named_node.rs +++ b/lib/oxrdf/src/named_node.rs @@ -2,7 +2,7 @@ use oxiri::{Iri, IriParseError}; use std::cmp::Ordering; use std::fmt; -use crate::Term; +use crate::{cast_error::TermCastErrorKind, Term, TermCastError}; /// An owned RDF [IRI](https://www.w3.org/TR/rdf11-concepts/#dfn-iri). /// @@ -219,12 +219,19 @@ impl PartialOrd> for NamedNode { } } -impl From for Option { +impl TryFrom for NamedNode { + type Error = TermCastError; + #[inline] - fn from(term: Term) -> Self { - match term { - Term::NamedNode(node) => Some(node), - _ => None, + 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()) } } } @@ -257,13 +264,13 @@ mod tests { #[test] fn casting() { - let named_node: Option = Term::NamedNode(NamedNode::new("http://example.org/test").unwrap()).into(); - assert_eq!(named_node, Some(NamedNode::new("http://example.org/test").unwrap())); + 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: Option = Term::Literal(Literal::new_simple_literal("Hello World!")).into(); - assert_eq!(literal, None); + let literal: Result = Term::Literal(Literal::new_simple_literal("Hello World!")).try_into(); + assert_eq!(literal.is_err(), true); - let bnode: Option = Term::BlankNode(BlankNode::new_from_unique_id(0x42)).into(); - assert_eq!(bnode, None); + let bnode: Result = Term::BlankNode(BlankNode::new_from_unique_id(0x42)).try_into(); + assert_eq!(bnode.is_err(), true); } } diff --git a/lib/oxrdf/src/triple.rs b/lib/oxrdf/src/triple.rs index 32c22f28..50b6fe9e 100644 --- a/lib/oxrdf/src/triple.rs +++ b/lib/oxrdf/src/triple.rs @@ -1,7 +1,8 @@ use crate::blank_node::BlankNode; +use crate::cast_error::TermCastErrorKind; use crate::literal::Literal; use crate::named_node::NamedNode; -use crate::{BlankNodeRef, LiteralRef, NamedNodeRef}; +use crate::{BlankNodeRef, LiteralRef, NamedNodeRef, TermCastError}; 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). @@ -195,15 +196,21 @@ impl fmt::Display for Subject { } } -impl From for Option { +impl TryFrom for Subject { + type Error = TermCastError; + #[inline] - fn from(term: Term) -> Self { + fn try_from(term: Term) -> Result { match term { - Term::NamedNode(node) => Some(Subject::NamedNode(node)), - Term::BlankNode(node) => Some(Subject::BlankNode(node)), + Term::NamedNode(node) => Ok(Subject::NamedNode(node)), + Term::BlankNode(node) => Ok(Subject::BlankNode(node)), #[cfg(feature = "rdf-star")] - Term::Triple(triple) => Some(Subject::Triple(triple)), - Term::Literal(_) => None + Term::Triple(triple) => Ok(Subject::Triple(triple)), + Term::Literal(literal) => Err(TermCastErrorKind::Msg(format!( + "Cannot convert a literal to a subject: {}", + literal + )) + .into()), } } } @@ -753,22 +760,16 @@ impl Triple { /// Builds an RDF [triple](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-triple). #[inline] - pub fn new_maybe( + pub fn new_from_terms( subject: impl Into, predicate: impl Into, object: impl Into, - ) -> Option { - match ( - Into::>::into(Into::::into(subject)), - Into::>::into(Into::::into(predicate)), - Into::>::into(Into::::into(object))) { - (Some(subject), Some(predicate), Some(object)) => Some(Self { - subject, - predicate, - object, - }), - _ => None, - } + ) -> Result { + Ok(Self { + subject: TryInto::::try_into(subject.into())?, + predicate: TryInto::::try_into(predicate.into())?, + object: object.into(), + }) } /// Encodes that this triple is in an [RDF dataset](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-dataset). @@ -800,13 +801,19 @@ impl fmt::Display for Triple { } #[cfg(feature = "rdf-star")] -impl From for Option> { +impl TryFrom for Box { + type Error = TermCastError; + #[inline] - fn from(term: Term) -> Self { - match term { - #[cfg(feature = "rdf-star")] - Term::Triple(triple) => Some(triple), - _ => None, + 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()) } } } @@ -1286,42 +1293,42 @@ mod tests { }; let triple_box = Box::new(triple); - let t: Option> = Term::Triple(triple_box.clone()).into(); - assert_eq!(t, Some(triple_box.clone())); + let t: Result, TermCastError> = Term::Triple(triple_box.clone()).try_into(); + assert_eq!(t.unwrap(), triple_box.clone()); - let literal: Option> = Term::Literal(Literal::new_simple_literal("Hello World!")).into(); - assert_eq!(literal, None); + let literal: Result, TermCastError> = Term::Literal(Literal::new_simple_literal("Hello World!")).try_into(); + assert_eq!(literal.is_err(), true); - let bnode: Option> = Term::BlankNode(BlankNode::new_from_unique_id(0x42)).into(); - assert_eq!(bnode, None); + let bnode: Result, TermCastError> = Term::BlankNode(BlankNode::new_from_unique_id(0x42)).try_into(); + assert_eq!(bnode.is_err(), true); - let named_node: Option> = Term::NamedNode(NamedNode::new("http://example.org/test").unwrap()).into(); - assert_eq!(named_node, None); + let named_node: Result, TermCastError> = Term::NamedNode(NamedNode::new("http://example.org/test").unwrap()).try_into(); + assert_eq!(named_node.is_err(), true); } #[test] fn constructing_triple() { use super::*; - let optional_triple = Triple::new_maybe( + let optional_triple = Triple::new_from_terms( Term::NamedNode(NamedNode::new("http://example.org/test").unwrap()), Term::NamedNode(NamedNode::new("http://example.org/test").unwrap()), Term::NamedNode(NamedNode::new("http://example.org/test").unwrap()) ); - let optional_triple_2 = Triple::new_maybe( + let optional_triple_2 = Triple::new_from_terms( NamedNode::new("http://example.org/test").unwrap(), NamedNode::new("http://example.org/test").unwrap(), NamedNode::new("http://example.org/test").unwrap() ); - let bad_triple = Triple::new_maybe( + let bad_triple = Triple::new_from_terms( Term::BlankNode(BlankNode::new("abc123").unwrap()), Term::NamedNode(NamedNode::new("http://example.org/test").unwrap()), Term::NamedNode(NamedNode::new("http://example.org/test").unwrap()) ); - let bad_triple_2 = Triple::new_maybe( + let bad_triple_2 = Triple::new_from_terms( BlankNode::new("abc123").unwrap(), NamedNode::new("http://example.org/test").unwrap(), NamedNode::new("http://example.org/test").unwrap() @@ -1339,11 +1346,13 @@ mod tests { NamedNode::new("http://example.org/test").unwrap() ); - assert_eq!(optional_triple, Some(triple)); - assert_eq!(optional_triple, Some(triple_2.clone())); - assert_eq!(optional_triple_2, Some(triple_2)); - assert_eq!(bad_triple, None); - assert_eq!(bad_triple_2, None); + let unwrapped = optional_triple.unwrap(); + + 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); + assert_eq!(bad_triple_2.is_err(), true); } #[test] @@ -1355,16 +1364,16 @@ mod tests { }; let triple_box = Box::new(triple); - let t: Option = Term::Triple(triple_box.clone()).into(); - assert_eq!(t, Some(Subject::Triple(triple_box.clone()))); + let t: Result = Term::Triple(triple_box.clone()).try_into(); + assert_eq!(t.unwrap(), Subject::Triple(triple_box.clone())); - let literal: Option = Term::Literal(Literal::new_simple_literal("Hello World!")).into(); - assert_eq!(literal, None); + let literal: Result = Term::Literal(Literal::new_simple_literal("Hello World!")).try_into(); + assert_eq!(literal.is_err(), true); - let bnode: Option = Term::BlankNode(BlankNode::new_from_unique_id(0x42)).into(); - assert_eq!(bnode, Some(Subject::BlankNode(BlankNode::new_from_unique_id(0x42)))); + 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: Option = Term::NamedNode(NamedNode::new("http://example.org/test").unwrap()).into(); - assert_eq!(named_node, Some(Subject::NamedNode(NamedNode::new("http://example.org/test").unwrap()))); + let named_node: Result = Term::NamedNode(NamedNode::new("http://example.org/test").unwrap()).try_into(); + assert_eq!(named_node.unwrap(), Subject::NamedNode(NamedNode::new("http://example.org/test").unwrap())); } }