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