Skip to main content

cloudconvert_sdk/
error.rs

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