github_mcp/core/
errors.rs1use thiserror::Error;
4
5#[derive(Debug, Error)]
9pub enum McpifyError {
10 #[error("{0}")]
11 Configuration(String),
12
13 #[error("{0}")]
14 Authentication(String),
15
16 #[error("{message}{}", format_validation_details(details))]
17 Validation {
18 message: String,
19 details: Option<serde_json::Value>,
20 },
21
22 #[error("{0}")]
23 NotFound(String),
24
25 #[error("circuit breaker is open")]
26 CircuitBreakerOpen,
27
28 #[error("rate limit exceeded")]
29 RateLimitExceeded,
30}
31
32fn format_validation_details(details: &Option<serde_json::Value>) -> String {
39 match details.as_ref().and_then(serde_json::Value::as_array) {
40 Some(errors) if !errors.is_empty() => {
41 let joined = errors
42 .iter()
43 .filter_map(serde_json::Value::as_str)
44 .collect::<Vec<_>>()
45 .join("; ");
46 format!(": {joined}")
47 }
48 _ => String::new(),
49 }
50}
51
52impl McpifyError {
53 pub fn code(&self) -> &'static str {
54 match self {
55 Self::Configuration(_) => "CONFIGURATION_ERROR",
56 Self::Authentication(_) => "AUTHENTICATION_ERROR",
57 Self::Validation { .. } => "VALIDATION_ERROR",
58 Self::NotFound(_) => "NOT_FOUND",
59 Self::CircuitBreakerOpen => "CIRCUIT_BREAKER_OPEN",
60 Self::RateLimitExceeded => "RATE_LIMIT_EXCEEDED",
61 }
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn every_variant_reports_its_error_code() {
71 assert_eq!(
72 McpifyError::Configuration("x".into()).code(),
73 "CONFIGURATION_ERROR"
74 );
75 assert_eq!(
76 McpifyError::Authentication("x".into()).code(),
77 "AUTHENTICATION_ERROR"
78 );
79 assert_eq!(
80 McpifyError::Validation {
81 message: "x".into(),
82 details: None
83 }
84 .code(),
85 "VALIDATION_ERROR"
86 );
87 assert_eq!(McpifyError::NotFound("x".into()).code(), "NOT_FOUND");
88 assert_eq!(
89 McpifyError::CircuitBreakerOpen.code(),
90 "CIRCUIT_BREAKER_OPEN"
91 );
92 assert_eq!(McpifyError::RateLimitExceeded.code(), "RATE_LIMIT_EXCEEDED");
93 }
94
95 #[test]
96 fn validation_display_appends_details_when_present() {
97 let err = McpifyError::Validation {
98 message: "unexpected response shape for 'getAllProjects'".into(),
99 details: Some(serde_json::json!(["\"foo\" is not of type \"object\""])),
100 };
101 assert_eq!(
102 err.to_string(),
103 "unexpected response shape for 'getAllProjects': \"foo\" is not of type \"object\""
104 );
105 }
106
107 #[test]
108 fn validation_display_omits_suffix_when_details_absent() {
109 let err = McpifyError::Validation {
110 message: "invalid input for 'getIssue'".into(),
111 details: None,
112 };
113 assert_eq!(err.to_string(), "invalid input for 'getIssue'");
114 }
115}