systemprompt_sync/
error.rs1use 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>;