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