Skip to main content

openlark_client/
error.rs

1//! OpenLark Client 错误类型定义
2//!
3//! 基于 openlark-core 的现代化错误处理系统
4//! 直接使用 CoreError,提供类型安全和用户友好的错误管理
5
6use crate::registry::RegistryError;
7use openlark_core::error::{
8    ApiError, CoreError, ErrorCategory, ErrorCode, ErrorContext, ErrorTrait, ErrorType,
9};
10
11/// 🚨 OpenLark 客户端错误类型
12///
13/// 直接类型别名,充分利用 CoreError 的强大功能
14pub type Error = CoreError;
15
16/// 📦 客户端结果类型别名
17pub type Result<T> = std::result::Result<T, Error>;
18
19// ============================================================================
20// 便利错误创建函数(重新导出核心函数)
21// ============================================================================
22
23/// 创建网络错误
24pub fn network_error(message: impl Into<String>) -> Error {
25    openlark_core::error::network_error(message)
26}
27
28/// 创建认证错误
29pub fn authentication_error(message: impl Into<String>) -> Error {
30    openlark_core::error::authentication_error(message)
31}
32
33/// 创建访问令牌格式/内容无效错误
34pub fn token_invalid_error(detail: impl Into<String>) -> Error {
35    openlark_core::error::token_invalid_error(detail)
36}
37
38/// 创建访问令牌过期错误(飞书通用码 99991677)
39pub fn token_expired_error(detail: impl Into<String>) -> Error {
40    openlark_core::error::token_expired_error(detail)
41}
42
43/// 创建缺少权限 scope 的错误
44pub fn permission_missing_error(scopes: &[impl AsRef<str>]) -> Error {
45    openlark_core::error::permission_missing_error(scopes)
46}
47
48/// 创建 SSO 令牌无效错误
49pub fn sso_token_invalid_error(detail: impl Into<String>) -> Error {
50    openlark_core::error::sso_token_invalid_error(detail)
51}
52
53/// 创建身份标识非法错误
54pub fn user_identity_invalid_error(desc: impl Into<String>) -> Error {
55    openlark_core::error::user_identity_invalid_error(desc)
56}
57
58/// 基于飞书通用 `code` 的统一错误映射(客户端自定义解析时可复用)
59pub fn from_feishu_response(
60    code: i32,
61    endpoint: impl Into<String>,
62    message: impl Into<String>,
63    request_id: Option<String>,
64) -> Error {
65    let mapped = ErrorCode::from_feishu_code(code).unwrap_or_else(|| ErrorCode::from_code(code));
66
67    let mut ctx = ErrorContext::new();
68    ctx.add_context("feishu_code", code.to_string());
69    if let Some(req) = request_id {
70        ctx.set_request_id(req);
71    }
72
73    let status = mapped
74        .http_status()
75        .unwrap_or_else(|| match mapped.category() {
76            ErrorCategory::RateLimit => 429,
77            ErrorCategory::Authentication
78            | ErrorCategory::Permission
79            | ErrorCategory::Parameter => 400,
80            ErrorCategory::Resource => 404,
81            _ => 500,
82        });
83
84    CoreError::Api(Box::new(ApiError {
85        status,
86        endpoint: endpoint.into().into(),
87        message: message.into(),
88        source: None,
89        code: mapped,
90        ctx: Box::new(ctx),
91    }))
92}
93
94/// 创建API错误
95pub fn api_error(
96    status: u16,
97    endpoint: impl Into<String>,
98    message: impl Into<String>,
99    request_id: Option<String>,
100) -> Error {
101    openlark_core::error::api_error(status, endpoint, message, request_id)
102}
103
104/// 创建验证错误
105pub fn validation_error(field: impl Into<String>, message: impl Into<String>) -> Error {
106    openlark_core::error::validation_error(field, message)
107}
108
109/// 创建配置错误
110pub fn configuration_error(message: impl Into<String>) -> Error {
111    openlark_core::error::configuration_error(message)
112}
113
114/// 创建序列化错误
115pub fn serialization_error(message: impl Into<String>) -> Error {
116    openlark_core::error::serialization_error(message, None::<serde_json::Error>)
117}
118
119/// 创建业务逻辑错误
120pub fn business_error(_code: impl Into<String>, message: impl Into<String>) -> Error {
121    openlark_core::error::business_error(message)
122}
123
124/// 创建超时错误
125pub fn timeout_error(operation: impl Into<String>) -> Error {
126    use std::time::Duration;
127    openlark_core::error::timeout_error(Duration::from_secs(30), Some(operation.into()))
128}
129
130/// 创建限流错误
131pub fn rate_limit_error(retry_after: Option<u64>) -> Error {
132    use std::time::Duration;
133    openlark_core::error::rate_limit_error(
134        100,
135        Duration::from_secs(60),
136        retry_after.map(Duration::from_secs),
137    )
138}
139
140/// 创建服务不可用错误
141pub fn service_unavailable_error(service: impl Into<String>) -> Error {
142    use std::time::Duration;
143    openlark_core::error::service_unavailable_error(service, Some(Duration::from_secs(60)))
144}
145
146/// 创建内部错误
147pub fn internal_error(message: impl Into<String>) -> Error {
148    openlark_core::error::api_error(500, "internal", message, None::<String>)
149}
150
151/// 创建注册表错误
152pub fn registry_error(err: RegistryError) -> Error {
153    internal_error(format!("服务注册表错误: {}", err))
154}
155
156// ============================================================================
157// 错误扩展功能
158// ============================================================================
159
160/// 客户端错误扩展特征,提供错误恢复建议和步骤
161pub trait ClientErrorExt {
162    /// 获取错误建议
163    fn suggestion(&self) -> &'static str;
164
165    /// 获取错误恢复步骤
166    fn recovery_steps(&self) -> Vec<&'static str>;
167}
168
169impl ClientErrorExt for Error {
170    fn suggestion(&self) -> &'static str {
171        match self.error_type() {
172            ErrorType::Network => "检查网络连接,确认防火墙设置",
173            ErrorType::Authentication => "验证应用凭据,检查令牌有效性",
174            ErrorType::Api => "检查API参数,确认请求格式正确",
175            ErrorType::Validation => "验证输入参数格式和范围",
176            ErrorType::Configuration => "检查应用配置文件和环境变量",
177            ErrorType::Serialization => "确认数据格式正确,检查JSON结构",
178            ErrorType::Business => "确认业务逻辑条件,检查相关权限",
179            ErrorType::Timeout => "增加超时时间或优化请求内容",
180            ErrorType::RateLimit => "稍后重试,考虑降低请求频率",
181            ErrorType::ServiceUnavailable => "稍后重试,检查服务状态",
182            ErrorType::Internal => "联系技术支持,提供错误详情",
183        }
184    }
185
186    fn recovery_steps(&self) -> Vec<&'static str> {
187        match self.error_type() {
188            ErrorType::Network => vec![
189                "检查网络连接状态",
190                "确认代理设置正确",
191                "验证防火墙规则",
192                "尝试切换网络环境",
193            ],
194            ErrorType::Authentication => vec![
195                "验证应用ID和密钥正确性",
196                "检查令牌是否过期",
197                "确认应用权限配置",
198                "重新生成访问令牌",
199            ],
200            ErrorType::Api => vec![
201                "检查请求参数格式",
202                "确认API端点正确",
203                "验证请求体结构",
204                "查阅API文档",
205            ],
206            ErrorType::Validation => vec![
207                "检查必填字段",
208                "验证数据格式和范围",
209                "确认字段类型正确",
210                "参考输入示例",
211            ],
212            ErrorType::Configuration => vec![
213                "检查环境变量设置",
214                "验证配置文件格式",
215                "确认应用权限配置",
216                "重新加载配置",
217            ],
218            ErrorType::Serialization => vec![
219                "检查JSON格式正确性",
220                "验证数据结构匹配",
221                "确认字段类型一致",
222                "使用在线JSON验证工具",
223            ],
224            ErrorType::Business => vec![
225                "检查业务规则约束",
226                "确认用户权限充分",
227                "验证数据完整性",
228                "联系业务负责人",
229            ],
230            ErrorType::Timeout => vec![
231                "增加请求超时时间",
232                "优化网络环境",
233                "减少请求数据量",
234                "考虑分批处理",
235            ],
236            ErrorType::RateLimit => vec![
237                "等待后重试",
238                "降低请求频率",
239                "实施退避策略",
240                "联系技术支持提高限额",
241            ],
242            ErrorType::ServiceUnavailable => vec![
243                "稍后重试请求",
244                "检查服务状态页面",
245                "切换到备用方案",
246                "联系技术支持",
247            ],
248            ErrorType::Internal => vec![
249                "记录详细错误信息",
250                "检查系统日志",
251                "重启相关服务",
252                "联系技术支持",
253            ],
254        }
255    }
256}
257
258// ============================================================================
259// 类型转换
260// ============================================================================
261
262// 注意: reqwest::Error -> CoreError 转换已在 openlark-core 中实现
263// 这里不需要重复实现,直接使用 CoreError 的转换机制
264
265// 注意: 不能为外部类型实现 From,因为这些类型由 CoreError 定义在 openlark-core 中
266// 请使用对应的函数来进行错误转换
267
268// 从注册表错误转换
269impl From<RegistryError> for Error {
270    fn from(err: RegistryError) -> Self {
271        registry_error(err)
272    }
273}
274
275// ============================================================================
276// 便利函数
277// ============================================================================
278
279/// 🔧 从 openlark-core SDKResult 转换为客户端 Result 的便利函数
280///
281/// 这个函数现在只是类型转换,因为我们直接使用 CoreError
282///
283/// # 示例
284///
285/// ```rust
286/// use openlark_client::error::from_sdk_result;
287///
288/// let core_result: openlark_core::SDKResult<String> = Ok("success".to_string());
289/// let client_result = from_sdk_result(core_result);
290/// assert!(client_result.is_ok());
291/// ```
292pub fn from_sdk_result<T>(result: openlark_core::SDKResult<T>) -> Result<T> {
293    result
294}
295
296/// 🔧 创建带有上下文的错误的便利函数
297pub fn with_context<T>(
298    result: Result<T>,
299    context_key: impl Into<String>,
300    context_value: impl Into<String>,
301) -> Result<T> {
302    let key = context_key.into();
303    let value = context_value.into();
304    result.map_err(|err| err.with_context_kv(key, value))
305}
306
307/// 🔧 创建带有操作上下文的错误的便利函数
308pub fn with_operation_context<T>(
309    result: Result<T>,
310    operation: impl Into<String>,
311    component: impl Into<String>,
312) -> Result<T> {
313    let operation = operation.into();
314    let component = component.into();
315    result.map_err(|err| err.with_operation(operation, component))
316}
317
318// ============================================================================
319// 错误分析和报告
320// ============================================================================
321
322/// 错误分析器,提供详细的错误信息和恢复建议
323#[derive(Debug)]
324pub struct ErrorAnalyzer<'a> {
325    error: &'a Error,
326}
327
328impl<'a> ErrorAnalyzer<'a> {
329    /// 创建错误分析器
330    pub fn new(error: &'a Error) -> Self {
331        Self { error }
332    }
333
334    /// 获取详细的错误报告
335    pub fn detailed_report(&self) -> String {
336        let mut report = String::new();
337
338        report.push_str("🚨 错误分析报告\n");
339        report.push_str("================\n\n");
340
341        // 基本信息
342        report.push_str("📋 基本信息:\n");
343        report.push_str(&format!("  错误类型: {:?}\n", self.error.error_type()));
344        report.push_str(&format!("  错误代码: {:?}\n", self.error.error_code()));
345        report.push_str(&format!("  严重程度: {:?}\n", self.error.severity()));
346        report.push_str(&format!("  可重试: {}\n", self.error.is_retryable()));
347
348        if let Some(request_id) = self.error.context().request_id() {
349            report.push_str(&format!("  请求ID: {}\n", request_id));
350        }
351
352        report.push('\n');
353
354        // 错误消息
355        report.push_str("💬 错误消息:\n");
356        report.push_str(&format!("  技术消息: {}\n", self.error));
357        report.push_str(&format!(
358            "  用户消息: {}\n",
359            self.error.user_message().unwrap_or("未知错误")
360        ));
361
362        report.push('\n');
363
364        // 建议和恢复步骤
365        report.push_str("💡 建议:\n");
366        report.push_str(&format!("  {}\n", self.error.suggestion()));
367
368        report.push_str("\n🔧 恢复步骤:\n");
369        for (i, step) in self.error.recovery_steps().iter().enumerate() {
370            report.push_str(&format!("  {}. {}\n", i + 1, step));
371        }
372
373        report.push('\n');
374
375        // 上下文信息
376        if self.error.context().context_len() > 0 {
377            report.push_str("📊 上下文信息:\n");
378            for (key, value) in self.error.context().all_context() {
379                report.push_str(&format!("  {}: {}\n", key, value));
380            }
381            report.push('\n');
382        }
383
384        // 时间戳
385        if let Some(timestamp) = self.error.context().timestamp() {
386            report.push_str(&format!("⏰ 发生时间: {:?}\n", timestamp));
387        }
388        report
389    }
390
391    /// 获取适合日志记录的错误摘要
392    pub fn log_summary(&self) -> String {
393        format!(
394            "Error[{:?}:{:?}] {} - {}",
395            self.error.error_type(),
396            self.error.error_code(),
397            self.error.user_message().unwrap_or("未知错误"),
398            if self.error.is_retryable() {
399                "(可重试)"
400            } else {
401                "(不可重试)"
402            }
403        )
404    }
405
406    /// 获取用户友好的错误消息,包含恢复建议
407    pub fn user_friendly_with_suggestion(&self) -> String {
408        format!(
409            "{}\n\n💡 建议: {}\n\n🔧 可以尝试:\n{}",
410            self.error.user_message().unwrap_or("未知错误"),
411            self.error.suggestion(),
412            self.error
413                .recovery_steps()
414                .iter()
415                .enumerate()
416                .map(|(i, step)| format!("{}. {}", i + 1, step))
417                .collect::<Vec<_>>()
418                .join("\n")
419        )
420    }
421}
422
423// 注意: 不能为外部类型 CoreError 定义 inherent impl
424// 请使用 ClientErrorExt trait 来获得扩展功能
425
426// ============================================================================
427// 测试
428// ============================================================================
429
430#[cfg(test)]
431#[allow(unused_imports)]
432mod tests {
433    use super::*;
434
435    #[test]
436    fn test_error_convenience_functions() {
437        let network_err = network_error("连接失败");
438        assert!(network_err.is_network_error());
439        assert!(network_err.is_retryable());
440
441        let auth_err = authentication_error("令牌无效");
442        assert!(auth_err.is_auth_error());
443        assert!(!auth_err.is_retryable());
444
445        let validation_err = validation_error("email", "邮箱格式不正确");
446        assert!(validation_err.is_validation_error());
447        assert!(!validation_err.is_retryable());
448    }
449
450    #[test]
451    fn test_error_analyzer() {
452        let error = api_error(404, "/users", "用户不存在", Some("req-123".to_string()));
453        let analyzer = ErrorAnalyzer::new(&error);
454
455        let report = analyzer.detailed_report();
456        assert!(report.contains("错误分析报告"));
457        assert!(report.contains("API错误"));
458        assert!(report.contains("req-123"));
459
460        let summary = analyzer.log_summary();
461        assert!(summary.contains("Error"));
462        assert!(summary.contains("Api"));
463
464        let user_msg = analyzer.user_friendly_with_suggestion();
465        assert!(user_msg.contains("建议"));
466        assert!(user_msg.contains("可以尝试"));
467    }
468
469    #[test]
470    fn test_client_error_ext() {
471        let error = timeout_error("数据同步");
472
473        assert!(!error.is_network_error());
474        assert!(!error.is_auth_error());
475        assert!(!error.is_business_error());
476        assert!(error.is_retryable());
477
478        let suggestion = error.suggestion();
479        assert!(!suggestion.is_empty());
480
481        let steps = error.recovery_steps();
482        assert!(!steps.is_empty());
483        assert!(steps.contains(&"增加请求超时时间"));
484    }
485
486    #[test]
487    fn test_error_conversions() {
488        // 测试 JSON 错误转换
489        let json_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
490        let error: Error = json_err.into();
491        assert!(error.is_serialization_error());
492
493        // 测试 tokio 超时错误转换
494        // let timeout_err = tokio::time::error::Elapsed {}; // Private field
495        // let error: Error = timeout_err.into();
496        // assert!(error.is_timeout_error());
497        // assert!(error.is_retryable());
498    }
499
500    #[test]
501    fn test_context_functions() {
502        let result: Result<i32> = Err(validation_error("age", "年龄不能为负数"));
503
504        let contextual_result = with_context(result, "user_id", "12345");
505        assert!(contextual_result.is_err());
506
507        let error = contextual_result.unwrap_err();
508        // 我们现在使用结构化上下文,验证上下文内容而不是字符串
509        // assert!(error.to_string().contains("user_id: 12345"));
510        assert_eq!(error.context().get_context("user_id"), Some("12345"));
511    }
512
513    #[test]
514    fn test_sdk_result_conversion() {
515        // 成功情况
516        let core_result: openlark_core::SDKResult<String> = Ok("success".to_string());
517        let client_result: Result<String> = from_sdk_result(core_result);
518        assert!(client_result.is_ok());
519        assert_eq!(client_result.unwrap(), "success");
520
521        // 失败情况
522        let core_result: openlark_core::SDKResult<String> = Err(network_error("网络错误"));
523        let client_result: Result<String> = from_sdk_result(core_result);
524        assert!(client_result.is_err());
525        assert!(client_result.unwrap_err().is_network_error());
526    }
527
528    #[test]
529    fn test_api_error_function() {
530        let error = api_error(
531            500,
532            "/api/test",
533            "服务器内部错误",
534            Some("req-456".to_string()),
535        );
536        assert!(error.is_api_error());
537        let analyzer = ErrorAnalyzer::new(&error);
538        let report = analyzer.detailed_report();
539        assert!(report.contains("服务器内部错误"));
540    }
541
542    #[test]
543    fn test_validation_error_function() {
544        let error = validation_error("field_name", "字段值为空");
545        assert!(error.is_validation_error());
546        let analyzer = ErrorAnalyzer::new(&error);
547        let user_msg = analyzer.user_friendly_with_suggestion();
548        assert!(user_msg.contains("建议"));
549    }
550
551    #[test]
552    fn test_configuration_error_function() {
553        let error = configuration_error("配置文件缺失");
554        assert!(error.is_config_error());
555    }
556
557    #[test]
558    fn test_serialization_error_function() {
559        let error = serialization_error("JSON解析失败");
560        assert!(error.is_serialization_error());
561    }
562
563    #[test]
564    fn test_business_error_function() {
565        let error = business_error("ERR_001", "业务规则验证失败");
566        assert!(error.is_business_error());
567    }
568
569    #[test]
570    fn test_timeout_error_function() {
571        let error = timeout_error("数据库查询超时");
572        assert!(error.is_timeout_error());
573        assert!(error.is_retryable());
574    }
575
576    #[test]
577    fn test_rate_limit_error_function() {
578        let error = rate_limit_error(Some(60));
579        assert!(error.is_rate_limited());
580    }
581
582    #[test]
583    fn test_service_unavailable_error_function() {
584        let error = service_unavailable_error("支付服务");
585        assert!(error.is_service_unavailable_error());
586    }
587
588    #[test]
589    fn test_internal_error_function() {
590        let error = internal_error("系统内部错误");
591        assert!(!error.is_user_error());
592    }
593
594    #[test]
595    fn test_token_invalid_error_function() {
596        let error = token_invalid_error("token格式不正确");
597        assert!(error.is_auth_error());
598    }
599
600    #[test]
601    fn test_token_expired_error_function() {
602        let error = token_expired_error("token已过期");
603        assert!(error.is_auth_error());
604    }
605
606    #[test]
607    fn test_permission_missing_error_function() {
608        let scopes = vec!["read:user", "write:docs"];
609        let error = permission_missing_error(&scopes);
610        assert!(error.is_auth_error());
611    }
612
613    #[test]
614    fn test_sso_token_invalid_error_function() {
615        let error = sso_token_invalid_error("SSO令牌无效");
616        assert!(error.is_auth_error());
617    }
618
619    #[test]
620    fn test_user_identity_invalid_error_function() {
621        let error = user_identity_invalid_error("用户身份标识非法");
622        assert!(error.is_auth_error());
623    }
624
625    #[test]
626    fn test_from_feishu_response_function() {
627        let error = from_feishu_response(
628            99991677,
629            "/api/test",
630            "token过期",
631            Some("req-789".to_string()),
632        );
633        // 错误可能是认证错误或其他类型,只需确保能正确创建
634        assert!(!error.to_string().is_empty());
635        let error2 = from_feishu_response(400, "/api/test", "参数错误", None);
636        assert!(!error2.to_string().is_empty());
637    }
638
639    #[test]
640    fn test_registry_error_conversion() {
641        let registry_err = crate::registry::RegistryError::ServiceNotFound {
642            name: "test".to_string(),
643        };
644        let error: Error = registry_err.into();
645        assert!(!error.is_user_error());
646    }
647
648    #[test]
649    fn test_error_analyzer_log_summary() {
650        let error = network_error("连接超时");
651        let analyzer = ErrorAnalyzer::new(&error);
652        let summary = analyzer.log_summary();
653        assert!(summary.contains("Network"));
654        assert!(summary.contains("可重试") || summary.contains("不可重试"));
655    }
656
657    #[test]
658    fn test_error_analyzer_user_friendly() {
659        let error = api_error(404, "/users/123", "用户不存在", None);
660        let analyzer = ErrorAnalyzer::new(&error);
661        let friendly = analyzer.user_friendly_with_suggestion();
662        assert!(friendly.contains("建议"));
663        assert!(friendly.contains("可以尝试"));
664    }
665
666    #[test]
667    fn test_with_operation_context() {
668        let result: Result<i32> = Err(network_error("网络错误"));
669        let contextual_result = with_operation_context(result, "test_operation", "TestComponent");
670        assert!(contextual_result.is_err());
671        let error = contextual_result.unwrap_err();
672        assert_eq!(
673            error.context().get_context("operation"),
674            Some("test_operation")
675        );
676        assert_eq!(
677            error.context().get_context("component"),
678            Some("TestComponent")
679        );
680    }
681
682    #[test]
683    fn test_with_operation_context_updates_timeout_operation_field() {
684        use std::time::Duration;
685
686        let result: Result<i32> = Err(openlark_core::error::timeout_error(
687            Duration::from_secs(3),
688            Some("old_operation".to_string()),
689        ));
690
691        let contextual_result = with_operation_context(result, "new_operation", "ClientLayer");
692        assert!(contextual_result.is_err());
693
694        match contextual_result.unwrap_err() {
695            CoreError::Timeout {
696                operation, ref ctx, ..
697            } => {
698                assert_eq!(operation.as_deref(), Some("new_operation"));
699                assert_eq!(ctx.operation(), Some("new_operation"));
700                assert_eq!(ctx.component(), Some("ClientLayer"));
701            }
702            other => panic!("expected timeout error, got {:?}", other.error_type()),
703        }
704    }
705
706    #[test]
707    fn test_all_error_types_suggestion() {
708        let error_types = vec![
709            (network_error("test"), "检查网络连接"),
710            (authentication_error("test"), "验证应用凭据"),
711            (api_error(500, "/test", "test", None), "检查API参数"),
712            (validation_error("field", "test"), "验证输入参数"),
713            (configuration_error("test"), "检查应用配置"),
714            (serialization_error("test"), "确认数据格式"),
715            (business_error("code", "test"), "确认业务逻辑"),
716            (timeout_error("test"), "增加超时时间"),
717            (rate_limit_error(None), "稍后重试"),
718            (service_unavailable_error("svc"), "稍后重试"),
719            (internal_error("test"), "联系技术支持"),
720        ];
721
722        for (error, expected_keyword) in error_types {
723            let suggestion = error.suggestion();
724            assert!(
725                suggestion.contains(expected_keyword) || !suggestion.is_empty(),
726                "Error type {:?} should have meaningful suggestion",
727                error.error_type()
728            );
729        }
730    }
731
732    #[test]
733    fn test_all_error_types_recovery_steps() {
734        let error_types = vec![
735            network_error("test"),
736            authentication_error("test"),
737            api_error(500, "/test", "test", None),
738            validation_error("field", "test"),
739            configuration_error("test"),
740            serialization_error("test"),
741            business_error("code", "test"),
742            timeout_error("test"),
743            rate_limit_error(None),
744            service_unavailable_error("svc"),
745            internal_error("test"),
746        ];
747
748        for error in error_types {
749            let steps = error.recovery_steps();
750            assert!(
751                !steps.is_empty(),
752                "Error type {:?} should have recovery steps",
753                error.error_type()
754            );
755            assert!(
756                steps.len() >= 3,
757                "Error type {:?} should have at least 3 recovery steps",
758                error.error_type()
759            );
760        }
761    }
762}