Skip to main content

systemprompt_scheduler/
error.rs

1//! Typed error boundary for the `systemprompt-scheduler` crate.
2//!
3//! [`SchedulerError`] is the canonical error returned from public, non-trait
4//! signatures (services, repositories, lifecycle helpers). It composes
5//! [`sqlx::Error`], [`tokio_cron_scheduler::JobSchedulerError`],
6//! [`systemprompt_database::RepositoryError`],
7//! [`systemprompt_analytics::AnalyticsError`], and
8//! [`systemprompt_users::UserError`] via `#[from]` so `?` propagation works
9//! transparently for every internal call site.
10//!
11//! Provider trait implementations (e.g. [`systemprompt_traits::Job`]) keep
12//! returning [`systemprompt_provider_contracts::ProviderResult`] — the
13//! `From<SchedulerError> for ProviderError` bridge below lets job bodies
14//! propagate `SchedulerError` through `?` without bespoke `map_err` chains.
15
16use thiserror::Error;
17
18#[derive(Debug, Error)]
19pub enum SchedulerError {
20    #[error("Job not found: {job_name}")]
21    JobNotFound { job_name: String },
22
23    #[error("Invalid cron schedule: {schedule}")]
24    InvalidSchedule { schedule: String },
25
26    #[error("Job execution failed: {job_name} - {error}")]
27    JobExecutionFailed { job_name: String, error: String },
28
29    #[error("Database error: {0}")]
30    Database(#[from] sqlx::Error),
31
32    #[error("Repository error: {0}")]
33    Repository(#[from] systemprompt_database::RepositoryError),
34
35    #[error("Analytics error: {0}")]
36    Analytics(#[from] systemprompt_analytics::AnalyticsError),
37
38    #[error("Users error: {0}")]
39    Users(#[from] systemprompt_users::UserError),
40
41    #[error("Cron scheduler error: {0}")]
42    CronScheduler(#[from] tokio_cron_scheduler::JobSchedulerError),
43
44    #[error("Configuration error: {message}")]
45    ConfigError { message: String },
46
47    #[error("Scheduler already running")]
48    AlreadyRunning,
49
50    #[error("Scheduler not initialized")]
51    NotInitialized,
52
53    #[error("Job context missing dependency: {0}")]
54    MissingContext(String),
55
56    #[error("Job panicked: {0}")]
57    Panic(String),
58
59    #[error("I/O error: {0}")]
60    Io(#[from] std::io::Error),
61
62    #[error("internal: {0}")]
63    Internal(String),
64}
65
66impl SchedulerError {
67    pub fn job_not_found(job_name: impl Into<String>) -> Self {
68        Self::JobNotFound {
69            job_name: job_name.into(),
70        }
71    }
72
73    pub fn invalid_schedule(schedule: impl Into<String>) -> Self {
74        Self::InvalidSchedule {
75            schedule: schedule.into(),
76        }
77    }
78
79    pub fn job_execution_failed(job_name: impl Into<String>, error: impl Into<String>) -> Self {
80        Self::JobExecutionFailed {
81            job_name: job_name.into(),
82            error: error.into(),
83        }
84    }
85
86    pub fn config_error(message: impl Into<String>) -> Self {
87        Self::ConfigError {
88            message: message.into(),
89        }
90    }
91
92    pub fn missing_context(name: impl Into<String>) -> Self {
93        Self::MissingContext(name.into())
94    }
95
96    pub fn panic(message: impl Into<String>) -> Self {
97        Self::Panic(message.into())
98    }
99}
100
101impl From<SchedulerError> for systemprompt_provider_contracts::ProviderError {
102    fn from(err: SchedulerError) -> Self {
103        Self::Internal(err.to_string())
104    }
105}
106
107pub type SchedulerResult<T> = Result<T, SchedulerError>;