Browse Source

Add a DBError type for specific errors from DB::new().

Kestrel 2 years ago
parent
commit
969a578000
3 changed files with 42 additions and 17 deletions
  1. 1 1
      microrm-macros/Cargo.toml
  2. 3 3
      microrm-macros/src/lib.rs
  3. 38 13
      microrm/src/lib.rs

+ 1 - 1
microrm-macros/Cargo.toml

@@ -10,7 +10,7 @@ proc-macro = true
 
 [dependencies]
 # proc_macro = "*"
-syn = { version = "1.0", features = ["derive", "extra-traits"] }
+syn = { version = "1.0", features = ["derive"] }
 quote = "1.0"
 convert_case = "0.5"
 proc-macro2 = "1.0"

+ 3 - 3
microrm-macros/src/lib.rs

@@ -5,6 +5,7 @@ use quote::{quote,format_ident};
 use convert_case::{Case, Casing};
 
 /// Turns a serializable/deserializable struct into a microrm entity model.
+///
 /// There are two important visible effects:
 /// - Provides an implementation of `microrm::model::Entity`
 /// - Defines a <struct-name>Columns enum
@@ -13,9 +14,9 @@ use convert_case::{Case, Casing};
 /// where applicable, so a struct named `TestModel` is given a table name `test_model`
 /// and a struct field named `field_name` is given a variant name of `FieldName`.
 ///
-/// The #[microrm...] attributes can be used to control the derivation somewhat.
+/// The `#[microrm...]` attributes can be used to control the derivation somewhat.
 /// The following are understood:
-/// - #[microrm_internal]: this is internal to the microrm crate (of extremely limited usefulness
+/// - `#[microrm_internal]`: this is internal to the microrm crate (of extremely limited usefulness
 /// outside the microrm library)
 #[proc_macro_derive(Entity, attributes(microrm_internal))]
 pub fn derive_entity(tokens: TokenStream) -> TokenStream {
@@ -34,7 +35,6 @@ pub fn derive_entity(tokens: TokenStream) -> TokenStream {
         }
         else {
             let body : Result<syn::Expr,_> = syn::parse2(attr.tokens);
-            println!("body: {:?}", body);
             if body.is_err() { continue }
         }
     }

+ 38 - 13
microrm/src/lib.rs

@@ -52,6 +52,24 @@ pub mod re_export {
     pub use rusqlite;
 }
 
+#[derive(Debug)]
+pub enum DBError {
+    ConnectFailure,
+    NoSchema,
+    DifferentSchema,
+    DropFailure,
+    CreateFailure,
+    SanityCheckFailure
+}
+
+impl std::fmt::Display for DBError {
+    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+        fmt.write_fmt(format_args!("Database error: {:?}", self))
+    }
+}
+
+impl std::error::Error for DBError { }
+
 /// SQLite database connection
 pub struct DB {
     conn: rusqlite::Connection,
@@ -60,24 +78,24 @@ pub struct DB {
 }
 
 impl DB {
-    pub fn new(schema: model::SchemaModel, path: &str, allow_recreate: bool) -> Result<Self, &'static str> {
+    pub fn new(schema: model::SchemaModel, path: &str, allow_recreate: bool) -> Result<Self, DBError> {
         Self::from_connection(
-            rusqlite::Connection::open(path).expect("Opening database connection failed"),
+            rusqlite::Connection::open(path).map_err(|e| DBError::ConnectFailure)?,
             schema,
             allow_recreate,
         )
     }
 
-    /// For use in tests
-    pub fn new_in_memory(schema: model::SchemaModel) -> Result<Self, &'static str> {
+    /// Mostly for use in tests, but may be useful in some applications as well.
+    pub fn new_in_memory(schema: model::SchemaModel) -> Result<Self, DBError> {
         Self::from_connection(
-            rusqlite::Connection::open_in_memory().expect("Opening database connection failed"),
+            rusqlite::Connection::open_in_memory().map_err(|e| DBError::ConnectFailure)?,
             schema,
             true,
         )
     }
 
-    pub fn recreate_schema(&self) -> Result<(), &'static str> {
+    pub fn recreate_schema(&self) -> Result<(), DBError> {
         self.create_schema()
     }
 
@@ -85,7 +103,7 @@ impl DB {
         conn: rusqlite::Connection,
         schema: model::SchemaModel,
         allow_recreate: bool,
-    ) -> Result<Self, &'static str> {
+    ) -> Result<Self, DBError> {
         let sig = Self::calculate_schema_hash(&schema);
         let ret = Self {
             conn,
@@ -114,12 +132,18 @@ impl DB {
         base64::encode(hasher.finalize())
     }
 
-    fn check_schema(&self, allow_recreate: bool) -> Result<(), &'static str> {
+    fn check_schema(&self, allow_recreate: bool) -> Result<(), DBError> {
         let hash = query::get_one_by(self, meta::MetaschemaColumns::Key, "schema_hash");
 
-        if hash.is_none() || hash.unwrap().value != self.schema_hash {
+        if hash.is_none() {
+            if !allow_recreate {
+                return Err(DBError::NoSchema)
+            }
+            self.create_schema();
+        }
+        else if hash.unwrap().value != self.schema_hash {
             if !allow_recreate {
-                return Err("No schema version in database, and not allowed to create!")
+                return Err(DBError::DifferentSchema)
             }
             self.create_schema();
         }
@@ -127,15 +151,15 @@ impl DB {
         Ok(())
     }
 
-    fn create_schema(&self) -> Result<(), &'static str> {
+    fn create_schema(&self) -> Result<(), DBError> {
         for ds in self.schema.drop() {
             let prepared = self.conn.prepare(ds);
-            prepared.unwrap().execute([]).expect("Dropping sql failed");
+            prepared.unwrap().execute([]).map_err(|e| DBError::DropFailure);
         }
 
         for cs in self.schema.create() {
             let prepared = self.conn.prepare(cs);
-            prepared.unwrap().execute([]).expect("Creation sql failed");
+            prepared.unwrap().execute([]).map_err(|e| DBError::CreateFailure);
         }
 
         query::add(
@@ -148,6 +172,7 @@ impl DB {
 
         let sanity_check = query::get_one_by(self, meta::MetaschemaColumns::Key, "schema_hash");
         assert_eq!(sanity_check.is_some(), true);
+        assert_eq!(sanity_check.unwrap().value, self.schema_hash);
 
         Ok(())
     }