use crate::clients::GraphqlError;
use crate::rest::resources::v2025_10::common::WebhookTopic;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum WebhookError {
#[error("Host URL is not configured. Please set host in ShopifyConfig to register webhooks.")]
HostNotConfigured,
#[error("Webhook registration not found for topic: {topic:?}")]
RegistrationNotFound {
topic: WebhookTopic,
},
#[error(transparent)]
GraphqlError(#[from] GraphqlError),
#[error("Shopify API error: {message}")]
ShopifyError {
message: String,
},
#[error("Webhook subscription not found in Shopify for topic: {topic:?}")]
SubscriptionNotFound {
topic: WebhookTopic,
},
#[error("Webhook signature verification failed")]
InvalidHmac,
#[error("No handler registered for webhook topic: {topic}")]
NoHandlerForTopic {
topic: String,
},
#[error("Failed to parse webhook payload: {message}")]
PayloadParseError {
message: String,
},
}
#[cfg(test)]
mod tests {
use super::*;
use crate::clients::{HttpError, HttpResponseError};
#[test]
fn test_host_not_configured_error_message() {
let error = WebhookError::HostNotConfigured;
let message = error.to_string();
assert!(message.contains("Host URL is not configured"));
assert!(message.contains("ShopifyConfig"));
}
#[test]
fn test_registration_not_found_error_message() {
let error = WebhookError::RegistrationNotFound {
topic: WebhookTopic::OrdersCreate,
};
let message = error.to_string();
assert!(message.contains("not found"));
assert!(message.contains("OrdersCreate"));
}
#[test]
fn test_shopify_error_message() {
let error = WebhookError::ShopifyError {
message: "Invalid callback URL".to_string(),
};
let message = error.to_string();
assert!(message.contains("Shopify API error"));
assert!(message.contains("Invalid callback URL"));
}
#[test]
fn test_from_graphql_error_conversion() {
let http_error = HttpError::Response(HttpResponseError {
code: 401,
message: r#"{"error":"Unauthorized"}"#.to_string(),
error_reference: None,
});
let graphql_error = GraphqlError::Http(http_error);
let webhook_error: WebhookError = graphql_error.into();
assert!(matches!(webhook_error, WebhookError::GraphqlError(_)));
assert!(webhook_error.to_string().contains("Unauthorized"));
}
#[test]
fn test_all_error_variants_implement_std_error() {
let error: &dyn std::error::Error = &WebhookError::HostNotConfigured;
let _ = error;
let error: &dyn std::error::Error = &WebhookError::RegistrationNotFound {
topic: WebhookTopic::OrdersCreate,
};
let _ = error;
let error: &dyn std::error::Error = &WebhookError::ShopifyError {
message: "test".to_string(),
};
let _ = error;
let http_error = HttpError::Response(HttpResponseError {
code: 400,
message: "test".to_string(),
error_reference: None,
});
let error: &dyn std::error::Error =
&WebhookError::GraphqlError(GraphqlError::Http(http_error));
let _ = error;
let error: &dyn std::error::Error = &WebhookError::InvalidHmac;
let _ = error;
let error: &dyn std::error::Error = &WebhookError::NoHandlerForTopic {
topic: "orders/create".to_string(),
};
let _ = error;
let error: &dyn std::error::Error = &WebhookError::PayloadParseError {
message: "invalid json".to_string(),
};
let _ = error;
}
#[test]
fn test_subscription_not_found_error_message() {
let error = WebhookError::SubscriptionNotFound {
topic: WebhookTopic::ProductsUpdate,
};
let message = error.to_string();
assert!(message.contains("not found in Shopify"));
assert!(message.contains("ProductsUpdate"));
}
#[test]
fn test_invalid_hmac_error_message() {
let error = WebhookError::InvalidHmac;
let message = error.to_string();
assert_eq!(message, "Webhook signature verification failed");
assert!(!message.contains("key"));
assert!(!message.contains("secret"));
}
#[test]
fn test_no_handler_for_topic_error_message_formatting() {
let error = WebhookError::NoHandlerForTopic {
topic: "orders/create".to_string(),
};
let message = error.to_string();
assert!(message.contains("No handler registered"));
assert!(message.contains("orders/create"));
}
#[test]
fn test_payload_parse_error_message_formatting() {
let error = WebhookError::PayloadParseError {
message: "expected value at line 1 column 1".to_string(),
};
let message = error.to_string();
assert!(message.contains("Failed to parse webhook payload"));
assert!(message.contains("expected value at line 1 column 1"));
}
}