Skip to main content

shopify_sdk/clients/rest/
errors.rs

1//! REST-specific error types for the Shopify API SDK.
2//!
3//! This module contains error types for REST API operations, including
4//! the REST API being disabled, invalid paths, and wrapped HTTP errors.
5//!
6//! # Error Handling
7//!
8//! The SDK uses specific error types for different failure scenarios:
9//!
10//! - [`RestError::RestApiDisabled`]: When the REST API is deprecated/disabled in config
11//! - [`RestError::InvalidPath`]: When a REST API path fails validation
12//! - [`RestError::Http`]: Wraps underlying HTTP errors
13//!
14//! # Example
15//!
16//! ```rust,ignore
17//! use shopify_sdk::clients::rest::{RestClient, RestError};
18//!
19//! match client.get("products", None).await {
20//!     Ok(response) => println!("Products: {}", response.body),
21//!     Err(RestError::RestApiDisabled) => {
22//!         println!("REST API is disabled. Please use GraphQL.");
23//!     }
24//!     Err(RestError::InvalidPath { path }) => {
25//!         println!("Invalid path: {}", path);
26//!     }
27//!     Err(RestError::Http(e)) => {
28//!         println!("HTTP error: {}", e);
29//!     }
30//! }
31//! ```
32
33use crate::clients::HttpError;
34use thiserror::Error;
35
36/// Error type for REST API operations.
37///
38/// This enum provides specific error types for REST API operations,
39/// wrapping HTTP errors and adding REST-specific error cases.
40///
41/// # Example
42///
43/// ```rust
44/// use shopify_sdk::clients::rest::RestError;
45///
46/// // REST API disabled error
47/// let error = RestError::RestApiDisabled;
48/// assert!(error.to_string().contains("deprecated"));
49///
50/// // Invalid path error
51/// let error = RestError::InvalidPath { path: "".to_string() };
52/// assert!(error.to_string().contains("Invalid"));
53/// ```
54#[derive(Debug, Error)]
55pub enum RestError {
56    /// The REST Admin API has been deprecated and is disabled.
57    ///
58    /// This error is returned when the REST API is disabled in the SDK
59    /// configuration, indicating that users should migrate to GraphQL.
60    #[error("The Admin REST API has been deprecated. Please use the GraphQL Admin API. For more information see https://www.shopify.com/ca/partners/blog/all-in-on-graphql")]
61    RestApiDisabled,
62
63    /// The REST API path is invalid.
64    ///
65    /// This error is returned when a path fails validation, such as
66    /// when it is empty after normalization.
67    #[error("Invalid REST API path: {path}")]
68    InvalidPath {
69        /// The invalid path that was provided.
70        path: String,
71    },
72
73    /// An HTTP-level error occurred.
74    ///
75    /// This variant wraps [`HttpError`] for unified error handling.
76    #[error(transparent)]
77    Http(#[from] HttpError),
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use crate::clients::{HttpResponseError, MaxHttpRetriesExceededError};
84
85    #[test]
86    fn test_rest_api_disabled_error_message_matches_ruby_sdk() {
87        let error = RestError::RestApiDisabled;
88        let message = error.to_string();
89
90        // Match Ruby SDK error message format
91        assert!(message.contains("The Admin REST API has been deprecated"));
92        assert!(message.contains("Please use the GraphQL Admin API"));
93        assert!(message.contains("https://www.shopify.com/ca/partners/blog/all-in-on-graphql"));
94    }
95
96    #[test]
97    fn test_invalid_path_error_includes_path_in_message() {
98        let error = RestError::InvalidPath {
99            path: "/invalid/path".to_string(),
100        };
101        let message = error.to_string();
102
103        assert!(message.contains("Invalid REST API path"));
104        assert!(message.contains("/invalid/path"));
105    }
106
107    #[test]
108    fn test_invalid_path_error_with_empty_path() {
109        let error = RestError::InvalidPath {
110            path: String::new(),
111        };
112        let message = error.to_string();
113
114        assert!(message.contains("Invalid REST API path"));
115        // Empty path should still be mentioned (as empty string)
116        assert_eq!(message, "Invalid REST API path: ");
117    }
118
119    #[test]
120    fn test_http_error_wraps_http_response_error() {
121        let http_error = HttpError::Response(HttpResponseError {
122            code: 404,
123            message: r#"{"error":"Not Found"}"#.to_string(),
124            error_reference: Some("abc-123".to_string()),
125        });
126
127        let rest_error = RestError::Http(http_error);
128        let message = rest_error.to_string();
129
130        assert!(message.contains("Not Found"));
131    }
132
133    #[test]
134    fn test_from_http_error_conversion() {
135        let http_error = HttpError::Response(HttpResponseError {
136            code: 500,
137            message: r#"{"error":"Internal Server Error"}"#.to_string(),
138            error_reference: None,
139        });
140
141        // Test From<HttpError> conversion
142        let rest_error: RestError = http_error.into();
143
144        assert!(matches!(rest_error, RestError::Http(_)));
145    }
146
147    #[test]
148    fn test_all_error_variants_implement_std_error() {
149        // RestApiDisabled
150        let disabled_error: &dyn std::error::Error = &RestError::RestApiDisabled;
151        let _ = disabled_error;
152
153        // InvalidPath
154        let path_error: &dyn std::error::Error = &RestError::InvalidPath {
155            path: "test".to_string(),
156        };
157        let _ = path_error;
158
159        // Http
160        let http_error: &dyn std::error::Error =
161            &RestError::Http(HttpError::Response(HttpResponseError {
162                code: 400,
163                message: "test".to_string(),
164                error_reference: None,
165            }));
166        let _ = http_error;
167    }
168
169    #[test]
170    fn test_http_error_wraps_max_retries_exceeded() {
171        let http_error = HttpError::MaxRetries(MaxHttpRetriesExceededError {
172            code: 429,
173            tries: 3,
174            message: r#"{"error":"Rate limited"}"#.to_string(),
175            error_reference: None,
176        });
177
178        let rest_error = RestError::Http(http_error);
179        let message = rest_error.to_string();
180
181        assert!(message.contains("Exceeded maximum retry count"));
182        assert!(message.contains("3"));
183    }
184}