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.
This commit is contained in:
Nick Alexander 2018-06-27 13:19:40 -07:00
parent ac1b0b15fe
commit d31ec28aa8
8 changed files with 124 additions and 36 deletions

View file

@ -185,7 +185,7 @@ pub trait Pullable {
} }
pub trait Syncable { 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. /// 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; .partition_map[":db.part/user"].index;
let t = format!("[[:db/add {} :db.schema/attribute \"tempid\"]]", next + 1); 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() { match conn.transact(&mut sqlite, t.as_str()) {
Ok(e @ ::mentat_db::DbError { .. }) => { Err(MentatError::DbError(e)) => {
assert_eq!(e.kind(), ::mentat_db::DbErrorKind::UnrecognizedEntid(next + 1)); assert_eq!(e.kind(), ::mentat_db::DbErrorKind::UnrecognizedEntid(next + 1));
}, },
x => panic!("expected db error, got {:?}", x), 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! // we should reject this, because the first ID was provided by the user!
let t = format!("[[:db/add {} :db.schema/attribute \"tempid\"]]", next); let t = format!("[[:db/add {} :db.schema/attribute \"tempid\"]]", next);
match conn.transact(&mut sqlite, t.as_str()).expect_err("expected transact error").downcast() { match conn.transact(&mut sqlite, t.as_str()) {
Ok(e @ ::mentat_db::DbError { .. }) => { Err(MentatError::DbError(e)) => {
// All this, despite this being the ID we were about to allocate! // All this, despite this being the ID we were about to allocate!
assert_eq!(e.kind(), ::mentat_db::DbErrorKind::UnrecognizedEntid(next)); assert_eq!(e.kind(), ::mentat_db::DbErrorKind::UnrecognizedEntid(next));
}, },
@ -1059,9 +1059,9 @@ mod tests {
// Bad EDN: missing closing ']'. // Bad EDN: missing closing ']'.
let report = conn.transact(&mut sqlite, "[[:db/add \"t\" :db/ident :a/keyword]"); 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() { match report.expect_err("expected transact to fail for bad edn") {
Ok(edn::ParseError { .. }) => { }, MentatError::EdnParseError(_) => { },
Err(x) => panic!("expected EDN parse error, got {:?}", x), x => panic!("expected EDN parse error, got {:?}", x),
} }
// Good EDN. // Good EDN.
@ -1070,9 +1070,9 @@ mod tests {
// Bad transaction data: missing leading :db/add. // Bad transaction data: missing leading :db/add.
let report = conn.transact(&mut sqlite, "[[\"t\" :db/ident :b/keyword]]"); let report = conn.transact(&mut sqlite, "[[\"t\" :db/ident :b/keyword]]");
match report.expect_err("expected transact error").downcast() { match report.expect_err("expected transact error") {
Ok(edn::ParseError { .. }) => { }, MentatError::EdnParseError(_) => { },
Err(x) => panic!("expected EDN parse error, got {:?}", x), x => panic!("expected EDN parse error, got {:?}", x),
} }
// Good transaction data. // Good transaction data.
@ -1082,8 +1082,8 @@ mod tests {
// Bad transaction based on state of store: conflicting upsert. // Bad transaction based on state of store: conflicting upsert.
let report = conn.transact(&mut sqlite, "[[:db/add \"u\" :db/ident :a/keyword] let report = conn.transact(&mut sqlite, "[[:db/add \"u\" :db/ident :a/keyword]
[:db/add \"u\" :db/ident :b/keyword]]"); [:db/add \"u\" :db/ident :b/keyword]]");
match report.expect_err("expected transact error").downcast() { match report.expect_err("expected transact error") {
Ok(e @ ::mentat_db::DbError { .. }) => { MentatError::DbError(e) => {
match e.kind() { match e.kind() {
::mentat_db::DbErrorKind::SchemaConstraintViolation(_) => {}, ::mentat_db::DbErrorKind::SchemaConstraintViolation(_) => {},
_ => panic!("expected SchemaConstraintViolation"), _ => panic!("expected SchemaConstraintViolation"),
@ -1106,8 +1106,8 @@ mod tests {
let kw = kw!(:foo/bat); let kw = kw!(:foo/bat);
let schema = conn.current_schema(); let schema = conn.current_schema();
let res = conn.cache(&mut sqlite, &schema, &kw, CacheDirection::Forward, CacheAction::Register); let res = conn.cache(&mut sqlite, &schema, &kw, CacheDirection::Forward, CacheAction::Register);
match res.expect_err("expected cache to fail").downcast() { match res.expect_err("expected cache to fail") {
Ok(MentatError::UnknownAttribute(msg)) => assert_eq!(msg, ":foo/bat"), MentatError::UnknownAttribute(msg) => assert_eq!(msg, ":foo/bat"),
x => panic!("expected UnknownAttribute error, got {:?}", x), x => panic!("expected UnknownAttribute error, got {:?}", x),
} }
} }

View file

@ -419,8 +419,8 @@ mod testing {
let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully");
// This should fail: unrecognized entid. // This should fail: unrecognized entid.
match in_progress.transact_terms(terms, tempids).expect_err("expected transact to fail").downcast() { match in_progress.transact_terms(terms, tempids).expect_err("expected transact to fail") {
Ok(e @ mentat_db::DbError { .. }) => { MentatError::DbError(e) => {
assert_eq!(e.kind(), mentat_db::DbErrorKind::UnrecognizedEntid(999)); assert_eq!(e.kind(), mentat_db::DbErrorKind::UnrecognizedEntid(999));
}, },
_ => panic!("Should have rejected the entid."), _ => panic!("Should have rejected the entid."),

View file

@ -14,16 +14,23 @@ use std; // To refer to std::result::Result.
use std::collections::BTreeSet; use std::collections::BTreeSet;
use failure::Error; use rusqlite;
use edn;
use mentat_core::{ use mentat_core::{
Attribute, Attribute,
ValueType, ValueType,
}; };
use mentat_db;
use mentat_query; use mentat_query;
use mentat_query_algebrizer;
use mentat_query_projector;
use mentat_query_pull;
use mentat_sql;
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, MentatError>;
#[macro_export] #[macro_export]
macro_rules! bail { macro_rules! bail {
@ -34,6 +41,9 @@ macro_rules! bail {
#[derive(Debug, Fail)] #[derive(Debug, Fail)]
pub enum MentatError { pub enum MentatError {
#[fail(display = "bad uuid {}", _0)]
BadUuid(String),
#[fail(display = "path {} already exists", _0)] #[fail(display = "path {} already exists", _0)]
PathAlreadyExists(String), PathAlreadyExists(String),
@ -69,4 +79,78 @@ pub enum MentatError {
#[fail(display = "provided value of type {} doesn't match attribute value type {}", _0, _1)] #[fail(display = "provided value of type {} doesn't match attribute value type {}", _0, _1)]
ValueTypeMismatch(ValueType, ValueType), 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<std::io::Error> for MentatError {
fn from(error: std::io::Error) -> MentatError {
MentatError::IoError(error)
}
}
impl From<rusqlite::Error> for MentatError {
fn from(error: rusqlite::Error) -> MentatError {
MentatError::RusqliteError(error.to_string())
}
}
impl From<edn::ParseError> for MentatError {
fn from(error: edn::ParseError) -> MentatError {
MentatError::EdnParseError(error)
}
}
impl From<mentat_db::DbError> for MentatError {
fn from(error: mentat_db::DbError) -> MentatError {
MentatError::DbError(error)
}
}
impl From<mentat_query_algebrizer::AlgebrizerError> for MentatError {
fn from(error: mentat_query_algebrizer::AlgebrizerError) -> MentatError {
MentatError::AlgebrizerError(error)
}
}
impl From<mentat_query_projector::ProjectorError> for MentatError {
fn from(error: mentat_query_projector::ProjectorError) -> MentatError {
MentatError::ProjectorError(error)
}
}
impl From<mentat_query_pull::PullError> for MentatError {
fn from(error: mentat_query_pull::PullError) -> MentatError {
MentatError::PullError(error)
}
}
impl From<mentat_sql::SQLError> for MentatError {
fn from(error: mentat_sql::SQLError) -> MentatError {
MentatError::SQLError(error)
}
} }

View file

@ -104,6 +104,10 @@ macro_rules! kw {
#[macro_use] #[macro_use]
pub mod errors; pub mod errors;
pub use errors::{
MentatError,
Result,
};
pub mod conn; pub mod conn;
pub mod entity_builder; pub mod entity_builder;
pub mod query; pub mod query;

View file

@ -234,8 +234,8 @@ impl Pullable for Store {
} }
impl Syncable for Store { impl Syncable for Store {
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> {
let uuid = Uuid::parse_str(&user_uuid)?; let uuid = Uuid::parse_str(&user_uuid).map_err(|_| MentatError::BadUuid(user_uuid.clone()))?;
Ok(Syncer::flow(&mut self.sqlite, server_uri, &uuid)?) Ok(Syncer::flow(&mut self.sqlite, server_uri, &uuid)?)
} }
} }

View file

@ -233,7 +233,7 @@ fn test_unbound_inputs() {
let results = q_uncached(&c, &db.schema, let results = q_uncached(&c, &db.schema,
"[:find ?i . :in ?e :where [?e :db/ident ?i]]", inputs); "[: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) => { MentatError::UnboundVariables(vars) => {
assert_eq!(vars, vec!["?e".to_string()].into_iter().collect()); assert_eq!(vars, vec!["?e".to_string()].into_iter().collect());
}, },
@ -411,8 +411,8 @@ fn test_fulltext() {
[?a :foo/term ?term] [?a :foo/term ?term]
]"#; ]"#;
let r = conn.q_once(&mut c, query, None); let r = conn.q_once(&mut c, query, None);
match r.expect_err("expected query to fail").downcast() { match r.expect_err("expected query to fail") {
Ok(mentat_query_algebrizer::AlgebrizerError::InvalidArgument(PlainSymbol(s), ty, i)) => { MentatError::AlgebrizerError(mentat_query_algebrizer::AlgebrizerError::InvalidArgument(PlainSymbol(s), ty, i)) => {
assert_eq!(s, "fulltext"); assert_eq!(s, "fulltext");
assert_eq!(ty, "string"); assert_eq!(ty, "string");
assert_eq!(i, 2); assert_eq!(i, 2);
@ -426,8 +426,8 @@ fn test_fulltext() {
[?a :foo/term ?term] [?a :foo/term ?term]
[(fulltext $ :foo/fts ?a) [[?x ?val]]]]"#; [(fulltext $ :foo/fts ?a) [[?x ?val]]]]"#;
let r = conn.q_once(&mut c, query, None); let r = conn.q_once(&mut c, query, None);
match r.expect_err("expected query to fail").downcast() { match r.expect_err("expected query to fail") {
Ok(mentat_query_algebrizer::AlgebrizerError::InvalidArgument(PlainSymbol(s), ty, i)) => { MentatError::AlgebrizerError(mentat_query_algebrizer::AlgebrizerError::InvalidArgument(PlainSymbol(s), ty, i)) => {
assert_eq!(s, "fulltext"); assert_eq!(s, "fulltext");
assert_eq!(ty, "string"); assert_eq!(ty, "string");
assert_eq!(i, 2); assert_eq!(i, 2);
@ -582,8 +582,8 @@ fn test_aggregates_type_handling() {
// No type limits => can't do it. // No type limits => can't do it.
let r = store.q_once(r#"[:find (sum ?v) . :where [_ _ ?v]]"#, None); let r = store.q_once(r#"[:find (sum ?v) . :where [_ _ ?v]]"#, None);
let all_types = ValueTypeSet::any(); let all_types = ValueTypeSet::any();
match r.expect_err("expected query to fail").downcast() { match r.expect_err("expected query to fail") {
Ok(::mentat_query_projector::errors::ProjectorError::CannotApplyAggregateOperationToTypes( MentatError::ProjectorError(::mentat_query_projector::errors::ProjectorError::CannotApplyAggregateOperationToTypes(
SimpleAggregationOp::Sum, types)) => { SimpleAggregationOp::Sum, types)) => {
assert_eq!(types, all_types); assert_eq!(types, all_types);
}, },
@ -594,8 +594,8 @@ fn test_aggregates_type_handling() {
let r = store.q_once(r#"[:find (sum ?v) . let r = store.q_once(r#"[:find (sum ?v) .
:where [_ _ ?v] [(type ?v :db.type/instant)]]"#, :where [_ _ ?v] [(type ?v :db.type/instant)]]"#,
None); None);
match r.expect_err("expected query to fail").downcast() { match r.expect_err("expected query to fail") {
Ok(::mentat_query_projector::errors::ProjectorError::CannotApplyAggregateOperationToTypes( MentatError::ProjectorError(::mentat_query_projector::errors::ProjectorError::CannotApplyAggregateOperationToTypes(
SimpleAggregationOp::Sum, SimpleAggregationOp::Sum,
types)) => { types)) => {
assert_eq!(types, ValueTypeSet::of_one(ValueType::Instant)); assert_eq!(types, ValueTypeSet::of_one(ValueType::Instant));
@ -1336,8 +1336,8 @@ fn test_aggregation_implicit_grouping() {
[?person :foo/play ?game] [?person :foo/play ?game]
[?person :foo/is-vegetarian true] [?person :foo/is-vegetarian true]
[?person :foo/name ?name]]"#, None); [?person :foo/name ?name]]"#, None);
match res.expect_err("expected query to fail").downcast() { match res.expect_err("expected query to fail") {
Ok(::mentat_query_projector::errors::ProjectorError::AmbiguousAggregates(mmc, cc)) => { MentatError::ProjectorError(::mentat_query_projector::errors::ProjectorError::AmbiguousAggregates(mmc, cc)) => {
assert_eq!(mmc, 2); assert_eq!(mmc, 2);
assert_eq!(cc, 1); assert_eq!(cc, 1);
}, },

View file

@ -286,8 +286,8 @@ fn test_add_vocab() {
// Scoped borrow of `conn`. // Scoped borrow of `conn`.
{ {
let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); 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() { match in_progress.ensure_vocabulary(&foo_v1_malformed).expect_err("expected vocabulary to fail") {
Ok(MentatError::ConflictingAttributeDefinitions(vocab, version, attr, theirs, ours)) => { MentatError::ConflictingAttributeDefinitions(vocab, version, attr, theirs, ours) => {
assert_eq!(vocab.as_str(), ":org.mozilla/foo"); assert_eq!(vocab.as_str(), ":org.mozilla/foo");
assert_eq!(attr.as_str(), ":foo/baz"); assert_eq!(attr.as_str(), ":foo/baz");
assert_eq!(version, 1); assert_eq!(version, 1);

View file

@ -11,7 +11,6 @@
use std::io::Write; use std::io::Write;
use failure::{ use failure::{
err_msg,
Error, Error,
}; };
@ -404,7 +403,8 @@ impl Repl {
if self.path.is_empty() || path != self.path { if self.path.is_empty() || path != self.path {
let next = match encryption_key { let next = match encryption_key {
#[cfg(not(feature = "sqlcipher"))] #[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")] #[cfg(feature = "sqlcipher")]
Some(k) => { Some(k) => {
if empty { if empty {