Repository: replydev/cotp (language: rust)

Issue:
Use color-eyre crate for error handling
[color-eyre](https://lib.rs/crates/color-eyre)[ [docs]](https://docs.rs/color-eyre)
A fork of anyhow that gives you more control over the format of the generated error messages. Recommended if you intend to present error messages to end users. Otherwise anyhow is simpler.


Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index bc3f4bd6..ae359a0e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,6 +2,21 @@
 # It is not intended for manual editing.
 version = 3
 
+[[package]]
+name = "addr2line"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
 [[package]]
 name = "aead"
 version = "0.5.1"
@@ -112,6 +127,21 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
+[[package]]
+name = "backtrace"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
 [[package]]
 name = "base64"
 version = "0.21.4"
@@ -180,6 +210,15 @@ version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
 
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "cfg-if"
 version = "1.0.0"
@@ -277,6 +316,33 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "color-eyre"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204"
+dependencies = [
+ "backtrace",
+ "color-spantrace",
+ "eyre",
+ "indenter",
+ "once_cell",
+ "owo-colors",
+ "tracing-error",
+]
+
+[[package]]
+name = "color-spantrace"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce"
+dependencies = [
+ "once_cell",
+ "owo-colors",
+ "tracing-core",
+ "tracing-error",
+]
+
 [[package]]
 name = "color_quant"
 version = "1.1.0"
@@ -335,6 +401,7 @@ dependencies = [
  "base64",
  "chacha20poly1305",
  "clap",
+ "color-eyre",
  "copypasta-ext",
  "crossterm",
  "data-encoding",
@@ -354,6 +421,7 @@ dependencies = [
  "serde_json",
  "sha-1",
  "sha2",
+ "url",
  "urlencoding",
  "zeroize",
 ]
@@ -471,6 +539,25 @@ version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
 
+[[package]]
+name = "eyre"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb"
+dependencies = [
+ "indenter",
+ "once_cell",
+]
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
+dependencies = [
+ "percent-encoding",
+]
+
 [[package]]
 name = "generic-array"
 version = "0.14.6"
@@ -512,6 +599,12 @@ dependencies = [
  "polyval",
 ]
 
+[[package]]
+name = "gimli"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
+
 [[package]]
 name = "heck"
 version = "0.4.1"
@@ -533,6 +626,16 @@ dependencies = [
  "digest",
 ]
 
+[[package]]
+name = "idna"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
 [[package]]
 name = "image"
 version = "0.23.14"
@@ -547,6 +650,12 @@ dependencies = [
  "num-traits",
 ]
 
+[[package]]
+name = "indenter"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
+
 [[package]]
 name = "indoc"
 version = "2.0.3"
@@ -673,6 +782,15 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
 
+[[package]]
+name = "miniz_oxide"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+dependencies = [
+ "adler",
+]
+
 [[package]]
 name = "mio"
 version = "0.8.4"
@@ -786,6 +904,15 @@ dependencies = [
  "objc",
 ]
 
+[[package]]
+name = "object"
+version = "0.32.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
+dependencies = [
+ "memchr",
+]
+
 [[package]]
 name = "once_cell"
 version = "1.18.0"
@@ -804,6 +931,12 @@ version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
 
+[[package]]
+name = "owo-colors"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
+
 [[package]]
 name = "parking_lot"
 version = "0.12.1"
@@ -854,6 +987,18 @@ dependencies = [
  "hmac",
 ]
 
+[[package]]
+name = "percent-encoding"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
+
 [[package]]
 name = "pkg-config"
 version = "0.3.25"
@@ -1019,6 +1164,12 @@ dependencies = [
  "constant_time_eq 0.3.0",
 ]
 
+[[package]]
+name = "rustc-demangle"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+
 [[package]]
 name = "rustversion"
 version = "1.0.14"
@@ -1117,6 +1268,15 @@ dependencies = [
  "digest",
 ]
 
+[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
 [[package]]
 name = "signal-hook"
 version = "0.3.17"
@@ -1257,6 +1417,16 @@ dependencies = [
  "syn 1.0.105",
 ]
 
+[[package]]
+name = "thread_local"
+version = "1.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
 [[package]]
 name = "time"
 version = "0.3.23"
@@ -1275,18 +1445,90 @@ version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
 
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tracing"
+version = "0.1.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
+dependencies = [
+ "cfg-if",
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-error"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
+dependencies = [
+ "tracing",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
+dependencies = [
+ "sharded-slab",
+ "thread_local",
+ "tracing-core",
+]
+
 [[package]]
 name = "typenum"
 version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
 
+[[package]]
+name = "unicode-bidi"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
+
 [[package]]
 name = "unicode-ident"
 version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
 
+[[package]]
+name = "unicode-normalization"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
+
 [[package]]
 name = "unicode-segmentation"
 version = "1.10.0"
@@ -1309,6 +1551,17 @@ dependencies = [
  "subtle",
 ]
 
+[[package]]
+name = "url"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
 [[package]]
 name = "urlencoding"
 version = "2.1.3"
@@ -1321,6 +1574,12 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
 
+[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
 [[package]]
 name = "version_check"
 version = "0.9.4"
diff --git a/Cargo.toml b/Cargo.toml
index a7929493..886f2802 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -52,3 +52,5 @@ base64 = "0.21.4"
 md-5 = "0.10.6"
 ratatui = { version = "0.23.0", features = ["all-widgets"] }
 crossterm = "0.27.0"
+url = "2.4.1"
+color-eyre = "0.6.2"
diff --git a/build.rs b/build.rs
index b7cc59cf..d0d4d2bf 100644
--- a/build.rs
+++ b/build.rs
@@ -1,4 +1,5 @@
 use std::env;
+use std::process::Command;
 
 fn main() {
     let version = env!("CARGO_PKG_VERSION");
@@ -7,6 +8,24 @@ fn main() {
         println!("cargo:rustc-env=COTP_VERSION={}", version);
     } else {
         // Suffix with -DEBUG
-        println!("cargo:rustc-env=COTP_VERSION={}-DEBUG", version);
+        // If we can get the last commit hash, let's append that also
+        if let Some(last_commit) = get_last_commit() {
+            println!(
+                "cargo:rustc-env=COTP_VERSION={}-DEBUG-{}",
+                version, last_commit
+            );
+        } else {
+            println!("cargo:rustc-env=COTP_VERSION={}-DEBUG", version);
+        }
     }
 }
+
+fn get_last_commit() -> Option<String> {
+    Command::new("git")
+        .args(["rev-parse", "--short=12", "HEAD"])
+        .output()
+        .ok()
+        .filter(|e| e.status.success())
+        .map(|e| String::from_utf8(e.stdout))
+        .and_then(|e| e.ok())
+}
diff --git a/src/args.rs b/src/args.rs
index bffd88c9..d602ed1f 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -1,6 +1,7 @@
 use std::path::PathBuf;
 
 use clap::{Args, Parser, Subcommand};
+use color_eyre::eyre::eyre;
 
 use crate::{
     argument_functions, dashboard,
@@ -31,7 +32,7 @@ enum CotpSubcommands {
 #[derive(Args)]
 pub struct AddArgs {
     /// Add OTP code via an OTP URI
-    #[arg(short = 'u', long = "otpuri", required_unless_present = "issuer")]
+    #[arg(short = 'u', long = "otpuri", required_unless_present = "label")]
     pub otp_uri: bool,
 
     /// Specify the OTP code type
@@ -39,12 +40,12 @@ pub struct AddArgs {
     pub otp_type: OTPType,
 
     /// Code issuer
-    #[arg(short, long, required_unless_present = "otp_uri")]
-    pub issuer: Option<String>,
+    #[arg(short, long, default_value = "")]
+    pub issuer: String,
 
     /// Code label
-    #[arg(short, long, default_value = "")]
-    pub label: String,
+    #[arg(short, long, required_unless_present = "otp_uri")]
+    pub label: Option<String>,
 
     /// OTP Algorithm
     #[arg(short, long, value_enum, default_value_t = OTPAlgorithm::Sha1)]
@@ -179,6 +180,10 @@ pub struct BackupType {
     /// Import from Microsoft Authenticator
     #[arg(short = 'm', long = "microsoft-authenticator")]
     pub microsoft_authenticator: bool,
+
+    /// Import from OTP Uri batch
+    #[arg(short, long = "otp-uri")]
+    pub otp_uri: bool,
 }
 
 #[derive(Args)]
@@ -188,9 +193,13 @@ pub struct ExportFormat {
     #[arg(short, long)]
     pub cotp: bool,
 
-    /// Import from andOTP backup
+    /// Export into andOTP backup
     #[arg(short = 'e', long)]
     pub andotp: bool,
+
+    /// Export into an OTP URI
+    #[arg(short, long = "otp-uri")]
+    pub otp_uri: bool,
 }
 
 impl Default for ExportFormat {
@@ -198,11 +207,12 @@ impl Default for ExportFormat {
         Self {
             cotp: true,
             andotp: false,
+            otp_uri: false,
         }
     }
 }
 
-pub fn args_parser(matches: CotpArgs, read_result: OTPDatabase) -> Result<OTPDatabase, String> {
+pub fn args_parser(matches: CotpArgs, read_result: OTPDatabase) -> color_eyre::Result<OTPDatabase> {
     match matches.command {
         Some(CotpSubcommands::Add(args)) => argument_functions::add(args, read_result),
         Some(CotpSubcommands::Edit(args)) => argument_functions::edit(args, read_result),
@@ -210,7 +220,7 @@ pub fn args_parser(matches: CotpArgs, read_result: OTPDatabase) -> Result<OTPDat
         Some(CotpSubcommands::Export(args)) => argument_functions::export(args, read_result),
         Some(CotpSubcommands::Passwd) => argument_functions::change_password(read_result),
         // no args, show dashboard
-        None => dashboard(read_result).map_err(|e| format!("{:?}", e)),
+        None => dashboard(read_result).map_err(|e| eyre!("An error occurred: {e}")),
     }
 }
 
diff --git a/src/argument_functions.rs b/src/argument_functions.rs
index f6c9f355..b75959fb 100644
--- a/src/argument_functions.rs
+++ b/src/argument_functions.rs
@@ -1,49 +1,54 @@
 use crate::args::{AddArgs, EditArgs, ExportArgs, ImportArgs};
 use crate::exporters::do_export;
+use crate::exporters::otp_uri::OtpUriList;
 use crate::importers::aegis::AegisJson;
 use crate::importers::aegis_encrypted::AegisEncryptedDatabase;
 use crate::importers::authy_remote_debug::AuthyExportedList;
 use crate::importers::converted::ConvertedJsonList;
 use crate::importers::freeotp_plus::FreeOTPPlusJson;
+use crate::importers::importer::import_from_path;
 use crate::otp::from_otp_uri::FromOtpUri;
 use crate::otp::otp_element::{OTPDatabase, OTPElement};
-use crate::{importers, utils};
+use crate::utils;
+use color_eyre::eyre::{eyre, ErrReport};
 use zeroize::Zeroize;
 
-pub fn import(matches: ImportArgs, mut database: OTPDatabase) -> Result<OTPDatabase, String> {
+pub fn import(matches: ImportArgs, mut database: OTPDatabase) -> color_eyre::Result<OTPDatabase> {
     let path = matches.path;
 
     let backup_type = matches.backup_type;
 
     let result = if backup_type.cotp {
-        importers::importer::import_from_path::<OTPDatabase>(path)
+        import_from_path::<OTPDatabase>(path)
     } else if backup_type.andotp {
-        importers::importer::import_from_path::<Vec<OTPElement>>(path)
+        import_from_path::<Vec<OTPElement>>(path)
     } else if backup_type.aegis {
-        importers::importer::import_from_path::<AegisJson>(path)
+        import_from_path::<AegisJson>(path)
     } else if backup_type.aegis_encrypted {
-        importers::importer::import_from_path::<AegisEncryptedDatabase>(path)
+        import_from_path::<AegisEncryptedDatabase>(path)
     } else if backup_type.freeotp_plus {
-        importers::importer::import_from_path::<FreeOTPPlusJson>(path)
+        import_from_path::<FreeOTPPlusJson>(path)
     } else if backup_type.authy_exported {
-        importers::importer::import_from_path::<AuthyExportedList>(path)
+        import_from_path::<AuthyExportedList>(path)
     } else if backup_type.google_authenticator
         || backup_type.authy
         || backup_type.microsoft_authenticator
         || backup_type.freeotp
     {
-        importers::importer::import_from_path::<ConvertedJsonList>(path)
+        import_from_path::<ConvertedJsonList>(path)
+    } else if backup_type.otp_uri {
+        import_from_path::<OtpUriList>(path)
     } else {
-        return Err(String::from("Invalid arguments provided"));
+        return Err(eyre!("Invalid arguments provided"));
     };
 
-    let elements = result.map_err(|e| format!("An error occurred: {e}"))?;
+    let elements = result.map_err(|e| eyre!("{e}"))?;
 
     database.add_all(elements);
     Ok(database)
 }
 
-pub fn add(matches: AddArgs, mut database: OTPDatabase) -> Result<OTPDatabase, String> {
+pub fn add(matches: AddArgs, mut database: OTPDatabase) -> color_eyre::Result<OTPDatabase> {
     let otp_element = if matches.otp_uri {
         let mut otp_uri = rpassword::prompt_password("Insert the otp uri: ").unwrap();
         let result = OTPElement::from_otp_uri(otp_uri.as_str());
@@ -53,24 +58,23 @@ pub fn add(matches: AddArgs, mut database: OTPDatabase) -> Result<OTPDatabase, S
         get_from_args(matches)?
     };
     if !otp_element.valid_secret() {
-        return Err(String::from("Invalid secret."));
+        return Err(ErrReport::msg("Invalid secret."));
     }
 
     database.add_element(otp_element);
     Ok(database)
 }
 
-fn get_from_args(matches: AddArgs) -> Result<OTPElement, String> {
-    let secret = rpassword::prompt_password("Insert the secret: ")
-        .map_err(|e| format!("Error during password insertion: {:?}", e))?;
+fn get_from_args(matches: AddArgs) -> color_eyre::Result<OTPElement> {
+    let secret = rpassword::prompt_password("Insert the secret: ").map_err(ErrReport::from)?;
     Ok(map_args_to_code(secret, matches))
 }
 
 fn map_args_to_code(secret: String, matches: AddArgs) -> OTPElement {
     OTPElement {
         secret,
-        issuer: matches.issuer.unwrap(),
-        label: matches.label,
+        issuer: matches.issuer,
+        label: matches.label.unwrap(),
         digits: matches.digits,
         type_: matches.otp_type,
         algorithm: matches.algorithm,
@@ -80,7 +84,7 @@ fn map_args_to_code(secret: String, matches: AddArgs) -> OTPElement {
     }
 }
 
-pub fn edit(matches: EditArgs, mut database: OTPDatabase) -> Result<OTPDatabase, String> {
+pub fn edit(matches: EditArgs, mut database: OTPDatabase) -> color_eyre::Result<OTPDatabase> {
     let secret = matches
         .change_secret
         .then(|| rpassword::prompt_password("Insert the secret: ").unwrap());
@@ -90,7 +94,7 @@ pub fn edit(matches: EditArgs, mut database: OTPDatabase) -> Result<OTPDatabase,
 
     if let Some(real_index) = index.checked_sub(1) {
         if real_index >= database.elements_ref().len() {
-            return Err(format!("{index} is an invalid index"));
+            return Err(eyre!("{index} is an invalid index"));
         }
 
         match database.mut_element(real_index) {
@@ -121,15 +125,15 @@ pub fn edit(matches: EditArgs, mut database: OTPDatabase) -> Result<OTPDatabase,
                 }
                 database.mark_modified();
             }
-            None => return Err(format!("No element found at index {index}")),
+            None => return Err(eyre!("No element found at index {index}")),
         }
         Ok(database)
     } else {
-        Err(format! {"{index} is an invalid index"})
+        Err(eyre!("{index} is an invalid index"))
     }
 }
 
-pub fn export(matches: ExportArgs, database: OTPDatabase) -> Result<OTPDatabase, String> {
+pub fn export(matches: ExportArgs, database: OTPDatabase) -> color_eyre::Result<OTPDatabase> {
     let export_format = matches.format.unwrap_or_default();
     let exported_path = if matches.path.is_dir() {
         matches.path.join("exported.cotp")
@@ -142,6 +146,9 @@ pub fn export(matches: ExportArgs, database: OTPDatabase) -> Result<OTPDatabase,
     } else if export_format.andotp {
         let andotp: &Vec<OTPElement> = (&database).into();
         do_export(&andotp, exported_path)
+    } else if export_format.otp_uri {
+        let otp_uri_list: OtpUriList = (&database).into();
+        do_export(&otp_uri_list, exported_path)
     } else {
         unreachable!("Unreachable code");
     }
@@ -152,14 +159,14 @@ pub fn export(matches: ExportArgs, database: OTPDatabase) -> Result<OTPDatabase,
         );
         database
     })
-    .map_err(|e| format!("An error occurred while exporting database: {e}"))
+    .map_err(|e| eyre!("An error occurred while exporting database: {e}"))
 }
 
-pub fn change_password(mut database: OTPDatabase) -> Result<OTPDatabase, String> {
+pub fn change_password(mut database: OTPDatabase) -> color_eyre::Result<OTPDatabase> {
     let mut new_password = utils::verified_password("New password: ", 8);
     database
         .save_with_pw(&new_password)
-        .map_err(|e| format!("An error has occurred: {e}"))?;
+        .map_err(ErrReport::from)?;
     new_password.zeroize();
     Ok(database)
 }
diff --git a/src/crypto/cryptography.rs b/src/crypto/cryptography.rs
index fa4adb6f..4eaa0856 100644
--- a/src/crypto/cryptography.rs
+++ b/src/crypto/cryptography.rs
@@ -1,6 +1,7 @@
 use argon2::{Config, Variant, Version};
 use chacha20poly1305::aead::Aead;
 use chacha20poly1305::{Key, KeyInit, XChaCha20Poly1305, XNonce};
+use color_eyre::eyre::{eyre, ErrReport};
 use data_encoding::BASE64;
 
 use super::encrypted_database::EncryptedDatabase;
@@ -19,20 +20,13 @@ const KEY_DERIVATION_CONFIG: Config = Config {
     hash_length: XCHACHA20_POLY1305_KEY_LENGTH as u32,
 };
 
-pub fn argon_derive_key(password_bytes: &[u8], salt: &[u8]) -> Result<Vec<u8>, String> {
-    let config = KEY_DERIVATION_CONFIG;
-    let hash = argon2::hash_raw(password_bytes, salt, &config);
-    match hash {
-        Ok(vec) => Ok(vec),
-        Err(_e) => Err(String::from("Failed to derive encryption key")),
-    }
+pub fn argon_derive_key(password_bytes: &[u8], salt: &[u8]) -> color_eyre::Result<Vec<u8>> {
+    argon2::hash_raw(password_bytes, salt, &KEY_DERIVATION_CONFIG).map_err(ErrReport::from)
 }
 
-pub fn gen_salt() -> Result<[u8; ARGON2ID_SALT_LENGTH], String> {
+pub fn gen_salt() -> color_eyre::Result<[u8; ARGON2ID_SALT_LENGTH]> {
     let mut salt: [u8; ARGON2ID_SALT_LENGTH] = [0; ARGON2ID_SALT_LENGTH];
-    if let Err(e) = getrandom::getrandom(&mut salt) {
-        return Err(format!("Error during salt generation: {e}"));
-    }
+    getrandom::getrandom(&mut salt).map_err(ErrReport::from)?;
     Ok(salt)
 }
 
@@ -40,19 +34,19 @@ pub fn encrypt_string_with_key(
     plain_text: String,
     key: &Vec<u8>,
     salt: &[u8],
-) -> Result<EncryptedDatabase, String> {
+) -> color_eyre::Result<EncryptedDatabase> {
     let wrapped_key = Key::from_slice(key.as_slice());
 
     let aead = XChaCha20Poly1305::new(wrapped_key);
     let mut nonce_bytes: [u8; XCHACHA20_POLY1305_NONCE_LENGTH] =
         [0; XCHACHA20_POLY1305_NONCE_LENGTH];
-    if let Err(e) = getrandom::getrandom(&mut nonce_bytes) {
-        return Err(format!("Error during nonce generation: {e}"));
-    }
+
+    getrandom::getrandom(&mut nonce_bytes).map_err(ErrReport::from)?;
+
     let nonce = XNonce::from_slice(&nonce_bytes);
     let cipher_text = aead
         .encrypt(nonce, plain_text.as_bytes())
-        .expect("Failed to encrypt");
+        .map_err(|e| eyre!("Error during encryption: {e}"))?;
     Ok(EncryptedDatabase::new(
         1,
         BASE64.encode(&nonce_bytes),
@@ -64,10 +58,10 @@ pub fn encrypt_string_with_key(
 pub fn decrypt_string(
     encrypted_text: &str,
     password: &str,
-) -> Result<(String, Vec<u8>, Vec<u8>), String> {
+) -> color_eyre::Result<(String, Vec<u8>, Vec<u8>)> {
     //encrypted text is an encrypted database json serialized object
     let encrypted_database: EncryptedDatabase = serde_json::from_str(encrypted_text)
-        .map_err(|e| format!("Error during encrypted database deserialization: {e}"))?;
+        .map_err(|e| eyre!("Error during encrypted database deserialization: {e}"))?;
     let nonce = BASE64
         .decode(encrypted_database.nonce().as_bytes())
         .expect("Cannot decode Base64 nonce");
@@ -84,11 +78,9 @@ pub fn decrypt_string(
     let nonce = XNonce::from_slice(nonce.as_slice());
     let decrypted = aead
         .decrypt(nonce, cipher_text.as_slice())
-        .map_err(|_| String::from("Wrong password"))?;
-    match String::from_utf8(decrypted) {
-        Ok(result) => Ok((result, key, salt)),
-        Err(e) => Err(format!("Error during UTF-8 string conversion: {e}")),
-    }
+        .map_err(|_| eyre!("Wrong password"))?;
+    let from_utf8 = String::from_utf8(decrypted).map_err(ErrReport::from)?;
+    Ok((from_utf8, key, salt))
 }
 
 #[cfg(test)]
diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs
index c5381d53..edfb34ff 100644
--- a/src/exporters/mod.rs
+++ b/src/exporters/mod.rs
@@ -4,6 +4,7 @@ use serde::Serialize;
 use zeroize::Zeroize;
 
 pub mod andotp;
+pub mod otp_uri;
 
 pub fn do_export<T>(to_be_saved: &T, exported_path: PathBuf) -> Result<PathBuf, String>
 where
diff --git a/src/exporters/otp_uri.rs b/src/exporters/otp_uri.rs
new file mode 100644
index 00000000..4d4f6b27
--- /dev/null
+++ b/src/exporters/otp_uri.rs
@@ -0,0 +1,15 @@
+use crate::otp::otp_element::OTPDatabase;
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize)]
+pub struct OtpUriList {
+    pub items: Vec<String>,
+}
+
+impl<'a> From<&'a OTPDatabase> for OtpUriList {
+    fn from(value: &'a OTPDatabase) -> Self {
+        let items: Vec<String> = value.elements.iter().map(|e| e.get_otpauth_uri()).collect();
+
+        OtpUriList { items }
+    }
+}
diff --git a/src/importers/mod.rs b/src/importers/mod.rs
index 7314f9ef..2a81d2aa 100644
--- a/src/importers/mod.rs
+++ b/src/importers/mod.rs
@@ -4,3 +4,4 @@ pub mod authy_remote_debug;
 pub mod converted;
 pub mod freeotp_plus;
 pub mod importer;
+pub mod otp_uri;
diff --git a/src/importers/otp_uri.rs b/src/importers/otp_uri.rs
new file mode 100644
index 00000000..29b3ee7f
--- /dev/null
+++ b/src/importers/otp_uri.rs
@@ -0,0 +1,16 @@
+use crate::exporters::otp_uri::OtpUriList;
+use crate::otp::from_otp_uri::FromOtpUri;
+use crate::otp::otp_element::OTPElement;
+use color_eyre::eyre::ErrReport;
+
+impl TryFrom<OtpUriList> for Vec<OTPElement> {
+    type Error = ErrReport;
+
+    fn try_from(value: OtpUriList) -> Result<Self, Self::Error> {
+        value
+            .items
+            .into_iter()
+            .map(|e| OTPElement::from_otp_uri(e.as_str()))
+            .collect()
+    }
+}
diff --git a/src/main.rs b/src/main.rs
index 036e00bb..1b83e95c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,6 +1,7 @@
 #![forbid(unsafe_code)]
 use args::CotpArgs;
 use clap::Parser;
+use color_eyre::eyre::eyre;
 use interface::app::AppResult;
 use interface::event::{Event, EventHandler};
 use interface::handler::handle_key_events;
@@ -23,7 +24,7 @@ mod otp;
 mod reading;
 mod utils;
 
-fn init() -> Result<ReadResult, String> {
+fn init() -> color_eyre::Result<ReadResult> {
     match utils::init_app() {
         Ok(first_run) => {
             if first_run {
@@ -36,21 +37,18 @@ fn init() -> Result<ReadResult, String> {
                 };
                 let save_result = database.save_with_pw(&pw);
                 pw.zeroize();
-                match save_result {
-                    Ok((key, salt)) => Ok((database, key, salt.to_vec())),
-                    Err(_) => Err(String::from(
-                        "An error occurred during database overwriting",
-                    )),
-                }
+                save_result.map(|(key, salt)| (database, key, salt.to_vec()))
             } else {
                 get_elements()
             }
         }
-        Err(()) => Err(String::from("An error occurred during database creation")),
+        Err(()) => Err(eyre!("An error occurred during database creation")),
     }
 }
 
 fn main() -> AppResult<()> {
+    color_eyre::install()?;
+
     let cotp_args = CotpArgs::parse();
     let (database, key, salt) = match init() {
         Ok(v) => v,
diff --git a/src/otp/from_otp_uri.rs b/src/otp/from_otp_uri.rs
index 5b6cabf4..bc380206 100644
--- a/src/otp/from_otp_uri.rs
+++ b/src/otp/from_otp_uri.rs
@@ -1,55 +1,54 @@
-use regex::Regex;
+use color_eyre::eyre::ErrReport;
+use url::Url;
 
 use super::{otp_algorithm::OTPAlgorithm, otp_element::OTPElement, otp_type::OTPType};
 
-macro_rules! lazy_regex {
-    ($re:literal $(,)?) => {{
-        static RE: once_cell::sync::OnceCell<regex::Regex> = once_cell::sync::OnceCell::new();
-        RE.get_or_init(|| regex::Regex::new($re).unwrap())
-    }};
-}
-
 pub trait FromOtpUri: Sized {
-    fn from_otp_uri(otp_uri: &str) -> Result<Self, String>;
+    fn from_otp_uri(otp_uri: &str) -> color_eyre::Result<Self>;
 }
 
 impl FromOtpUri for OTPElement {
-    fn from_otp_uri(otp_uri: &str) -> Result<Self, String> {
-        let otp_type = get_match(lazy_regex!(r#"otpauth:[/][/]([a-zA-Z])[/]"#), otp_uri)
-            .map(|r| r.to_uppercase())
-            .unwrap_or_else(|_| "TOTP".to_string());
-        let (issuer, label) = lazy_regex!(r"[a-zA-Z][/](?:(.*):)(.+)\?")
-            .captures(otp_uri)
-            .map(|c| {
-                (
-                    c.get(1).map(|v| v.as_str().to_string()),
-                    c.get(2).map(|v| v.as_str().to_string()),
-                )
-            })
-            .unwrap_or((None, None));
-
-        if issuer.is_none() {
-            return Err(String::from("Issuer not found in OTP Uri"));
-        }
+    fn from_otp_uri(otp_uri: &str) -> color_eyre::Result<Self> {
+        let parsed_uri = Url::parse(otp_uri).map_err(ErrReport::from)?;
 
-        let secret = get_match(lazy_regex!(r#"[?&]secret=(.*?)(?:&|$)"#), otp_uri)?.to_uppercase();
-        let algorithm = get_match(lazy_regex!(r#"[?&]algorithm=(.*?)(?:&|$)"#), otp_uri)
+        let otp_type = parsed_uri
+            .host_str()
             .map(|r| r.to_uppercase())
-            .unwrap_or_else(|_| "SHA1".to_string());
-        let digits = get_match(lazy_regex!(r"[?&]digits=(\d*?)(?:&|$)"), otp_uri)
-            .map(|r| r.parse::<u64>().unwrap())
-            .unwrap_or(6);
-        let period = get_match(lazy_regex!(r"[?&]period=(\d*?)(?:&|$)"), otp_uri)
-            .map(|r| r.parse::<u64>().unwrap())
-            .unwrap_or(30);
-        let counter = get_match(lazy_regex!(r"[?&]counter=(\d*?)(?:&|$)"), otp_uri)
-            .map(|r| Some(r.parse::<u64>().unwrap()))
-            .unwrap_or(None);
+            .unwrap_or_else(|| "TOTP".to_string());
+
+        let (issuer, label) = get_issuer_and_label(&parsed_uri)?;
+
+        let secret = parsed_uri
+            .query_pairs()
+            .find(|(k, _v)| k == "secret")
+            .map(|(_k, v)| v.to_uppercase())
+            .ok_or(ErrReport::msg("Secret not found in OTP Uri"))?;
+
+        let algorithm = parsed_uri
+            .query_pairs()
+            .find(|(k, _v)| k == "algorithm")
+            .map(|(_k, v)| v.to_uppercase())
+            .unwrap_or_else(|| "SHA1".to_string());
+
+        let digits = parsed_uri
+            .query_pairs()
+            .find(|(k, _v)| k == "digits")
+            .map_or(6, |(_k, v)| v.parse::<u64>().unwrap_or(6));
+
+        let period = parsed_uri
+            .query_pairs()
+            .find(|(k, _v)| k == "period")
+            .map_or(30, |(_k, v)| v.parse::<u64>().unwrap_or(30));
+
+        let counter = parsed_uri
+            .query_pairs()
+            .find(|(k, _v)| k == "counter")
+            .and_then(|(_k, v)| v.parse::<u64>().ok());
 
         Ok(OTPElement {
             secret,
-            issuer: issuer.unwrap(), // Safe to wrap due to upper check
-            label: label.unwrap_or_default(),
+            issuer,
+            label,
             digits,
             type_: OTPType::from(otp_type.as_str()),
             algorithm: OTPAlgorithm::from(algorithm.as_str()),
@@ -60,11 +59,43 @@ impl FromOtpUri for OTPElement {
     }
 }
 
-fn get_match(regex: &Regex, value: &str) -> Result<String, String> {
-    let optional_value = regex.captures(value);
-    if optional_value.is_none() {
-        return Err(String::from("No match found"));
+fn get(parsed_uri: &Url) -> color_eyre::Result<Vec<String>> {
+    let first_segment: Vec<String> = parsed_uri
+        .path_segments()
+        .map(|c| c.collect::<Vec<_>>())
+        .ok_or(ErrReport::msg("Failed to collect path segments"))?
+        .first()
+        .ok_or(ErrReport::msg("No path segments found"))?
+        .split(':')
+        .collect::<Vec<_>>()
+        .into_iter()
+        .map(|v| v.to_owned())
+        .collect();
+    Ok(first_segment)
+}
+
+fn get_issuer_and_label(parsed_uri: &Url) -> color_eyre::Result<(String, String)> {
+    // Find the first path segments, OTP Uris should not have others
+    let first_segment = get(parsed_uri)?;
+
+    let first = first_segment
+        .get(0)
+        .and_then(|v| urlencoding::decode(v.as_str()).map(|v| v.into_owned()).ok());
+
+    let second = first_segment
+        .get(1)
+        .and_then(|v| urlencoding::decode(v).map(|v| v.into_owned()).ok());
+
+    match (first, second) {
+        (Some(i), Some(l)) => Ok((i, l)),
+        (Some(l), None) => {
+            let issuer = parsed_uri
+                .query_pairs()
+                .find(|(k, _v)| k == "issuer")
+                .map(|(_k, v)| v.to_string())
+                .unwrap_or_default();
+            Ok((issuer, l))
+        }
+        _ => Err(ErrReport::msg("No label found in OTP uri")),
     }
-    let match_str = optional_value.unwrap().get(1).unwrap();
-    Ok(match_str.as_str().to_owned())
 }
diff --git a/src/otp/migrations/mod.rs b/src/otp/migrations/mod.rs
index 1f86659b..26336808 100644
--- a/src/otp/migrations/mod.rs
+++ b/src/otp/migrations/mod.rs
@@ -2,7 +2,7 @@ use super::otp_element::OTPDatabase;
 
 pub struct Migration<'a> {
     to_version: u16, // Database version which we are migrating on
-    migration_function: &'a dyn Fn(&mut OTPDatabase) -> Result<(), String>, // Function to execute the migration
+    migration_function: &'a dyn Fn(&mut OTPDatabase) -> color_eyre::Result<()>, // Function to execute the migration
 }
 
 pub const MIGRATIONS_LIST: [Migration; 1] = [Migration {
@@ -10,12 +10,12 @@ pub const MIGRATIONS_LIST: [Migration; 1] = [Migration {
     migration_function: &migrate_to_2,
 }];
 
-fn migrate_to_2(database: &mut OTPDatabase) -> Result<(), String> {
+fn migrate_to_2(database: &mut OTPDatabase) -> color_eyre::Result<()> {
     database.version = 2;
     Ok(())
 }
 
-pub fn migrate(database: &mut OTPDatabase) -> Result<(), String> {
+pub fn migrate(database: &mut OTPDatabase) -> color_eyre::Result<()> {
     let mut binding = MIGRATIONS_LIST;
     let migrations = binding.as_mut();
     migrations.sort_unstable_by(|c1, c2| c1.to_version.cmp(&c2.to_version));
diff --git a/src/otp/otp_element.rs b/src/otp/otp_element.rs
index dd67bf67..eb7799db 100644
--- a/src/otp/otp_element.rs
+++ b/src/otp/otp_element.rs
@@ -1,3 +1,4 @@
+use color_eyre::eyre::ErrReport;
 use std::{fs::File, io::Write, vec};
 
 use crate::otp::otp_error::OtpError;
@@ -56,12 +57,12 @@ impl OTPDatabase {
         self.needs_modification
     }
 
-    pub fn save(&mut self, key: &Vec<u8>, salt: &[u8]) -> Result<(), String> {
+    pub fn save(&mut self, key: &Vec<u8>, salt: &[u8]) -> color_eyre::Result<()> {
         self.needs_modification = false;
         migrate(self)?;
         match self.overwrite_database_key(key, salt) {
             Ok(()) => Ok(()),
-            Err(e) => Err(format!("{e:?}")),
+            Err(e) => Err(ErrReport::from(e)),
         }
     }
 
@@ -79,7 +80,7 @@ impl OTPDatabase {
         }
     }
 
-    pub fn save_with_pw(&mut self, password: &str) -> Result<(Vec<u8>, [u8; 16]), String> {
+    pub fn save_with_pw(&mut self, password: &str) -> color_eyre::Result<(Vec<u8>, [u8; 16])> {
         let salt = gen_salt()?;
         let key = argon_derive_key(password.as_bytes(), &salt)?;
         self.save(&key, &salt)?;
@@ -220,11 +221,8 @@ impl OTPElement {
 
 fn get_label(issuer: &str, label: &str) -> String {
     let encoded_label = urlencoding::encode(label);
-    if !issuer.is_empty() {
-        let encoded_issuer = urlencoding::encode(issuer);
-        return format!("{encoded_issuer}:{encoded_label}");
-    }
-    encoded_label.to_string()
+    let encoded_issuer = urlencoding::encode(issuer);
+    format!("{encoded_issuer}:{encoded_label}")
 }
 
 #[cfg(test)]
@@ -236,7 +234,7 @@ mod test {
     use crate::otp::from_otp_uri::FromOtpUri;
 
     #[test]
-    fn test_serialization_otp_uri() {
+    fn test_serialization_otp_uri_full_element() {
         let otp_element = OTPElement {
             secret: String::from("xr5gh44x7bprcqgrdtulafeevt5rxqlbh5wvked22re43dh2d4mapv5g"),
             issuer: String::from("IssuerText"),
@@ -248,7 +246,23 @@ mod test {
             counter: None,
             pin: None,
         };
-        assert_eq!(otp_element.get_otpauth_uri().as_str(), "otpauth://totp/IssuerText:LabelText?secret=xr5gh44x7bprcqgrdtulafeevt5rxqlbh5wvked22re43dh2d4mapv5g&algorithm=SHA1&digits=6&period=30&lock=false");
+        assert_eq!("otpauth://totp/IssuerText:LabelText?secret=xr5gh44x7bprcqgrdtulafeevt5rxqlbh5wvked22re43dh2d4mapv5g&algorithm=SHA1&digits=6&period=30&lock=false",otp_element.get_otpauth_uri().as_str());
+    }
+
+    #[test]
+    fn test_serialization_otp_uri_no_issuer() {
+        let otp_element = OTPElement {
+            secret: String::from("xr5gh44x7bprcqgrdtulafeevt5rxqlbh5wvked22re43dh2d4mapv5g"),
+            issuer: String::from(""),
+            label: String::from("LabelText"),
+            digits: 6,
+            type_: Totp,
+            algorithm: Sha1,
+            period: 30,
+            counter: None,
+            pin: None,
+        };
+        assert_eq!("otpauth://totp/:LabelText?secret=xr5gh44x7bprcqgrdtulafeevt5rxqlbh5wvked22re43dh2d4mapv5g&algorithm=SHA1&digits=6&period=30&lock=false",otp_element.get_otpauth_uri().as_str());
     }
 
     #[test]
@@ -268,4 +282,10 @@ mod test {
 
         assert_eq!(expected, OTPElement::from_otp_uri(otp_uri).unwrap())
     }
+
+    #[test]
+    fn test_deserialization_with_issuer_parameter() {
+        let otp_uri = "otpauth://totp/2Ponies%40Github%20No.1?secret=JBSWY3DPEHPK3PXP&algorithm=SHA1&digits=6&period=30&lock=false&issuer=test";
+        assert_eq!(true, OTPElement::from_otp_uri(otp_uri).is_ok())
+    }
 }
diff --git a/src/reading.rs b/src/reading.rs
index 3b2dd27d..ed233a22 100644
--- a/src/reading.rs
+++ b/src/reading.rs
@@ -1,31 +1,28 @@
 use crate::crypto;
 use crate::otp::otp_element::{OTPDatabase, OTPElement};
 use crate::utils;
+use color_eyre::eyre::{eyre, ErrReport};
 use std::fs::read_to_string;
 use utils::get_db_path;
 use zeroize::Zeroize;
 
 pub type ReadResult = (OTPDatabase, Vec<u8>, Vec<u8>);
 
-pub fn get_elements() -> Result<ReadResult, String> {
+pub fn get_elements() -> color_eyre::Result<ReadResult> {
     let mut pw = utils::password("Password: ", 8);
-    let (elements, key, salt) = match read_from_file(&pw) {
-        Ok((result, key, salt)) => (result, key, salt),
-        Err(e) => return Err(format!("Cannot decrypt existing database: {e}")),
-    };
+    let (elements, key, salt) = read_from_file(&pw)?;
     pw.zeroize();
     Ok((elements, key, salt))
 }
 
-pub fn read_decrypted_text(password: &str) -> Result<(String, Vec<u8>, Vec<u8>), String> {
-    let encrypted_contents =
-        read_to_string(get_db_path()).map_err(|e| format!("Error during file reading: {e:?}"))?;
+pub fn read_decrypted_text(password: &str) -> color_eyre::Result<(String, Vec<u8>, Vec<u8>)> {
+    let encrypted_contents = read_to_string(get_db_path()).map_err(ErrReport::from)?;
     if encrypted_contents.is_empty() {
         return match utils::delete_db() {
-            Ok(_) => Err(String::from(
+            Ok(_) => Err(eyre!(
                 "Your database file was empty, please restart to create a new one.",
             )),
-            Err(_) => Err(String::from(
+            Err(_) => Err(eyre!(
                 "Your database file is empty, please remove it manually and restart.",
             )),
         };
@@ -34,12 +31,12 @@ pub fn read_decrypted_text(password: &str) -> Result<(String, Vec<u8>, Vec<u8>),
     crypto::cryptography::decrypt_string(&encrypted_contents, password)
 }
 
-pub fn read_from_file(password: &str) -> Result<ReadResult, String> {
+pub fn read_from_file(password: &str) -> color_eyre::Result<ReadResult> {
     match read_decrypted_text(password) {
         Ok((mut contents, key, salt)) => {
             let mut database: OTPDatabase = serde_json::from_str(&contents)
                 .or_else(|_| serde_json::from_str::<Vec<OTPElement>>(&contents).map(|r| r.into()))
-                .map_err(|e| format!("Failed to deserialize database: {e:?}"))?;
+                .map_err(ErrReport::from)?;
             contents.zeroize();
             database.sort();
             Ok((database, key, salt))


Test patch: