Skip to main content

shopify_sdk/
error.rs

1//! Error types for the Shopify API SDK.
2//!
3//! This module contains error types used throughout the SDK for configuration
4//! and validation errors.
5//!
6//! # Error Handling
7//!
8//! All configuration constructors return `Result<T, ConfigError>` to enable
9//! fail-fast validation. Error messages are designed to be clear and actionable.
10//!
11//! # Example
12//!
13//! ```rust
14//! use shopify_sdk::{ApiKey, ConfigError};
15//!
16//! let result = ApiKey::new("");
17//! assert!(matches!(result, Err(ConfigError::EmptyApiKey)));
18//! ```
19
20use thiserror::Error;
21
22/// Errors that can occur during SDK configuration.
23///
24/// This enum represents all possible errors that can occur when creating
25/// or validating configuration types. Each variant provides a clear,
26/// actionable error message.
27#[derive(Debug, Error, Clone, PartialEq, Eq)]
28pub enum ConfigError {
29    /// API key cannot be empty.
30    #[error("API key cannot be empty. Please provide a valid Shopify API key.")]
31    EmptyApiKey,
32
33    /// API secret key cannot be empty.
34    #[error("API secret key cannot be empty. Please provide a valid Shopify API secret key.")]
35    EmptyApiSecretKey,
36
37    /// Shop domain is invalid.
38    #[error("Invalid shop domain '{domain}'. Expected format: 'shop-name' or 'shop-name.myshopify.com'.")]
39    InvalidShopDomain {
40        /// The invalid domain that was provided.
41        domain: String,
42    },
43
44    /// API version is invalid.
45    #[error("Invalid API version '{version}'. Expected format: 'YYYY-MM' (e.g., '2024-01') or 'unstable'.")]
46    InvalidApiVersion {
47        /// The invalid version string that was provided.
48        version: String,
49    },
50
51    /// Scopes are invalid.
52    #[error("Invalid scopes: {reason}")]
53    InvalidScopes {
54        /// The reason the scopes are invalid.
55        reason: String,
56    },
57
58    /// A required field is missing.
59    #[error("Missing required field: '{field}'. This field must be set before building the configuration.")]
60    MissingRequiredField {
61        /// The name of the missing field.
62        field: &'static str,
63    },
64
65    /// Host URL is invalid.
66    #[error("Invalid host URL '{url}'. Please provide a valid URL with scheme (e.g., 'https://myapp.example.com').")]
67    InvalidHostUrl {
68        /// The invalid URL that was provided.
69        url: String,
70    },
71
72    /// API version is deprecated.
73    #[error("API version '{version}' is deprecated. Please upgrade to '{latest}' or a newer supported version.")]
74    DeprecatedApiVersion {
75        /// The deprecated version that was provided.
76        version: String,
77        /// The latest supported version to upgrade to.
78        latest: String,
79    },
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn test_empty_api_key_error_message() {
88        let error = ConfigError::EmptyApiKey;
89        let message = error.to_string();
90        assert!(message.contains("API key cannot be empty"));
91        assert!(message.contains("valid Shopify API key"));
92    }
93
94    #[test]
95    fn test_invalid_shop_domain_error_message() {
96        let error = ConfigError::InvalidShopDomain {
97            domain: "bad domain!".to_string(),
98        };
99        let message = error.to_string();
100        assert!(message.contains("bad domain!"));
101        assert!(message.contains("Expected format"));
102    }
103
104    #[test]
105    fn test_missing_required_field_error_message() {
106        let error = ConfigError::MissingRequiredField { field: "api_key" };
107        let message = error.to_string();
108        assert!(message.contains("api_key"));
109        assert!(message.contains("must be set"));
110    }
111
112    #[test]
113    fn test_error_implements_std_error() {
114        let error = ConfigError::EmptyApiKey;
115        // Verify it implements std::error::Error by using it as a dyn Error
116        let _: &dyn std::error::Error = &error;
117    }
118
119    #[test]
120    fn test_deprecated_api_version_error_message() {
121        let error = ConfigError::DeprecatedApiVersion {
122            version: "2024-01".to_string(),
123            latest: "2025-10".to_string(),
124        };
125        let message = error.to_string();
126        assert!(message.contains("2024-01"));
127        assert!(message.contains("deprecated"));
128        assert!(message.contains("2025-10"));
129        assert!(message.contains("upgrade"));
130    }
131}