feat(models): update models to use error-stack types

This makes a bunch of errors more meaningful and also helps with typing
This commit is contained in:
Suya1671 2025-06-18 15:20:31 +02:00
parent 518a6864b7
commit d58c6a69d6
No known key found for this signature in database
12 changed files with 177 additions and 122 deletions

22
Cargo.lock generated
View file

@ -574,6 +574,26 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "derive_more"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
dependencies = [
"derive_more-impl",
]
[[package]]
name = "derive_more-impl"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
]
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.10.7" version = "0.10.7"
@ -2409,10 +2429,10 @@ version = "0.1.0"
dependencies = [ dependencies = [
"axum", "axum",
"clap", "clap",
"derive_more",
"displaydoc", "displaydoc",
"dotenvy 0.15.7 (git+https://github.com/allan2/dotenvy)", "dotenvy 0.15.7 (git+https://github.com/allan2/dotenvy)",
"error-stack", "error-stack",
"eyre",
"http-body-util", "http-body-util",
"libsqlite3-sys", "libsqlite3-sys",
"menv", "menv",

View file

@ -16,7 +16,6 @@ error-stack = { version = "0.5.0", features = [
"serde", "serde",
"spantrace", "spantrace",
] } ] }
eyre = "0.6.12"
http-body-util = "0.1.3" http-body-util = "0.1.3"
menv = "0.2.7" menv = "0.2.7"
oauth2 = "5.0.0" oauth2 = "5.0.0"
@ -42,6 +41,7 @@ dotenvy = { git = "https://github.com/allan2/dotenvy", features = ["macros"] }
url = "2.5.4" url = "2.5.4"
serde_json = "1.0.140" serde_json = "1.0.140"
tower-http = { version = "0.6.6", features = ["trace"] } tower-http = { version = "0.6.6", features = ["trace"] }
derive_more = { version = "2.0.1", features = ["from"] }
[features] [features]
encrypt = ["libsqlite3-sys/bundled-sqlcipher"] encrypt = ["libsqlite3-sys/bundled-sqlcipher"]

View file

