Skip to main content

headless_lms_credit_registration/
error_reports.rs

1//! Reports operator-visible worker failures to the shared error registry the admin Errors page
2//! reads, the same table `ControllerError` writes 5xx responses to.
3
4use headless_lms_models::errors::{self, ErrorSource, NewErrorReport};
5use sqlx::PgPool;
6
7use crate::phase::{CreditRegistrationPhase, WorkerProcess};
8
9/// Records one phase's worker failures for the admin Errors page.
10#[derive(Clone, Copy)]
11pub(crate) struct ErrorReporter<'a> {
12    pool: &'a PgPool,
13    /// `None` for a caller that is not one of the two deployed workers (the test-only tick
14    /// endpoint), which is left unreported.
15    process: Option<WorkerProcess>,
16    phase: CreditRegistrationPhase,
17}
18
19impl<'a> ErrorReporter<'a> {
20    pub(crate) fn new(
21        pool: &'a PgPool,
22        process: Option<WorkerProcess>,
23        phase: CreditRegistrationPhase,
24    ) -> Self {
25        Self {
26            pool,
27            process,
28            phase,
29        }
30    }
31
32    /// Records one failure. Best-effort: a connection or insert failure is only logged, and this
33    /// never changes the caller's control flow or returns an error.
34    pub(crate) async fn report(
35        &self,
36        message: &str,
37        stack_trace: Option<String>,
38        details: serde_json::Value,
39    ) {
40        let Some(process) = self.process else {
41            return;
42        };
43        let mut details = details;
44        if let serde_json::Value::Object(map) = &mut details {
45            map.insert(
46                "kind".to_string(),
47                serde_json::json!("credit_registration_worker"),
48            );
49            map.insert("phase".to_string(), serde_json::json!(self.phase.as_str()));
50        }
51        let report = NewErrorReport {
52            service: process.as_str().to_string(),
53            error_source: Some(ErrorSource::Backend),
54            message: message.to_string(),
55            stack_trace,
56            path: None,
57            app_version: None,
58            details: Some(details),
59        };
60        errors::insert_best_effort(self.pool, &report).await;
61    }
62}