use crate::auth::Identity;
#[non_exhaustive]
pub struct BulkActionContext<'a> {
pub actor: &'a Identity,
pub correlation_id: Option<&'a str>,
pub ip_address: Option<&'a str>,
}
impl<'a> BulkActionContext<'a> {
pub fn new(actor: &'a Identity) -> Self {
Self {
actor,
correlation_id: None,
ip_address: None,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Default)]
pub struct BulkActionResult {
pub succeeded: usize,
pub failed: Vec<BulkActionFailure>,
pub message: Option<String>,
}
impl BulkActionResult {
pub fn ok(succeeded: usize) -> Self {
Self {
succeeded,
failed: Vec::new(),
message: None,
}
}
pub fn partial(succeeded: usize, failed: Vec<BulkActionFailure>) -> Self {
Self {
succeeded,
failed,
message: None,
}
}
pub fn total(&self) -> usize {
self.succeeded + self.failed.len()
}
pub fn with_message(mut self, message: impl Into<String>) -> Self {
self.message = Some(message.into());
self
}
}
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct BulkActionFailure {
pub id: i64,
pub reason: String,
}
impl BulkActionFailure {
pub fn new(id: i64, reason: impl Into<String>) -> Self {
Self {
id,
reason: reason.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ok_constructs_clean_result() {
let r = BulkActionResult::ok(7);
assert_eq!(r.succeeded, 7);
assert!(r.failed.is_empty());
assert!(r.message.is_none());
assert_eq!(r.total(), 7);
}
#[test]
fn partial_carries_failures_into_total() {
let r = BulkActionResult::partial(
5,
vec![
BulkActionFailure::new(42, "already overdue"),
BulkActionFailure::new(51, "row not active"),
],
);
assert_eq!(r.succeeded, 5);
assert_eq!(r.failed.len(), 2);
assert_eq!(r.total(), 7);
assert_eq!(r.failed[0].id, 42);
assert_eq!(r.failed[0].reason, "already overdue");
}
#[test]
fn with_message_attaches_summary() {
let r = BulkActionResult::ok(3).with_message("Marked 3 loans overdue");
assert_eq!(r.message.as_deref(), Some("Marked 3 loans overdue"));
}
#[test]
fn default_is_zero_result() {
let r = BulkActionResult::default();
assert_eq!(r.succeeded, 0);
assert!(r.failed.is_empty());
assert_eq!(r.total(), 0);
}
#[test]
fn failure_new_owns_reason() {
let f1 = BulkActionFailure::new(1, "static");
let f2 = BulkActionFailure::new(2, String::from("owned"));
let f3 = BulkActionFailure::new(3, format!("{}-{}", "fmt", "string"));
assert_eq!(f1.reason, "static");
assert_eq!(f2.reason, "owned");
assert_eq!(f3.reason, "fmt-string");
}
}