Switch out anyhow for miette

This commit is contained in:
Nadja Reitzenstein
2022-06-02 17:46:26 +02:00
parent 17fd08b7e5
commit 5f2214abe9
16 changed files with 454 additions and 127 deletions

View File

@ -12,6 +12,7 @@ use std::future::Future;
use std::pin::Pin;
use miette::IntoDiagnostic;
use std::task::{Context, Poll};
use std::time::Duration;
@ -110,11 +111,11 @@ static ROOT_CERTS: Lazy<RootCertStore> = Lazy::new(|| {
store
});
pub fn load(executor: Executor, config: &Config, resources: ResourcesHandle) -> anyhow::Result<()> {
pub fn load(executor: Executor, config: &Config, resources: ResourcesHandle) -> miette::Result<()> {
let span = tracing::info_span!("loading actors");
let _guard = span;
let mqtt_url = Url::parse(config.mqtt_url.as_str())?;
let mqtt_url = Url::parse(config.mqtt_url.as_str()).into_diagnostic()?;
let (transport, default_port) = match mqtt_url.scheme() {
"mqtts" | "ssl" => (
rumqttc::Transport::tls_with_config(
@ -131,12 +132,12 @@ pub fn load(executor: Executor, config: &Config, resources: ResourcesHandle) ->
scheme => {
tracing::error!(%scheme, "MQTT url uses invalid scheme");
anyhow::bail!("invalid config");
miette::bail!("invalid config");
}
};
let host = mqtt_url.host_str().ok_or_else(|| {
tracing::error!("MQTT url must contain a hostname");
anyhow::anyhow!("invalid config")
miette::miette!("invalid config")
})?;
let port = mqtt_url.port().unwrap_or(default_port);
@ -167,7 +168,7 @@ pub fn load(executor: Executor, config: &Config, resources: ResourcesHandle) ->
}
Err(error) => {
tracing::error!(?error, "MQTT connection failed");
anyhow::bail!("mqtt connection failed")
miette::bail!("mqtt connection failed")
}
}

View File

@ -1,4 +1,5 @@
use crate::users::Users;
use miette::{Context, IntoDiagnostic};
use rsasl::error::SessionError;
use rsasl::mechname::Mechname;
use rsasl::property::{AuthId, Password};
@ -127,8 +128,13 @@ impl AuthenticationHandle {
}
}
pub fn start(&self, mechanism: &Mechname) -> anyhow::Result<Session> {
Ok(self.inner.rsasl.server_start(mechanism)?)
pub fn start(&self, mechanism: &Mechname) -> miette::Result<Session> {
Ok(self
.inner
.rsasl
.server_start(mechanism)
.into_diagnostic()
.wrap_err("Failed to start a SASL authentication with the given mechanism")?)
}
pub fn list_available_mechs(&self) -> impl IntoIterator<Item = &Mechname> {

View File

@ -95,7 +95,7 @@ impl Roles {
pub fn is_permitted(&self, user: &UserData, perm: impl AsRef<Permission>) -> bool {
let perm = perm.as_ref();
tracing::debug!(perm=perm.as_str(), "Checking permission");
tracing::debug!(perm = perm.as_str(), "Checking permission");
let mut seen = HashSet::new();
for role_id in user.roles.iter() {
if self.permitted_tally(&mut seen, role_id, perm.as_ref()) {

View File

@ -60,7 +60,7 @@ impl APIServer {
acceptor: TlsAcceptor,
sessionmanager: SessionManager,
authentication: AuthenticationHandle,
) -> anyhow::Result<Self> {
) -> miette::Result<Self> {
let span = tracing::info_span!("binding API listen sockets");
let _guard = span.enter();

View File

@ -1,7 +1,81 @@
use thiserror::Error;
mod raw;
use miette::{Diagnostic, LabeledSpan, Severity, SourceCode};
pub use raw::RawDB;
use std::fmt::{Debug, Display, Formatter};
mod typed;
pub use typed::{Adapter, AlignedAdapter, ArchivedValue, DB};
pub type Error = lmdb::Error;
pub type ErrorO = lmdb::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[repr(transparent)]
#[derive(Debug, Error)]
#[error(transparent)]
pub struct Error(#[from] lmdb::Error);
impl Diagnostic for Error {
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
Some(Box::new(match self.0 {
lmdb::Error::KeyExist => "bffh::db::raw::key_exists".to_string(),
lmdb::Error::NotFound => "bffh::db::raw::not_found".to_string(),
lmdb::Error::PageNotFound => "bffh::db::raw::page_not_found".to_string(),
lmdb::Error::Corrupted => "bffh::db::raw::corrupted".to_string(),
lmdb::Error::Panic => "bffh::db::raw::panic".to_string(),
lmdb::Error::VersionMismatch => "bffh::db::raw::version_mismatch".to_string(),
lmdb::Error::Invalid => "bffh::db::raw::invalid".to_string(),
lmdb::Error::MapFull => "bffh::db::raw::map_full".to_string(),
lmdb::Error::DbsFull => "bffh::db::raw::dbs_full".to_string(),
lmdb::Error::ReadersFull => "bffh::db::raw::readers_full".to_string(),
lmdb::Error::TlsFull => "bffh::db::raw::tls_full".to_string(),
lmdb::Error::TxnFull => "bffh::db::raw::txn_full".to_string(),
lmdb::Error::CursorFull => "bffh::db::raw::cursor_full".to_string(),
lmdb::Error::PageFull => "bffh::db::raw::page_full".to_string(),
lmdb::Error::MapResized => "bffh::db::raw::map_resized".to_string(),
lmdb::Error::Incompatible => "bffh::db::raw::incompatible".to_string(),
lmdb::Error::BadRslot => "bffh::db::raw::bad_rslot".to_string(),
lmdb::Error::BadTxn => "bffh::db::raw::bad_txn".to_string(),
lmdb::Error::BadValSize => "bffh::db::raw::bad_val_size".to_string(),
lmdb::Error::BadDbi => "bffh::db::raw::bad_dbi".to_string(),
lmdb::Error::Other(n) => format!("bffh::db::raw::e{}", n),
}))
}
fn severity(&self) -> Option<Severity> {
Some(Severity::Error)
}
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
match self.0 {
lmdb::Error::KeyExist => Some(Box::new("The provided key already exists in the database")),
lmdb::Error::NotFound => Some(Box::new("The requested key was not found in the database")),
lmdb::Error::PageNotFound => Some(Box::new("The requested page was not found. This usually indicates corruption.")),
lmdb::Error::Corrupted => None,
lmdb::Error::Panic => None,
lmdb::Error::VersionMismatch => None,
lmdb::Error::Invalid => None,
lmdb::Error::MapFull => None,
lmdb::Error::DbsFull => None,
lmdb::Error::ReadersFull => None,
lmdb::Error::TlsFull => None,
lmdb::Error::TxnFull => None,
lmdb::Error::CursorFull => None,
lmdb::Error::PageFull => None,
lmdb::Error::MapResized => None,
lmdb::Error::Incompatible => None,
lmdb::Error::BadRslot => Some(Box::new("This usually indicates that the operation can't complete because an incompatible transaction is still open.")),
lmdb::Error::BadTxn => None,
lmdb::Error::BadValSize => None,
lmdb::Error::BadDbi => None,
lmdb::Error::Other(_) => None,
}
}
fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
None
}
}

View File

@ -1,3 +1,4 @@
use super::Result;
use lmdb::{DatabaseFlags, Environment, RwTransaction, Transaction, WriteFlags};
#[derive(Debug, Clone)]

View File

@ -142,11 +142,11 @@ impl<A: Adapter> DB<A> {
}
pub fn del(&self, txn: &mut RwTransaction, key: &impl AsRef<[u8]>) -> Result<(), db::Error> {
self.db.del::<_, &[u8]>(txn, key, None)
Ok(self.db.del::<_, &[u8]>(txn, key, None)?)
}
pub fn clear(&self, txn: &mut RwTransaction) -> Result<(), db::Error> {
self.db.clear(txn)
Ok(self.db.clear(txn)?)
}
pub fn get_all<'txn, T: Transaction>(

View File

@ -1,74 +1,116 @@
use thiserror::Error;
use crate::db;
use rsasl::error::SessionError;
use std::any::TypeId;
use std::error::Error as StdError;
use std::fmt;
use std::fmt::Display;
use std::io;
type DBError = db::Error;
use crate::resources::state::db::StateDBError;
use backtrace::{Backtrace, BacktraceFmt, PrintFmt};
use miette::{Diagnostic, LabeledSpan, Severity, SourceCode};
#[derive(Debug)]
pub struct TracedError<E: Diagnostic> {
pub inner: E,
pub backtrace: Backtrace,
}
impl<E: Diagnostic> fmt::Display for TracedError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Error: {}", self.inner)?;
let cwd = std::env::current_dir();
let mut print_path =
move |fmt: &mut fmt::Formatter<'_>, path: backtrace::BytesOrWideString<'_>| {
let path = path.into_path_buf();
if let Ok(cwd) = &cwd {
if let Ok(suffix) = path.strip_prefix(cwd) {
return fmt::Display::fmt(&suffix.display(), fmt);
}
}
fmt::Display::fmt(&path.display(), fmt)
};
let mut bf = BacktraceFmt::new(f, PrintFmt::Short, &mut print_path);
bf.add_context()?;
Ok(())
}
}
impl<E: 'static + Diagnostic> StdError for TracedError<E> {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.inner.source()
}
}
impl<E: 'static + Diagnostic> Diagnostic for TracedError<E> {
#[inline(always)]
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
self.inner.code()
}
#[inline(always)]
fn severity(&self) -> Option<Severity> {
self.inner.severity()
}
#[inline(always)]
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
self.inner.help()
}
#[inline(always)]
fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
self.inner.url()
}
#[inline(always)]
fn source_code(&self) -> Option<&dyn SourceCode> {
self.inner.source_code()
}
#[inline(always)]
fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
self.inner.labels()
}
#[inline(always)]
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
self.inner.related()
}
#[inline(always)]
fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
self.inner.diagnostic_source()
}
}
#[derive(Debug, Error, Diagnostic)]
/// Shared error type
pub enum Error {
pub enum BffhError {
#[error("SASL error: {0:?}")]
SASL(SessionError),
IO(io::Error),
Boxed(Box<dyn std::error::Error>),
Capnp(capnp::Error),
DB(DBError),
#[error("IO error: {0}")]
IO(#[from] io::Error),
#[error("IO error: {0}")]
Boxed(#[from] Box<dyn std::error::Error>),
#[error("IO error: {0}")]
Capnp(#[from] capnp::Error),
#[error("IO error: {0}")]
DB(#[from] db::Error),
#[error("You do not have the permission required to do that.")]
Denied,
#[error("State DB operation failed")]
StateDB(#[from] StateDBError),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::SASL(e) => {
write!(f, "SASL Error: {}", e)
}
Error::IO(e) => {
write!(f, "IO Error: {}", e)
}
Error::Boxed(e) => {
write!(f, "{}", e)
}
Error::Capnp(e) => {
write!(f, "Cap'n Proto Error: {}", e)
}
Error::DB(e) => {
write!(f, "DB Error: {:?}", e)
}
Error::Denied => {
write!(f, "You do not have the permission required to do that.")
}
}
impl From<SessionError> for BffhError {
fn from(e: SessionError) -> Self {
Self::SASL(e)
}
}
impl From<SessionError> for Error {
fn from(e: SessionError) -> Error {
Error::SASL(e)
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Error {
Error::IO(e)
}
}
impl From<Box<dyn std::error::Error>> for Error {
fn from(e: Box<dyn std::error::Error>) -> Error {
Error::Boxed(e)
}
}
impl From<capnp::Error> for Error {
fn from(e: capnp::Error) -> Error {
Error::Capnp(e)
}
}
impl From<DBError> for Error {
fn from(e: DBError) -> Error {
Error::DB(e)
}
}
pub type Result<T> = std::result::Result<T, Error>;
pub type Result<T> = std::result::Result<T, BffhError>;

View File

@ -42,9 +42,8 @@ mod tls;
use std::sync::Arc;
use anyhow::Context;
use futures_util::StreamExt;
use miette::{Context, IntoDiagnostic, Report};
use once_cell::sync::OnceCell;
use crate::audit::AuditLog;
@ -75,7 +74,7 @@ pub struct Diflouroborane {
pub static RESOURCES: OnceCell<ResourcesHandle> = OnceCell::new();
impl Diflouroborane {
pub fn new(config: Config) -> anyhow::Result<Self> {
pub fn new(config: Config) -> miette::Result<Self> {
logging::init(&config.logging);
tracing::info!(version = env::VERSION, "Starting BFFH");
@ -84,15 +83,16 @@ impl Diflouroborane {
let executor = Executor::new();
let env = StateDB::open_env(&config.db_path)
.context("Failed to create state DB env. Does the parent directory for `db_path` exist?")?;
let statedb =
StateDB::create_with_env(env.clone()).context("Failed to open state DB file")?;
let env = StateDB::open_env(&config.db_path)?;
let users = Users::new(env.clone()).context("Failed to open users DB file")?;
let statedb = StateDB::create_with_env(env.clone())?;
let users = Users::new(env.clone())?;
let roles = Roles::new(config.roles.clone());
let _audit_log = AuditLog::new(&config).context("Failed to initialize audit log")?;
let _audit_log = AuditLog::new(&config)
.into_diagnostic()
.wrap_err("Failed to initialize audit log")?;
let resources = ResourcesHandle::new(config.machines.iter().map(|(id, desc)| {
Resource::new(Arc::new(resources::Inner::new(
@ -113,13 +113,15 @@ impl Diflouroborane {
})
}
pub fn run(&mut self) -> anyhow::Result<()> {
pub fn run(&mut self) -> miette::Result<()> {
let mut signals = signal_hook_async_std::Signals::new(&[SIGINT, SIGQUIT, SIGTERM])
.context("Failed to construct signal handler")?;
.into_diagnostic()
.wrap_err("Failed to construct signal handler")?;
actors::load(self.executor.clone(), &self.config, self.resources.clone())?;
let tlsconfig = TlsConfig::new(self.config.tlskeylog.as_ref(), !self.config.is_quiet())?;
let tlsconfig = TlsConfig::new(self.config.tlskeylog.as_ref(), !self.config.is_quiet())
.into_diagnostic()?;
let acceptor = tlsconfig.make_tls_acceptor(&self.config.tlsconfig)?;
let sessionmanager = SessionManager::new(self.users.clone(), self.roles.clone());

View File

@ -1,6 +1,12 @@
use thiserror::Error;
use crate::db;
use crate::db::{AlignedAdapter, ArchivedValue, RawDB, DB};
use lmdb::{DatabaseFlags, Environment, EnvironmentFlags, Transaction, WriteFlags};
use miette::{Diagnostic, LabeledSpan, Severity, SourceCode};
use std::any::TypeId;
use std::error::Error;
use std::fmt::{Debug, Display, Formatter};
use std::{path::Path, sync::Arc};
use crate::resources::state::State;
@ -11,8 +17,24 @@ pub struct StateDB {
db: DB<AlignedAdapter<State>>,
}
#[derive(Debug, Error, Diagnostic)]
pub enum StateDBError {
#[error("opening the state db environment failed")]
#[diagnostic(
code(bffh::db::state::open_env),
help("does the parent directory for state_db exist?")
)]
OpenEnv(#[source] db::Error),
#[error("opening the state db failed")]
#[diagnostic(code(bffh::db::state::open))]
Open(#[source] db::Error),
#[error("creating the state db failed")]
#[diagnostic(code(bffh::db::state::create))]
Create(#[source] db::Error),
}
impl StateDB {
pub fn open_env<P: AsRef<Path>>(path: P) -> lmdb::Result<Arc<Environment>> {
pub fn open_env<P: AsRef<Path>>(path: P) -> Result<Arc<Environment>, StateDBError> {
Environment::new()
.set_flags(
EnvironmentFlags::WRITE_MAP
@ -23,6 +45,7 @@ impl StateDB {
.set_max_dbs(8)
.open(path.as_ref())
.map(Arc::new)
.map_err(|e| StateDBError::OpenEnv(e.into()))
}
fn new(env: Arc<Environment>, db: RawDB) -> Self {
@ -30,30 +53,32 @@ impl StateDB {
Self { env, db }
}
pub fn open_with_env(env: Arc<Environment>) -> lmdb::Result<Self> {
let db = unsafe { RawDB::open(&env, Some("state"))? };
pub fn open_with_env(env: Arc<Environment>) -> Result<Self, StateDBError> {
let db = unsafe { RawDB::open(&env, Some("state")) };
let db = db.map_err(|e| StateDBError::Open(e.into()))?;
Ok(Self::new(env, db))
}
pub fn open<P: AsRef<Path>>(path: P) -> lmdb::Result<Self> {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, StateDBError> {
let env = Self::open_env(path)?;
Self::open_with_env(env)
}
pub fn create_with_env(env: Arc<Environment>) -> lmdb::Result<Self> {
pub fn create_with_env(env: Arc<Environment>) -> Result<Self, StateDBError> {
let flags = DatabaseFlags::empty();
let db = unsafe { RawDB::create(&env, Some("state"), flags)? };
let db = unsafe { RawDB::create(&env, Some("state"), flags) };
let db = db.map_err(|e| StateDBError::Create(e.into()))?;
Ok(Self::new(env, db))
}
pub fn create<P: AsRef<Path>>(path: P) -> lmdb::Result<Self> {
pub fn create<P: AsRef<Path>>(path: P) -> Result<Self, StateDBError> {
let env = Self::open_env(path)?;
Self::create_with_env(env)
}
pub fn begin_ro_txn(&self) -> Result<impl Transaction + '_, db::Error> {
self.env.begin_ro_txn()
self.env.begin_ro_txn().map_err(db::Error::from)
}
pub fn get(&self, key: impl AsRef<[u8]>) -> Result<Option<ArchivedValue<State>>, db::Error> {
@ -72,7 +97,7 @@ impl StateDB {
let mut txn = self.env.begin_rw_txn()?;
let flags = WriteFlags::empty();
self.db.put(&mut txn, key, val, flags)?;
txn.commit()
Ok(txn.commit()?)
}
}

View File

@ -6,6 +6,7 @@ use std::sync::Arc;
use crate::capnp::TlsListen;
use futures_rustls::TlsAcceptor;
use miette::IntoDiagnostic;
use rustls::version::{TLS12, TLS13};
use rustls::{Certificate, PrivateKey, ServerConfig, SupportedCipherSuite};
use tracing::Level;
@ -74,26 +75,27 @@ impl TlsConfig {
}
}
pub fn make_tls_acceptor(&self, config: &TlsListen) -> anyhow::Result<TlsAcceptor> {
pub fn make_tls_acceptor(&self, config: &TlsListen) -> miette::Result<TlsAcceptor> {
let span = tracing::debug_span!("tls");
let _guard = span.enter();
tracing::debug!(path = %config.certfile.as_path().display(), "reading certificates");
let mut certfp = BufReader::new(File::open(config.certfile.as_path())?);
let certs = rustls_pemfile::certs(&mut certfp)?
let mut certfp = BufReader::new(File::open(config.certfile.as_path()).into_diagnostic()?);
let certs = rustls_pemfile::certs(&mut certfp)
.into_diagnostic()?
.into_iter()
.map(Certificate)
.collect();
tracing::debug!(path = %config.keyfile.as_path().display(), "reading private key");
let mut keyfp = BufReader::new(File::open(config.keyfile.as_path())?);
let key = match rustls_pemfile::read_one(&mut keyfp)? {
let mut keyfp = BufReader::new(File::open(config.keyfile.as_path()).into_diagnostic()?);
let key = match rustls_pemfile::read_one(&mut keyfp).into_diagnostic()? {
Some(rustls_pemfile::Item::PKCS8Key(key) | rustls_pemfile::Item::RSAKey(key)) => {
PrivateKey(key)
}
_ => {
tracing::error!("private key file invalid");
anyhow::bail!("private key file must contain a PEM-encoded private key")
miette::bail!("private key file must contain a PEM-encoded private key")
}
};
@ -105,15 +107,17 @@ impl TlsConfig {
match min.as_str() {
"tls12" => tls_builder.with_protocol_versions(&[&TLS12]),
"tls13" => tls_builder.with_protocol_versions(&[&TLS13]),
x => anyhow::bail!("TLS version {} is invalid", x),
x => miette::bail!("TLS version {} is invalid", x),
}
} else {
tls_builder.with_safe_default_protocol_versions()
}?;
}
.into_diagnostic()?;
let mut tls_config = tls_builder
.with_no_client_auth()
.with_single_cert(certs, key)?;
.with_single_cert(certs, key)
.into_diagnostic()?;
if let Some(keylog) = &self.keylog {
tls_config.key_log = keylog.clone();

View File

@ -2,7 +2,7 @@ use lmdb::{DatabaseFlags, Environment, RwTransaction, Transaction, WriteFlags};
use rkyv::Infallible;
use std::collections::HashMap;
use anyhow::Context;
use miette::{Context, IntoDiagnostic};
use std::sync::Arc;
use crate::db;
@ -28,9 +28,11 @@ pub struct User {
}
impl User {
pub fn check_password(&self, pwd: &[u8]) -> anyhow::Result<bool> {
pub fn check_password(&self, pwd: &[u8]) -> miette::Result<bool> {
if let Some(ref encoded) = self.userdata.passwd {
argon2::verify_encoded(encoded, pwd).context("Stored password is an invalid string")
argon2::verify_encoded(encoded, pwd)
.into_diagnostic()
.wrap_err("Stored password is an invalid string")
} else {
Ok(false)
}
@ -109,7 +111,7 @@ impl UserDB {
// TODO: Make an userdb-specific Transaction newtype to make this safe
pub unsafe fn get_rw_txn(&self) -> Result<RwTransaction, db::Error> {
// The returned transaction is only valid for *this* environment.
self.env.begin_rw_txn()
Ok(self.env.begin_rw_txn()?)
}
pub unsafe fn new(env: Arc<Environment>, db: RawDB) -> Self {

View File

@ -1,10 +1,10 @@
use anyhow::Context;
use lmdb::{Environment, Transaction};
use once_cell::sync::OnceCell;
use rkyv::{Archive, Deserialize, Infallible, Serialize};
use std::collections::HashMap;
use std::fmt::{Display, Formatter, Write};
use miette::{Context, IntoDiagnostic};
use std::path::Path;
use std::sync::Arc;
@ -65,7 +65,7 @@ pub struct Users {
}
impl Users {
pub fn new(env: Arc<Environment>) -> anyhow::Result<Self> {
pub fn new(env: Arc<Environment>) -> miette::Result<Self> {
let span = tracing::debug_span!("users", ?env, "Creating Users handle");
let _guard = span.enter();
@ -74,7 +74,7 @@ impl Users {
tracing::debug!("Global resource not yet initialized, initializing…");
unsafe { UserDB::create(env) }
})
.context("Failed to open userdb")?;
.wrap_err("Failed to open userdb")?;
Ok(Self { userdb })
}
@ -90,19 +90,19 @@ impl Users {
})
}
pub fn put_user(&self, uid: &str, user: &db::User) -> Result<(), lmdb::Error> {
pub fn put_user(&self, uid: &str, user: &db::User) -> Result<(), crate::db::Error> {
tracing::trace!(uid, ?user, "Updating user");
self.userdb.put(uid, user)
}
pub fn del_user(&self, uid: &str) -> Result<(), lmdb::Error> {
pub fn del_user(&self, uid: &str) -> Result<(), crate::db::Error> {
tracing::trace!(uid, "Deleting user");
self.userdb.delete(uid)
}
pub fn load_file<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
let f = std::fs::read(path)?;
let map: HashMap<String, UserData> = toml::from_slice(&f)?;
pub fn load_file<P: AsRef<Path>>(&self, path: P) -> miette::Result<()> {
let f = std::fs::read(path).into_diagnostic()?;
let map: HashMap<String, UserData> = toml::from_slice(&f).into_diagnostic()?;
let mut txn = unsafe { self.userdb.get_rw_txn()? };
@ -132,7 +132,7 @@ impl Users {
}
}
txn.commit()?;
txn.commit().map_err(crate::db::Error::from)?;
Ok(())
}
}