Skip to main content

cloudconvert_sdk/
error.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4use crate::jobs::RateLimit;
5
6pub type Result<T, E = Error> = std::result::Result<T, E>;
7
8#[derive(Debug, thiserror::Error)]
9#[non_exhaustive]
10pub enum Error {
11    #[error("CloudConvert API returned HTTP {status}: {message}")]
12    Api {
13        status: u16,
14        message: String,
15        code: Option<String>,
16        errors: Option<Box<Value>>,
17        rate_limit: Option<Box<RateLimit>>,
18    },
19
20    #[error("HTTP request failed: {0}")]
21    Http(#[from] reqwest::Error),
22
23    #[error("local IO failed: {0}")]
24    Io(#[from] std::io::Error),
25
26    #[error("URL parsing failed: {0}")]
27    Url(#[from] url::ParseError),
28
29    #[error("JSON serialization failed: {0}")]
30    Json(#[from] serde_json::Error),
31
32    #[error("missing required environment variable {0}")]
33    MissingEnv(&'static str),
34
35    #[error("task is not an import/upload task that is ready for upload")]
36    UploadTaskNotReady,
37
38    #[error("CloudConvert redirect response did not include a valid Location header")]
39    MissingRedirectLocation,
40
41    #[error("webhook signature is not valid hex")]
42    InvalidSignatureHex,
43
44    #[error("invalid CloudConvert API path")]
45    InvalidApiPath,
46
47    #[error("invalid CloudConvert resource id")]
48    InvalidResourceId,
49
50    #[error("invalid CloudConvert region")]
51    InvalidRegion,
52
53    #[error("{0}")]
54    InvalidBuilderState(#[from] InvalidBuilderState),
55
56    #[cfg(feature = "socket")]
57    #[error("Socket.IO operation failed: {0}")]
58    Socket(String),
59}
60
61impl Error {
62    pub fn api_error(&self) -> Option<ApiError<'_>> {
63        match self {
64            Self::Api {
65                status,
66                message,
67                code,
68                errors,
69                rate_limit,
70            } => Some(ApiError {
71                status: *status,
72                message,
73                code: code.as_deref(),
74                errors: errors.as_deref(),
75                rate_limit: rate_limit.as_deref(),
76                retry_after: rate_limit
77                    .as_ref()
78                    .and_then(|rate_limit| rate_limit.retry_after),
79            }),
80            _ => None,
81        }
82    }
83}
84
85#[derive(Clone, Copy, Debug)]
86#[non_exhaustive]
87pub struct ApiError<'a> {
88    pub status: u16,
89    pub message: &'a str,
90    pub code: Option<&'a str>,
91    pub errors: Option<&'a Value>,
92    pub rate_limit: Option<&'a RateLimit>,
93    pub retry_after: Option<u64>,
94}
95
96#[derive(Clone, Copy, Debug, Eq, PartialEq)]
97pub struct InvalidBuilderState {
98    pub kind: InvalidBuilderStateKind,
99    pub builder: &'static str,
100    pub method: &'static str,
101}
102
103impl InvalidBuilderState {
104    pub(crate) fn missing_previous_task(method: &'static str) -> Self {
105        Self {
106            kind: InvalidBuilderStateKind::MissingPreviousTask,
107            builder: "JobBuilder",
108            method,
109        }
110    }
111}
112
113impl std::fmt::Display for InvalidBuilderState {
114    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        match self.kind {
116            InvalidBuilderStateKind::MissingPreviousTask => write!(
117                formatter,
118                "invalid {} state in {}: shorthand requires a previous task",
119                self.builder, self.method
120            ),
121        }
122    }
123}
124
125impl std::error::Error for InvalidBuilderState {}
126
127#[derive(Clone, Copy, Debug, Eq, PartialEq)]
128#[non_exhaustive]
129pub enum InvalidBuilderStateKind {
130    MissingPreviousTask,
131}
132
133#[derive(Debug, Deserialize, Serialize)]
134pub(crate) struct ApiErrorBody {
135    pub message: Option<String>,
136    pub code: Option<String>,
137    pub errors: Option<Value>,
138}