Skip to main content

systemprompt_sync/
error.rs

1//! Error types for the systemprompt-sync crate.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use systemprompt_models::domain_error;
7
8domain_error! {
9    pub enum SyncError {
10        common: [io, http, json, yaml],
11
12        #[error("API error {status}: {message}")]
13        ApiError { status: u16, message: String },
14
15        #[error("Unauthorized - run 'systemprompt cloud login'")]
16        Unauthorized,
17
18        #[error("Tenant has no associated app")]
19        TenantNoApp,
20
21        #[error("Must run from project root (with infrastructure/ directory)")]
22        NotProjectRoot,
23
24        #[error("Command failed: {command}")]
25        CommandFailed { command: String },
26
27        #[error("Failed to spawn `{command}`: {source}")]
28        CommandSpawnFailed {
29            command: String,
30            #[source]
31            source: std::io::Error,
32        },
33
34        #[error("Failed to open file {path}: {source}")]
35        FileOpenFailed {
36            path: String,
37            #[source]
38            source: std::io::Error,
39        },
40
41        #[error("Docker login failed")]
42        DockerLoginFailed,
43
44        #[error("Git SHA unavailable")]
45        GitShaUnavailable,
46
47        #[error("Missing configuration: {0}")]
48        MissingConfig(String),
49
50        #[error("Partial import failure after {completed}/{total} items: {message}")]
51        PartialImport {
52            completed: usize,
53            total: usize,
54            message: String,
55        },
56
57        #[error("Unsafe tarball entry rejected: {0}")]
58        TarballUnsafe(String),
59
60        #[error("Database error: {0}")]
61        Database(#[from] sqlx::Error),
62
63        #[error("Path error: {0}")]
64        StripPrefix(#[from] std::path::StripPrefixError),
65
66        #[error("Zip error: {0}")]
67        Zip(#[from] zip::result::ZipError),
68
69        #[error("Invalid input: {0}")]
70        InvalidInput(String),
71
72        #[error("internal: {0}")]
73        Internal(String),
74
75        #[error("{0}")]
76        Cloud(#[from] systemprompt_cloud::CloudError),
77
78        #[error("{0}")]
79        ConfigLoad(#[from] systemprompt_loader::ConfigLoadError),
80
81        #[error("{0}")]
82        ExtensionDiscovery(#[from] systemprompt_extension::LoaderError),
83
84        #[error("Hostname not configured for tenant.\nRun: systemprompt cloud login")]
85        HostnameNotConfigured,
86
87        #[error("Pre-deploy sync failed. Use --no-sync to skip (WARNING: may lose data).")]
88        PreDeploySyncFailed,
89
90        #[error("{stage} failed: {source}")]
91        PreSyncStage {
92            stage: &'static str,
93            #[source]
94            source: Box<SyncError>,
95        },
96
97        #[error("{0}")]
98        BuildArtifacts(String),
99    }
100}
101
102impl SyncError {
103    pub fn internal(cause: impl std::fmt::Display) -> Self {
104        Self::Internal(cause.to_string())
105    }
106
107    pub fn pre_sync_stage(stage: &'static str, source: Self) -> Self {
108        Self::PreSyncStage {
109            stage,
110            source: Box::new(source),
111        }
112    }
113
114    pub fn invalid_input(cause: impl std::fmt::Display) -> Self {
115        Self::InvalidInput(cause.to_string())
116    }
117
118    pub const fn is_retryable(&self) -> bool {
119        matches!(self, Self::Http(_))
120            || matches!(
121                self,
122                Self::ApiError { status, .. }
123                    if *status == 502 || *status == 503 || *status == 504 || *status == 429
124            )
125    }
126}
127
128pub type SyncResult<T> = Result<T, SyncError>;