Skip to main content

datalab_cli/
error.rs

1use serde::Serialize;
2use std::path::PathBuf;
3use thiserror::Error;
4
5#[derive(Error, Debug)]
6pub enum DatalabError {
7    #[error("API error ({status}): {message}")]
8    ApiError { status: u16, message: String },
9
10    #[error("Rate limited. Retry after {retry_after:?} seconds")]
11    RateLimited { retry_after: Option<u64> },
12
13    #[error("Request timed out after {seconds} seconds")]
14    Timeout { seconds: u64 },
15
16    #[error("Network error: {0}")]
17    NetworkError(#[from] reqwest::Error),
18
19    #[error("Cache error: {0}")]
20    CacheError(#[from] std::io::Error),
21
22    #[error("JSON error: {0}")]
23    JsonError(#[from] serde_json::Error),
24
25    #[error("Invalid input: {0}")]
26    InvalidInput(String),
27
28    #[error("Missing API key. Set DATALAB_API_KEY environment variable")]
29    MissingApiKey,
30
31    #[error("File not found: {0}")]
32    FileNotFound(PathBuf),
33
34    #[error("Processing failed: {0}")]
35    ProcessingFailed(String),
36}
37
38#[derive(Serialize)]
39pub struct ErrorResponse {
40    pub error: String,
41    pub code: String,
42}
43
44impl DatalabError {
45    pub fn code(&self) -> &'static str {
46        match self {
47            DatalabError::ApiError { .. } => "API_ERROR",
48            DatalabError::RateLimited { .. } => "RATE_LIMITED",
49            DatalabError::Timeout { .. } => "TIMEOUT",
50            DatalabError::NetworkError(_) => "NETWORK_ERROR",
51            DatalabError::CacheError(_) => "CACHE_ERROR",
52            DatalabError::JsonError(_) => "JSON_ERROR",
53            DatalabError::InvalidInput(_) => "INVALID_INPUT",
54            DatalabError::MissingApiKey => "MISSING_API_KEY",
55            DatalabError::FileNotFound(_) => "FILE_NOT_FOUND",
56            DatalabError::ProcessingFailed(_) => "PROCESSING_FAILED",
57        }
58    }
59
60    pub fn to_json(&self) -> String {
61        let response = ErrorResponse {
62            error: self.to_string(),
63            code: self.code().to_string(),
64        };
65        serde_json::to_string(&response)
66            .unwrap_or_else(|_| format!(r#"{{"error":"{}","code":"{}"}}"#, self, self.code()))
67    }
68
69    /// Returns an actionable suggestion for fixing the error
70    pub fn suggestion(&self) -> Option<String> {
71        match self {
72            DatalabError::MissingApiKey => Some(
73                "Set your API key:\n  export DATALAB_API_KEY=\"your-api-key\"".to_string()
74            ),
75            DatalabError::ApiError { status, .. } => match status {
76                401 => Some(
77                    "Your API key appears to be invalid. Check that DATALAB_API_KEY is set correctly.".to_string()
78                ),
79                403 => Some(
80                    "Access forbidden. Your API key may not have permission for this operation.".to_string()
81                ),
82                413 => Some(
83                    "File too large. Maximum file size is 200MB.".to_string()
84                ),
85                429 => Some(
86                    "Rate limit exceeded. Wait a moment before retrying.".to_string()
87                ),
88                500..=599 => Some(
89                    "The API server encountered an error. Try again later.".to_string()
90                ),
91                _ => None,
92            },
93            DatalabError::RateLimited { retry_after } => {
94                if let Some(secs) = retry_after {
95                    Some(format!(
96                        "You've exceeded the rate limit. Wait {} seconds before retrying.",
97                        secs
98                    ))
99                } else {
100                    Some(
101                        "You've exceeded the rate limit. Wait a moment before retrying.".to_string()
102                    )
103                }
104            }
105            DatalabError::Timeout { seconds } => Some(format!(
106                "The request timed out after {} seconds. Try:\n  \
107                 - Using --timeout with a higher value\n  \
108                 - Processing fewer pages with --max-pages\n  \
109                 - Checking your network connection",
110                seconds
111            )),
112            DatalabError::NetworkError(_) => Some(
113                "Could not connect to the API. Check your internet connection.".to_string()
114            ),
115            DatalabError::FileNotFound(path) => Some(format!(
116                "The file '{}' does not exist or is not readable.\n  \
117                 Check that the path is correct and you have read permissions.",
118                path.display()
119            )),
120            DatalabError::InvalidInput(msg) => {
121                if msg.contains("schema") || msg.contains("JSON") {
122                    Some(
123                        "Ensure your JSON is valid. You can validate it with: jq . your-file.json".to_string()
124                    )
125                } else {
126                    None
127                }
128            }
129            DatalabError::ProcessingFailed(msg) => {
130                if msg.contains("unsupported") {
131                    Some(
132                        "This file format may not be supported. Supported formats: PDF, PNG, JPG, DOCX.".to_string()
133                    )
134                } else {
135                    None
136                }
137            }
138            _ => None,
139        }
140    }
141
142    /// Returns a help URL for more information about the error
143    pub fn help_url(&self) -> Option<&'static str> {
144        match self {
145            DatalabError::MissingApiKey => Some("https://www.datalab.to/app/keys"),
146            DatalabError::ApiError { status, .. } if *status == 429 => {
147                Some("https://documentation.datalab.to/docs/common/limits")
148            }
149            DatalabError::RateLimited { .. } => {
150                Some("https://documentation.datalab.to/docs/common/limits")
151            }
152            _ => None,
153        }
154    }
155}
156
157pub type Result<T> = std::result::Result<T, DatalabError>;