From d31ec28aa8451431c66be96190039c10db806e4e Mon Sep 17 00:00:00 2001 From: Nick Alexander Date: Wed, 27 Jun 2018 13:19:40 -0700 Subject: [PATCH] Patch it all together: use MentatError at top-level. I elected to keep Tolstoy using `failure::Error`, because Tolstoy looks rather more like a high-level application (and will continue to do so for a while) than a production-ready mid- or low-level API. --- src/conn.rs | 30 +++++------ src/entity_builder.rs | 4 +- src/errors.rs | 88 +++++++++++++++++++++++++++++++- src/lib.rs | 4 ++ src/store.rs | 4 +- tests/query.rs | 22 ++++---- tests/vocabulary.rs | 4 +- tools/cli/src/mentat_cli/repl.rs | 4 +- 8 files changed, 124 insertions(+), 36 deletions(-) diff --git a/src/conn.rs b/src/conn.rs index a34ed30e..140c890f 100644 --- a/src/conn.rs +++ b/src/conn.rs @@ -185,7 +185,7 @@ pub trait Pullable { } pub trait Syncable { - fn sync(&mut self, server_uri: &String, user_uuid: &String) -> Result<()>; + fn sync(&mut self, server_uri: &String, user_uuid: &String) -> ::std::result::Result<(), ::failure::Error>; } /// Represents an in-progress, not yet committed, set of changes to the store. @@ -871,8 +871,8 @@ mod tests { .partition_map[":db.part/user"].index; let t = format!("[[:db/add {} :db.schema/attribute \"tempid\"]]", next + 1); - match conn.transact(&mut sqlite, t.as_str()).expect_err("expected transact error").downcast() { - Ok(e @ ::mentat_db::DbError { .. }) => { + match conn.transact(&mut sqlite, t.as_str()) { + Err(MentatError::DbError(e)) => { assert_eq!(e.kind(), ::mentat_db::DbErrorKind::UnrecognizedEntid(next + 1)); }, x => panic!("expected db error, got {:?}", x), @@ -898,8 +898,8 @@ mod tests { // we should reject this, because the first ID was provided by the user! let t = format!("[[:db/add {} :db.schema/attribute \"tempid\"]]", next); - match conn.transact(&mut sqlite, t.as_str()).expect_err("expected transact error").downcast() { - Ok(e @ ::mentat_db::DbError { .. }) => { + match conn.transact(&mut sqlite, t.as_str()) { + Err(MentatError::DbError(e)) => { // All this, despite this being the ID we were about to allocate! assert_eq!(e.kind(), ::mentat_db::DbErrorKind::UnrecognizedEntid(next)); }, @@ -1059,9 +1059,9 @@ mod tests { // Bad EDN: missing closing ']'. let report = conn.transact(&mut sqlite, "[[:db/add \"t\" :db/ident :a/keyword]"); - match report.expect_err("expected transact to fail for bad edn").downcast() { - Ok(edn::ParseError { .. }) => { }, - Err(x) => panic!("expected EDN parse error, got {:?}", x), + match report.expect_err("expected transact to fail for bad edn") { + MentatError::EdnParseError(_) => { }, + x => panic!("expected EDN parse error, got {:?}", x), } // Good EDN. @@ -1070,9 +1070,9 @@ mod tests { // Bad transaction data: missing leading :db/add. let report = conn.transact(&mut sqlite, "[[\"t\" :db/ident :b/keyword]]"); - match report.expect_err("expected transact error").downcast() { - Ok(edn::ParseError { .. }) => { }, - Err(x) => panic!("expected EDN parse error, got {:?}", x), + match report.expect_err("expected transact error") { + MentatError::EdnParseError(_) => { }, + x => panic!("expected EDN parse error, got {:?}", x), } // Good transaction data. @@ -1082,8 +1082,8 @@ mod tests { // Bad transaction based on state of store: conflicting upsert. let report = conn.transact(&mut sqlite, "[[:db/add \"u\" :db/ident :a/keyword] [:db/add \"u\" :db/ident :b/keyword]]"); - match report.expect_err("expected transact error").downcast() { - Ok(e @ ::mentat_db::DbError { .. }) => { + match report.expect_err("expected transact error") { + MentatError::DbError(e) => { match e.kind() { ::mentat_db::DbErrorKind::SchemaConstraintViolation(_) => {}, _ => panic!("expected SchemaConstraintViolation"), @@ -1106,8 +1106,8 @@ mod tests { let kw = kw!(:foo/bat); let schema = conn.current_schema(); let res = conn.cache(&mut sqlite, &schema, &kw, CacheDirection::Forward, CacheAction::Register); - match res.expect_err("expected cache to fail").downcast() { - Ok(MentatError::UnknownAttribute(msg)) => assert_eq!(msg, ":foo/bat"), + match res.expect_err("expected cache to fail") { + MentatError::UnknownAttribute(msg) => assert_eq!(msg, ":foo/bat"), x => panic!("expected UnknownAttribute error, got {:?}", x), } } diff --git a/src/entity_builder.rs b/src/entity_builder.rs index ec3e29cc..c273b8ca 100644 --- a/src/entity_builder.rs +++ b/src/entity_builder.rs @@ -419,8 +419,8 @@ mod testing { let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); // This should fail: unrecognized entid. - match in_progress.transact_terms(terms, tempids).expect_err("expected transact to fail").downcast() { - Ok(e @ mentat_db::DbError { .. }) => { + match in_progress.transact_terms(terms, tempids).expect_err("expected transact to fail") { + MentatError::DbError(e) => { assert_eq!(e.kind(), mentat_db::DbErrorKind::UnrecognizedEntid(999)); }, _ => panic!("Should have rejected the entid."), diff --git a/src/errors.rs b/src/errors.rs index 29622c5c..337aad52 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -14,16 +14,23 @@ use std; // To refer to std::result::Result. use std::collections::BTreeSet; -use failure::Error; +use rusqlite; + +use edn; use mentat_core::{ Attribute, ValueType, }; +use mentat_db; use mentat_query; +use mentat_query_algebrizer; +use mentat_query_projector; +use mentat_query_pull; +use mentat_sql; -pub type Result = std::result::Result; +pub type Result = std::result::Result; #[macro_export] macro_rules! bail { @@ -34,6 +41,9 @@ macro_rules! bail { #[derive(Debug, Fail)] pub enum MentatError { + #[fail(display = "bad uuid {}", _0)] + BadUuid(String), + #[fail(display = "path {} already exists", _0)] PathAlreadyExists(String), @@ -69,4 +79,78 @@ pub enum MentatError { #[fail(display = "provided value of type {} doesn't match attribute value type {}", _0, _1)] ValueTypeMismatch(ValueType, ValueType), + + #[fail(display = "{}", _0)] + IoError(#[cause] std::io::Error), + + // It would be better to capture the underlying `rusqlite::Error`, but that type doesn't + // implement many useful traits, including `Clone`, `Eq`, and `PartialEq`. + #[fail(display = "SQL error: _0")] + RusqliteError(String), + + #[fail(display = "{}", _0)] + EdnParseError(#[cause] edn::ParseError), + + #[fail(display = "{}", _0)] + DbError(#[cause] mentat_db::DbError), + + #[fail(display = "{}", _0)] + AlgebrizerError(#[cause] mentat_query_algebrizer::AlgebrizerError), + + #[fail(display = "{}", _0)] + ProjectorError(#[cause] mentat_query_projector::ProjectorError), + + #[fail(display = "{}", _0)] + PullError(#[cause] mentat_query_pull::PullError), + + #[fail(display = "{}", _0)] + SQLError(#[cause] mentat_sql::SQLError), +} + +impl From for MentatError { + fn from(error: std::io::Error) -> MentatError { + MentatError::IoError(error) + } +} + +impl From for MentatError { + fn from(error: rusqlite::Error) -> MentatError { + MentatError::RusqliteError(error.to_string()) + } +} + +impl From for MentatError { + fn from(error: edn::ParseError) -> MentatError { + MentatError::EdnParseError(error) + } +} + +impl From for MentatError { + fn from(error: mentat_db::DbError) -> MentatError { + MentatError::DbError(error) + } +} + +impl From for MentatError { + fn from(error: mentat_query_algebrizer::AlgebrizerError) -> MentatError { + MentatError::AlgebrizerError(error) + } +} + +impl From for MentatError { + fn from(error: mentat_query_projector::ProjectorError) -> MentatError { + MentatError::ProjectorError(error) + } +} + +impl From for MentatError { + fn from(error: mentat_query_pull::PullError) -> MentatError { + MentatError::PullError(error) + } +} + +impl From for MentatError { + fn from(error: mentat_sql::SQLError) -> MentatError { + MentatError::SQLError(error) + } } diff --git a/src/lib.rs b/src/lib.rs index c62f3af8..28159ffc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -104,6 +104,10 @@ macro_rules! kw { #[macro_use] pub mod errors; +pub use errors::{ + MentatError, + Result, +}; pub mod conn; pub mod entity_builder; pub mod query; diff --git a/src/store.rs b/src/store.rs index 30856683..52a37d5c 100644 --- a/src/store.rs +++ b/src/store.rs @@ -234,8 +234,8 @@ impl Pullable for Store { } impl Syncable for Store { - fn sync(&mut self, server_uri: &String, user_uuid: &String) -> Result<()> { - let uuid = Uuid::parse_str(&user_uuid)?; + fn sync(&mut self, server_uri: &String, user_uuid: &String) -> ::std::result::Result<(), ::failure::Error> { + let uuid = Uuid::parse_str(&user_uuid).map_err(|_| MentatError::BadUuid(user_uuid.clone()))?; Ok(Syncer::flow(&mut self.sqlite, server_uri, &uuid)?) } } diff --git a/tests/query.rs b/tests/query.rs index 8b3b012e..6cbe188e 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -233,7 +233,7 @@ fn test_unbound_inputs() { let results = q_uncached(&c, &db.schema, "[:find ?i . :in ?e :where [?e :db/ident ?i]]", inputs); - match results.expect_err("expected unbound variables").downcast().expect("expected specific error") { + match results.expect_err("expected unbound variables") { MentatError::UnboundVariables(vars) => { assert_eq!(vars, vec!["?e".to_string()].into_iter().collect()); }, @@ -411,8 +411,8 @@ fn test_fulltext() { [?a :foo/term ?term] ]"#; let r = conn.q_once(&mut c, query, None); - match r.expect_err("expected query to fail").downcast() { - Ok(mentat_query_algebrizer::AlgebrizerError::InvalidArgument(PlainSymbol(s), ty, i)) => { + match r.expect_err("expected query to fail") { + MentatError::AlgebrizerError(mentat_query_algebrizer::AlgebrizerError::InvalidArgument(PlainSymbol(s), ty, i)) => { assert_eq!(s, "fulltext"); assert_eq!(ty, "string"); assert_eq!(i, 2); @@ -426,8 +426,8 @@ fn test_fulltext() { [?a :foo/term ?term] [(fulltext $ :foo/fts ?a) [[?x ?val]]]]"#; let r = conn.q_once(&mut c, query, None); - match r.expect_err("expected query to fail").downcast() { - Ok(mentat_query_algebrizer::AlgebrizerError::InvalidArgument(PlainSymbol(s), ty, i)) => { + match r.expect_err("expected query to fail") { + MentatError::AlgebrizerError(mentat_query_algebrizer::AlgebrizerError::InvalidArgument(PlainSymbol(s), ty, i)) => { assert_eq!(s, "fulltext"); assert_eq!(ty, "string"); assert_eq!(i, 2); @@ -582,8 +582,8 @@ fn test_aggregates_type_handling() { // No type limits => can't do it. let r = store.q_once(r#"[:find (sum ?v) . :where [_ _ ?v]]"#, None); let all_types = ValueTypeSet::any(); - match r.expect_err("expected query to fail").downcast() { - Ok(::mentat_query_projector::errors::ProjectorError::CannotApplyAggregateOperationToTypes( + match r.expect_err("expected query to fail") { + MentatError::ProjectorError(::mentat_query_projector::errors::ProjectorError::CannotApplyAggregateOperationToTypes( SimpleAggregationOp::Sum, types)) => { assert_eq!(types, all_types); }, @@ -594,8 +594,8 @@ fn test_aggregates_type_handling() { let r = store.q_once(r#"[:find (sum ?v) . :where [_ _ ?v] [(type ?v :db.type/instant)]]"#, None); - match r.expect_err("expected query to fail").downcast() { - Ok(::mentat_query_projector::errors::ProjectorError::CannotApplyAggregateOperationToTypes( + match r.expect_err("expected query to fail") { + MentatError::ProjectorError(::mentat_query_projector::errors::ProjectorError::CannotApplyAggregateOperationToTypes( SimpleAggregationOp::Sum, types)) => { assert_eq!(types, ValueTypeSet::of_one(ValueType::Instant)); @@ -1336,8 +1336,8 @@ fn test_aggregation_implicit_grouping() { [?person :foo/play ?game] [?person :foo/is-vegetarian true] [?person :foo/name ?name]]"#, None); - match res.expect_err("expected query to fail").downcast() { - Ok(::mentat_query_projector::errors::ProjectorError::AmbiguousAggregates(mmc, cc)) => { + match res.expect_err("expected query to fail") { + MentatError::ProjectorError(::mentat_query_projector::errors::ProjectorError::AmbiguousAggregates(mmc, cc)) => { assert_eq!(mmc, 2); assert_eq!(cc, 1); }, diff --git a/tests/vocabulary.rs b/tests/vocabulary.rs index 9e4841d2..e8f7ce5d 100644 --- a/tests/vocabulary.rs +++ b/tests/vocabulary.rs @@ -286,8 +286,8 @@ fn test_add_vocab() { // Scoped borrow of `conn`. { let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); - match in_progress.ensure_vocabulary(&foo_v1_malformed).expect_err("expected vocabulary to fail").downcast() { - Ok(MentatError::ConflictingAttributeDefinitions(vocab, version, attr, theirs, ours)) => { + match in_progress.ensure_vocabulary(&foo_v1_malformed).expect_err("expected vocabulary to fail") { + MentatError::ConflictingAttributeDefinitions(vocab, version, attr, theirs, ours) => { assert_eq!(vocab.as_str(), ":org.mozilla/foo"); assert_eq!(attr.as_str(), ":foo/baz"); assert_eq!(version, 1); diff --git a/tools/cli/src/mentat_cli/repl.rs b/tools/cli/src/mentat_cli/repl.rs index ce08286a..6c3564e0 100644 --- a/tools/cli/src/mentat_cli/repl.rs +++ b/tools/cli/src/mentat_cli/repl.rs @@ -11,7 +11,6 @@ use std::io::Write; use failure::{ - err_msg, Error, }; @@ -404,7 +403,8 @@ impl Repl { if self.path.is_empty() || path != self.path { let next = match encryption_key { #[cfg(not(feature = "sqlcipher"))] - Some(_) => return Err(err_msg(".open_encrypted and .empty_encrypted require the sqlcipher Mentat feature")), + Some(_) => return Err(::mentat::MentatError::RusqliteError(".open_encrypted and .empty_encrypted require the sqlcipher Mentat feature".into())), + #[cfg(feature = "sqlcipher")] Some(k) => { if empty {