@ -1,6 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use error_stack::{Result, ResultExt, report}; use error_stack::{Result, ResultExt};
use slack_morphism::prelude::*; use slack_morphism::prelude::*;
use tracing::{debug, info, trace}; use tracing::{debug, info, trace};
@ -125,7 +125,7 @@ impl Member {
member::Id::new(member_id) member::Id::new(member_id)
.validate_by_system(system.id, &user_state.db) .validate_by_system(system.id, &user_state.db)
.await .await
.ok() .change_context(CommandError::Sqlx)?
}; };
debug!(target_member_id = ?new_active_member_id, "Changing active member"); debug!(target_member_id = ?new_active_member_id, "Changing active member");
@ -143,13 +143,13 @@ impl Member {
info!("Successfully switched to base account"); info!("Successfully switched to base account");
"Switched to base account".into() "Switched to base account".into()
} }
Err(ChangeActiveMemberError::MemberNotFound) => { Err(e) => match e.current_context() {
debug!("Requested member not found in system"); ChangeActiveMemberError::MemberNotFound => {
"The member you gave doesn't exist!".into() debug!("Requested member not found in system");
} "The member you gave doesn't exist!".into()
Err(ChangeActiveMemberError::Sqlx(err)) => { }
return Err(report!(err).change_context(CommandError::Sqlx)); ChangeActiveMemberError::Sqlx => return Err(e.change_context(CommandError::Sqlx)),
} },
}; };
Ok(SlackCommandEventResponse::new( Ok(SlackCommandEventResponse::new(

View file

@ -3,14 +3,15 @@ use std::sync::Arc;
mod member; mod member;
mod system; mod system;
mod trigger; mod trigger;
use axum::{Extension, Json}; use axum::{Extension, Json};
use clap::{Parser, error::ErrorKind}; use clap::{Parser, error::ErrorKind};
use error_stack::ResultExt; use error_stack::ResultExt;
use member::Member;
use slack_morphism::prelude::*; use slack_morphism::prelude::*;
use system::System;
use tracing::{Level, debug, error, trace}; use tracing::{Level, debug, error, trace};
use member::Member;
use system::System;
use trigger::Trigger; use trigger::Trigger;
use crate::fields; use crate::fields;

View file

@ -199,9 +199,10 @@ impl Trigger {
let member_id = member::Id::new(member_id); let member_id = member::Id::new(member_id);
// Validate the member belongs to the user's system // Validate the member belongs to the user's system
let Ok(member_id) = member_id let Some(member_id) = member_id
.validate_by_system(system_id, &user_state.db) .validate_by_system(system_id, &user_state.db)
.await .await
.change_context(CommandError::Sqlx)?
else { else {
debug!("Member not found"); debug!("Member not found");
return Ok(SlackCommandEventResponse::new( return Ok(SlackCommandEventResponse::new(

View file

@ -4,6 +4,7 @@ use std::error::Error;
use std::sync::Arc; use std::sync::Arc;
use axum::Extension; use axum::Extension;
use error_stack::ResultExt;
use member::{create_member, edit_member}; use member::{create_member, edit_member};
use slack_morphism::prelude::*; use slack_morphism::prelude::*;
use tracing::{debug, error}; use tracing::{debug, error};
@ -114,7 +115,8 @@ async fn handle_modal_view(
return Ok(()); return Ok(());
}; };
let Ok(trusted_member_id) = member_id.validate_by_user(&user_id, &user_state.db).await let Some(trusted_member_id) =
member_id.validate_by_user(&user_id, &user_state.db).await?
else { else {
error!( error!(
id, id,
@ -137,7 +139,8 @@ async fn handle_modal_view(
.map(models::member::Id::new) .map(models::member::Id::new)
.expect("Failed to parse member id from external id"); .expect("Failed to parse member id from external id");
let Ok(trusted_member_id) = member_id.validate_by_user(&user_id, &user_state.db).await let Some(trusted_member_id) =
member_id.validate_by_user(&user_id, &user_state.db).await?
else { else {
error!( error!(
id, id,

View file

@ -1,9 +1,9 @@
use crate::id; use crate::id;
use super::{Trustability, Trusted, Untrusted, member, system}; use super::{Trustability, Trusted, Untrusted, member, system};
use error_stack::ResultExt; use error_stack::{Result, ResultExt};
use slack_morphism::prelude::*; use slack_morphism::prelude::*;
use sqlx::{SqlitePool, prelude::*}; use sqlx::{SqlitePool, prelude::*, sqlite::SqliteQueryResult};
use tracing::{debug, warn}; use tracing::{debug, warn};
id!( id!(
@ -26,30 +26,24 @@ impl Id<Untrusted> {
self, self,
system_id: system::Id<Trusted>, system_id: system::Id<Trusted>,
db: &SqlitePool, db: &SqlitePool,
) -> Result<Id<Trusted>, Self> { ) -> Result<Option<Id<Trusted>>, sqlx::Error> {
let exists = sqlx::query!( sqlx::query!(
"SELECT EXISTS(SELECT 1 FROM aliases WHERE id = $1 AND system_id = $2) AS 'exists: bool'", "SELECT
id as 'id: Id<Trusted>'
FROM aliases
WHERE id = $1 AND system_id = $2",
self.id, self.id,
system_id.id system_id.id
) )
.fetch_one(db) .fetch_optional(db)
.await .await
.ok() .map(|res| res.map(|res| res.id))
.is_some_and(|record| record.exists); .attach_printable("Failed to fetch alias id from database")
if exists {
Ok(Id {
id: self.id,
trusted: std::marker::PhantomData,
})
} else {
Err(self)
}
} }
} }
impl Id<Trusted> { impl Id<Trusted> {
pub async fn delete(self, db_pool: &SqlitePool) -> Result<(), sqlx::Error> { pub async fn delete(self, db_pool: &SqlitePool) -> Result<SqliteQueryResult, sqlx::Error> {
sqlx::query!( sqlx::query!(
r#" r#"
DELETE FROM aliases DELETE FROM aliases
@ -59,7 +53,7 @@ impl Id<Trusted> {
) )
.execute(db_pool) .execute(db_pool)
.await .await
.map(|_| ()) .attach_printable("Failed to delete alias from database")
} }
} }
@ -99,6 +93,7 @@ impl Alias {
) )
.fetch_optional(db) .fetch_optional(db)
.await .await
.attach_printable("Failed to fetch alias from database")
} }
pub async fn fetch_by_system_id( pub async fn fetch_by_system_id(
@ -122,12 +117,13 @@ impl Alias {
) )
.fetch_all(db) .fetch_all(db)
.await .await
.attach_printable("Failed to fetch aliases from database")
} }
pub async fn fetch_by_member_id( pub async fn fetch_by_member_id(
db: &SqlitePool, db: &SqlitePool,
member_id: member::Id<Trusted>, member_id: member::Id<Trusted>,
) -> error_stack::Result<Vec<Self>, Error> { ) -> error_stack::Result<Vec<Self>, sqlx::Error> {
sqlx::query_as!( sqlx::query_as!(
Self, Self,
r#" r#"
@ -144,7 +140,7 @@ impl Alias {
) )
.fetch_all(db) .fetch_all(db)
.await .await
.change_context(Error::Sqlx) .attach_printable("Failed to fetch aliases from database")
} }
} }
@ -188,7 +184,7 @@ impl View {
system_id: system::Id<Trusted>, system_id: system::Id<Trusted>,
member_id: member::Id<Trusted>, member_id: member::Id<Trusted>,
db_pool: &SqlitePool, db_pool: &SqlitePool,
) -> error_stack::Result<Id<Trusted>, Error> { ) -> Result<Id<Trusted>, sqlx::Error> {
debug!( debug!(
"Adding alias for {} (Member ID {}) to database", "Adding alias for {} (Member ID {}) to database",
system_id, member_id system_id, member_id
@ -207,7 +203,6 @@ impl View {
.fetch_one(db_pool) .fetch_one(db_pool)
.await .await
.attach_printable("Error adding alias to database") .attach_printable("Error adding alias to database")
.change_context(Error::Sqlx)
.map(|row| Id { .map(|row| Id {
id: row.id, id: row.id,
trusted: std::marker::PhantomData, trusted: std::marker::PhantomData,
@ -219,7 +214,7 @@ impl View {
&self, &self,
alias_id: Id<Trusted>, alias_id: Id<Trusted>,
db: &SqlitePool, db: &SqlitePool,
) -> error_stack::Result<(), Error> { ) -> error_stack::Result<SqliteQueryResult, sqlx::Error> {
sqlx::query!( sqlx::query!(
r#" r#"
UPDATE aliases UPDATE aliases
@ -232,8 +227,6 @@ impl View {
.execute(db) .execute(db)
.await .await
.attach_printable("Error updating member alias in database") .attach_printable("Error updating member alias in database")
.change_context(Error::Sqlx)
.map(|_| ())
} }
pub fn create_add_view(self, member_id: member::Id<Trusted>) -> SlackView { pub fn create_add_view(self, member_id: member::Id<Trusted>) -> SlackView {

View file

@ -1,4 +1,4 @@
use error_stack::ResultExt; use error_stack::{Result, ResultExt};
use slack_morphism::prelude::*; use slack_morphism::prelude::*;
use sqlx::{SqlitePool, prelude::*, sqlite::SqliteQueryResult}; use sqlx::{SqlitePool, prelude::*, sqlite::SqliteQueryResult};
use tracing::{debug, warn}; use tracing::{debug, warn};
@ -7,14 +7,14 @@ use crate::id;
use super::{ use super::{
Trusted, Untrusted, system, Trusted, Untrusted, system,
trigger::{self, Trigger, Type}, trigger::{Trigger, Type},
user, user,
}; };
#[derive(thiserror::Error, displaydoc::Display, Debug)] #[derive(thiserror::Error, displaydoc::Display, Debug)]
pub enum Error { pub enum Error {
/// Error while calling the database /// Error while calling the database
Sqlx, Database,
/// A field was missing from the view /// A field was missing from the view
MissingField(String), MissingField(String),
} }
@ -39,67 +39,95 @@ impl Id<Untrusted> {
self, self,
system_id: system::Id<Trusted>, system_id: system::Id<Trusted>,
db: &SqlitePool, db: &SqlitePool,
) -> Result<Id<Trusted>, Self> { ) -> Result<Option<Id<Trusted>>, sqlx::Error> {
let exists = sqlx::query!( sqlx::query!(
"SELECT EXISTS(SELECT 1 FROM members WHERE id = $1 AND system_id = $2) AS 'exists: bool'", "SELECT
id as 'id: Id<Trusted>'
FROM members
WHERE id = $1 AND system_id = $2",
self.id, self.id,
system_id.id system_id.id
) )
.fetch_one(db) .fetch_optional(db)
.await .await
.ok() .attach_printable("Failed to validate member by system")
.is_some_and(|record| record.exists); .map(|res| res.map(|res| res.id))
if exists {
Ok(Id {
id: self.id,
trusted: std::marker::PhantomData,
})
} else {
Err(self)
}
} }
pub async fn validate_by_user( pub async fn validate_by_user(
self, self,
user_id: &user::Id<Trusted>, user_id: &user::Id<Trusted>,
db: &SqlitePool, db: &SqlitePool,
) -> Result<Id<Trusted>, Self> { ) -> Result<Option<Id<Trusted>>, sqlx::Error> {
let exists = sqlx::query!( sqlx::query!(
"SELECT EXISTS( "
SELECT 1 SELECT
members.id as 'id: Id<Trusted>'
FROM members FROM members
JOIN systems ON members.system_id = systems.id JOIN systems ON members.system_id = systems.id
WHERE members.id = $1 AND systems.owner_id = $2 WHERE members.id = $1 AND systems.owner_id = $2
) AS 'exists: bool'", ",
self.id, self.id,
user_id user_id
) )
.fetch_one(db) .fetch_optional(db)
.await .await
.ok() .attach_printable("Failed to validate member by user")
.is_some_and(|record| record.exists); .map(|res| res.map(|res| res.id))
}
if exists { pub async fn fetch_by_alias(
Ok(Id { alias: &str,
id: self.id, system_id: system::Id<Trusted>,
trusted: std::marker::PhantomData, db: &SqlitePool,
}) ) -> Result<Option<Id<Trusted>>, sqlx::Error> {
} else { sqlx::query!(
Err(self) "SELECT
} member_id AS 'id: Id<Trusted>'
FROM aliases
WHERE alias = $1 AND system_id = $2",
alias,
system_id
)
.fetch_optional(db)
.await
.attach_printable("Failed to fetch member id by alias")
.map(|res| res.map(|res| res.id))
} }
} }
impl Id<Trusted> { impl Id<Trusted> {
pub async fn fetch_triggers( pub async fn fetch_triggers(self, db: &SqlitePool) -> Result<Vec<Trigger>, sqlx::Error> {
self,
db: &SqlitePool,
) -> error_stack::Result<Vec<Trigger>, trigger::Error> {
Trigger::fetch_by_member_id(db, self).await Trigger::fetch_by_member_id(db, self).await
} }
} }
#[derive(Debug, derive_more::From)]
/// An untrusted member reference from an external source
pub enum MemberRef {
Id(Id<Untrusted>),
/// We were given a [`super::Alias`]
Alias(String),
}
impl MemberRef {
pub async fn validate_by_system(
&self,
system_id: system::Id<Trusted>,
db: &SqlitePool,
) -> Result<Option<Id<Trusted>>, sqlx::Error> {
match self {
MemberRef::Id(id) => id
.validate_by_system(system_id, db)
.await
.attach_printable("Failed to validate member reference via id and system"),
MemberRef::Alias(alias) => Id::fetch_by_alias(alias, system_id, db)
.await
.attach_printable("Failed to validate member reference via alias and system"),
}
}
}
// TO-DO: move SQL to rust struct // TO-DO: move SQL to rust struct
#[derive(FromRow, Debug)] #[derive(FromRow, Debug)]
#[allow(dead_code)] #[allow(dead_code)]
@ -149,6 +177,7 @@ impl Member {
) )
.fetch_optional(db) .fetch_optional(db)
.await .await
.attach_printable("Failed to fetch member by id and trust by system")
} }
/// Fetch a member by their id /// Fetch a member by their id
@ -177,6 +206,7 @@ impl Member {
) )
.fetch_optional(db) .fetch_optional(db)
.await .await
.attach_printable("Failed to fetch member by id")
} }
} }
@ -315,7 +345,7 @@ impl View {
&self, &self,
system_id: system::Id<Trusted>, system_id: system::Id<Trusted>,
db: &SqlitePool, db: &SqlitePool,
) -> error_stack::Result<i64, Error> { ) -> error_stack::Result<i64, sqlx::Error> {
debug!("Adding member {} to database", self.display_name); debug!("Adding member {} to database", self.display_name);
sqlx::query!(" sqlx::query!("
INSERT INTO members (full_name, display_name, profile_picture_url, title, pronouns, name_pronunciation, name_recording_url, system_id) INSERT INTO members (full_name, display_name, profile_picture_url, title, pronouns, name_pronunciation, name_recording_url, system_id)
@ -334,7 +364,6 @@ impl View {
.fetch_one(db) .fetch_one(db)
.await .await
.attach_printable("Error adding member to database") .attach_printable("Error adding member to database")
.change_context(Error::Sqlx)
.map(|row| row.id) .map(|row| row.id)
} }
@ -345,7 +374,7 @@ impl View {
&self, &self,
member_id: Id<Trusted>, member_id: Id<Trusted>,
db: &SqlitePool, db: &SqlitePool,
) -> error_stack::Result<Option<SqliteQueryResult>, Error> { ) -> error_stack::Result<SqliteQueryResult, sqlx::Error> {
sqlx::query!(" sqlx::query!("
UPDATE members UPDATE members
SET full_name = $1, display_name = $2, profile_picture_url = $3, title = $4, pronouns = $5, name_pronunciation = $6, name_recording_url = $7 SET full_name = $1, display_name = $2, profile_picture_url = $3, title = $4, pronouns = $5, name_pronunciation = $6, name_recording_url = $7
@ -361,15 +390,13 @@ impl View {
member_id, member_id,
).execute(db).await ).execute(db).await
.attach_printable("Error editing member in database") .attach_printable("Error editing member in database")
.change_context(Error::Sqlx)
.map(Some)
} }
} }
impl TryFrom<SlackViewState> for View { impl TryFrom<SlackViewState> for View {
type Error = Error; type Error = Error;
fn try_from(value: SlackViewState) -> Result<Self, Self::Error> { fn try_from(value: SlackViewState) -> std::result::Result<Self, Self::Error> {
let mut view = Self::default(); let mut view = Self::default();
for (_id, values) in value.values { for (_id, values) in value.values {
for (id, content) in values { for (id, content) in values {

View file

@ -1,8 +1,9 @@
use crate::id; use crate::id;
use super::{Trusted, member}; use super::{Trusted, member};
use error_stack::{Result, ResultExt};
use slack_morphism::SlackTs; use slack_morphism::SlackTs;
use sqlx::{SqlitePool, prelude::*}; use sqlx::{SqlitePool, prelude::*, sqlite::SqliteQueryResult};
id!( id!(
/// You cannot create a message id, as it is internal generated-only. /// You cannot create a message id, as it is internal generated-only.
@ -28,7 +29,7 @@ impl MessageLog {
pub async fn delete_by_message_id( pub async fn delete_by_message_id(
message_id: String, message_id: String,
db: &SqlitePool, db: &SqlitePool,
) -> Result<(), sqlx::Error> { ) -> Result<SqliteQueryResult, sqlx::Error> {
sqlx::query!( sqlx::query!(
r#" r#"
DELETE FROM message_logs DELETE FROM message_logs
@ -38,7 +39,7 @@ impl MessageLog {
) )
.execute(db) .execute(db)
.await .await
.map(|_| ()) .attach_printable("Failed to delete message log")
} }
/// Fetches a message log by the slack message ID. /// Fetches a message log by the slack message ID.
@ -62,6 +63,7 @@ impl MessageLog {
) )
.fetch_optional(db) .fetch_optional(db)
.await .await
.attach_printable("Failed to fetch message log")
} }
/// Fetches all message logs by the member ID. /// Fetches all message logs by the member ID.
@ -86,6 +88,7 @@ impl MessageLog {
) )
.fetch_all(db) .fetch_all(db)
.await .await
.attach_printable("Failed to fetch message logs")
} }
pub async fn insert( pub async fn insert(
@ -108,5 +111,6 @@ impl MessageLog {
) )
.fetch_one(db) .fetch_one(db)
.await .await
.attach_printable("Failed to insert message log")
} }
} }

View file

@ -33,7 +33,7 @@ macro_rules! id {
($(#[$attr:meta])* => $name:ident) => { ($(#[$attr:meta])* => $name:ident) => {
#[derive(::sqlx::Type, Debug, PartialEq, Eq, Clone, Copy)] #[derive(::sqlx::Type, Debug, PartialEq, Eq, Clone, Copy)]
$(#[$attr])* $(#[$attr])*
pub struct Id<T> { pub struct Id<T: $crate::models::Trustability> {
pub id: i64, pub id: i64,
trusted: ::std::marker::PhantomData<T>, trusted: ::std::marker::PhantomData<T>,
} }
@ -46,7 +46,7 @@ macro_rules! id {
fn encode_by_ref( fn encode_by_ref(
&self, &self,
buf: &mut <DB as ::sqlx::Database>::ArgumentBuffer<'q>, buf: &mut <DB as ::sqlx::Database>::ArgumentBuffer<'q>,
) -> Result<::sqlx::encode::IsNull, ::sqlx::error::BoxDynError> { ) -> ::std::result::Result<::sqlx::encode::IsNull, ::sqlx::error::BoxDynError> {
<i64 as ::sqlx::Encode<'_, DB>>::encode_by_ref(&self.id, buf) <i64 as ::sqlx::Encode<'_, DB>>::encode_by_ref(&self.id, buf)
} }
@ -62,7 +62,7 @@ macro_rules! id {
{ {
fn decode( fn decode(
value: <DB as ::sqlx::Database>::ValueRef<'q>, value: <DB as ::sqlx::Database>::ValueRef<'q>,
) -> Result<Self, ::sqlx::error::BoxDynError> { ) -> ::std::result::Result<Self, ::sqlx::error::BoxDynError> {
let id = <i64 as ::sqlx::Decode<'_, DB>>::decode(value)?; let id = <i64 as ::sqlx::Decode<'_, DB>>::decode(value)?;
Ok(Id { Ok(Id {
id, id,

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
id, fields, id,
models::member::{Member, TriggeredMember}, models::member::{Member, TriggeredMember},
}; };
@ -9,6 +9,7 @@ use super::{
trigger::Trigger, trigger::Trigger,
user, user,
}; };
use error_stack::{Result, ResultExt, bail};
use redact::Secret; use redact::Secret;
use sqlx::{SqlitePool, prelude::*}; use sqlx::{SqlitePool, prelude::*};
use tracing::debug; use tracing::debug;
@ -72,7 +73,7 @@ pub struct System {
/// Error while changing the active member /// Error while changing the active member
pub enum ChangeActiveMemberError { pub enum ChangeActiveMemberError {
/// Error while calling the database /// Error while calling the database
Sqlx(#[from] sqlx::Error), Sqlx,
/// The member is not part of the system /// The member is not part of the system
MemberNotFound, MemberNotFound,
} }
@ -106,6 +107,7 @@ impl System {
) )
.fetch_optional(db) .fetch_optional(db)
.await .await
.attach_printable("Error fetching system")
} }
pub async fn active_member(&self, db: &SqlitePool) -> Result<Option<Member>, sqlx::Error> { pub async fn active_member(&self, db: &SqlitePool) -> Result<Option<Member>, sqlx::Error> {
@ -128,13 +130,19 @@ impl System {
let mut new_active_member = None; let mut new_active_member = None;
if let Some(new_active_member_id) = new_active_member_id { if let Some(new_active_member_id) = new_active_member_id {
let Some(member) = Member::fetch_by_id(new_active_member_id, db).await? else { let Some(member) = Member::fetch_by_id(new_active_member_id, db)
return Err(ChangeActiveMemberError::MemberNotFound); .await
.change_context(ChangeActiveMemberError::Sqlx)
.attach_printable("Failed to fetch member")?
else {
bail!(ChangeActiveMemberError::MemberNotFound);
}; };
new_active_member = Some(member); new_active_member = Some(member);
} }
fields!(new_active_member = ?&new_active_member);
sqlx::query!( sqlx::query!(
r#" r#"
UPDATE systems UPDATE systems
@ -145,7 +153,9 @@ impl System {
self.id self.id
) )
.execute(db) .execute(db)
.await?; .await
.change_context(ChangeActiveMemberError::Sqlx)
.attach_printable("Failed to update system active member")?;
self.active_member_id = new_active_member_id; self.active_member_id = new_active_member_id;
Ok(new_active_member) Ok(new_active_member)
@ -174,6 +184,7 @@ impl System {
) )
.fetch_all(db) .fetch_all(db)
.await .await
.attach_printable("Failed to fetch members")
} }
pub async fn fetch_triggered_member( pub async fn fetch_triggered_member(
@ -203,5 +214,6 @@ impl System {
) )
.fetch_optional(db) .fetch_optional(db)
.await .await
.attach_printable("Failed to fetch triggered member")
} }
} }

View file

@ -3,9 +3,9 @@ use std::str::FromStr;
use crate::id; use crate::id;
use super::{Trustability, Trusted, Untrusted, member, system}; use super::{Trustability, Trusted, Untrusted, member, system};
use error_stack::ResultExt; use error_stack::{Result, ResultExt};
use slack_morphism::prelude::*; use slack_morphism::prelude::*;
use sqlx::{SqlitePool, prelude::*}; use sqlx::{SqlitePool, prelude::*, sqlite::SqliteQueryResult};
use tracing::{debug, warn}; use tracing::{debug, warn};
id!( id!(
@ -28,30 +28,24 @@ impl Id<Untrusted> {
self, self,
system_id: system::Id<Trusted>, system_id: system::Id<Trusted>,
db: &SqlitePool, db: &SqlitePool,
) -> Result<Id<Trusted>, Self> { ) -> Result<Id<Trusted>, sqlx::Error> {
let exists = sqlx::query!( sqlx::query!(
"SELECT EXISTS(SELECT 1 FROM triggers WHERE id = $1 AND system_id = $2) AS 'exists: bool'", "SELECT
id as 'id: Id<Trusted>'
FROM triggers
WHERE id = $1 AND system_id = $2",
self.id, self.id,
system_id.id system_id.id
) )
.fetch_one(db) .fetch_one(db)
.await .await
.ok() .map(|record| record.id)
.is_some_and(|record| record.exists); .attach_printable("Error validating trigger")
if exists {
Ok(Id {
id: self.id,
trusted: std::marker::PhantomData,
})
} else {
Err(self)
}
} }
} }
impl Id<Trusted> { impl Id<Trusted> {
pub async fn delete(self, db_pool: &SqlitePool) -> Result<(), sqlx::Error> { pub async fn delete(self, db_pool: &SqlitePool) -> Result<SqliteQueryResult, sqlx::Error> {
sqlx::query!( sqlx::query!(
r#" r#"
DELETE FROM triggers DELETE FROM triggers
@ -61,7 +55,7 @@ impl Id<Trusted> {
) )
.execute(db_pool) .execute(db_pool)
.await .await
.map(|_| ()) .attach_printable("Error deleting trigger")
} }
} }
@ -99,7 +93,7 @@ pub struct UnknownType(String);
impl FromStr for Type { impl FromStr for Type {
type Err = UnknownType; type Err = UnknownType;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s { match s {
"suffix" => Ok(Self::Suffix), "suffix" => Ok(Self::Suffix),
"prefix" => Ok(Self::Prefix), "prefix" => Ok(Self::Prefix),
@ -140,6 +134,7 @@ impl Trigger {
) )
.fetch_optional(db) .fetch_optional(db)
.await .await
.attach_printable("Error fetching trigger")
} }
pub async fn fetch_by_system_id( pub async fn fetch_by_system_id(
@ -164,12 +159,13 @@ impl Trigger {
) )
.fetch_all(db) .fetch_all(db)
.await .await
.attach_printable("Error fetching triggers")
} }
pub async fn fetch_by_member_id( pub async fn fetch_by_member_id(
db: &SqlitePool, db: &SqlitePool,
member_id: member::Id<Trusted>, member_id: member::Id<Trusted>,
) -> error_stack::Result<Vec<Self>, Error> { ) -> error_stack::Result<Vec<Self>, sqlx::Error> {
sqlx::query_as!( sqlx::query_as!(
Trigger, Trigger,
r#" r#"
@ -187,7 +183,7 @@ impl Trigger {
) )
.fetch_all(db) .fetch_all(db)
.await .await
.change_context(Error::Sqlx) .attach_printable("Error fetching triggers")
} }
} }
@ -289,7 +285,7 @@ impl View {
&self, &self,
trigger_id: Id<Trusted>, trigger_id: Id<Trusted>,
db: &SqlitePool, db: &SqlitePool,
) -> error_stack::Result<(), Error> { ) -> Result<SqliteQueryResult, sqlx::Error> {
sqlx::query!( sqlx::query!(
r#" r#"
UPDATE triggers UPDATE triggers
@ -303,8 +299,6 @@ impl View {
.execute(db) .execute(db)
.await .await
.attach_printable("Error updating trigger in database") .attach_printable("Error updating trigger in database")
.change_context(Error::Sqlx)
.map(|_| ())
} }
pub fn create_add_view(self, member_id: member::Id<Trusted>) -> SlackView { pub fn create_add_view(self, member_id: member::Id<Trusted>) -> SlackView {