1use serde::{Deserialize, Serialize};
9use sklears_core::{
10 error::{Result as SklResult, SklearsError},
11 traits::Estimator,
12};
13use std::collections::{BTreeMap, HashMap};
14use std::fmt;
15use std::sync::Arc;
16use std::time::{Duration, Instant};
17
18#[derive(Debug, Clone)]
20pub struct ExternalIntegrationManager {
21 integrations: HashMap<String, Arc<dyn ExternalIntegration + Send + Sync>>,
23 configs: HashMap<String, IntegrationConfig>,
25 retry_policies: HashMap<String, RetryPolicy>,
27 circuit_breakers: HashMap<String, CircuitBreakerState>,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct IntegrationConfig {
34 pub name: String,
36 pub integration_type: IntegrationType,
38 pub connection: ConnectionConfig,
40 pub auth: Option<AuthConfig>,
42 pub timeout: TimeoutConfig,
44 pub rate_limit: Option<RateLimitConfig>,
46 pub health_check: HealthCheckConfig,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
52pub enum IntegrationType {
53 RestApi,
55 GraphQl,
57 Database,
59 MessageQueue,
61 FileSystem,
63 CloudStorage,
65 Container,
67 Serverless,
69 Custom(String),
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct ConnectionConfig {
76 pub endpoint: String,
78 pub pool_size: Option<usize>,
80 pub keep_alive: bool,
82 pub tls: Option<TlsConfig>,
84 pub headers: BTreeMap<String, String>,
86 pub query_params: BTreeMap<String, String>,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct TlsConfig {
93 pub verify_certificates: bool,
95 pub ca_cert_path: Option<String>,
97 pub client_cert_path: Option<String>,
99 pub client_key_path: Option<String>,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct AuthConfig {
106 pub auth_type: AuthType,
108 pub credentials: AuthCredentials,
110 pub refresh: Option<RefreshConfig>,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
116pub enum AuthType {
117 None,
119 Basic,
121 Bearer,
123 ApiKey,
125 OAuth2,
127 Custom(String),
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133pub enum AuthCredentials {
134 Basic { username: String, password: String },
136 Bearer { token: String },
138 ApiKey { key: String, header: String },
140 OAuth2 {
142 client_id: String,
143 client_secret: String,
144 access_token: Option<String>,
145 refresh_token: Option<String>,
146 },
147 Custom(BTreeMap<String, String>),
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct RefreshConfig {
154 pub endpoint: String,
156 pub interval: Duration,
158 pub retry_attempts: usize,
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct TimeoutConfig {
165 pub connect_timeout: Duration,
167 pub request_timeout: Duration,
169 pub read_timeout: Duration,
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct RateLimitConfig {
176 pub requests_per_second: f64,
178 pub burst_capacity: usize,
180 pub backoff_strategy: BackoffStrategy,
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize)]
186pub enum BackoffStrategy {
187 Fixed(Duration),
189 Exponential { initial: Duration, max: Duration },
191 Linear {
193 initial: Duration,
194 increment: Duration,
195 },
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct HealthCheckConfig {
201 pub endpoint: Option<String>,
203 pub interval: Duration,
205 pub timeout: Duration,
207 pub failure_threshold: usize,
209 pub success_threshold: usize,
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize)]
215pub struct RetryPolicy {
216 pub max_attempts: usize,
218 pub backoff: BackoffStrategy,
220 pub retry_conditions: Vec<RetryCondition>,
222}
223
224#[derive(Debug, Clone, Serialize, Deserialize)]
226pub enum RetryCondition {
227 NetworkError,
229 Timeout,
231 StatusCode(Vec<u16>),
233 ServerError,
235 Custom(String),
237}
238
239#[derive(Debug, Clone)]
241pub struct CircuitBreakerState {
242 state: CircuitState,
244 failure_count: usize,
246 last_failure: Option<Instant>,
248 config: CircuitBreakerConfig,
250}
251
252#[derive(Debug, Clone, PartialEq, Eq)]
254pub enum CircuitState {
255 Closed,
257 Open,
259 HalfOpen,
261}
262
263#[derive(Debug, Clone, Serialize, Deserialize)]
265pub struct CircuitBreakerConfig {
266 pub failure_threshold: usize,
268 pub reset_timeout: Duration,
270 pub success_threshold: usize,
272}
273
274pub trait ExternalIntegration: fmt::Debug {
276 fn initialize(&mut self, config: &IntegrationConfig) -> SklResult<()>;
278
279 fn health_check(&self) -> SklResult<HealthStatus>;
281
282 fn send_data(&self, data: &IntegrationData) -> SklResult<IntegrationResponse>;
284
285 fn receive_data(&self, request: &IntegrationRequest) -> SklResult<IntegrationData>;
287
288 fn execute_operation(&self, operation: &Operation) -> SklResult<OperationResult>;
290
291 fn cleanup(&mut self) -> SklResult<()>;
293}
294
295#[derive(Debug, Clone)]
297pub struct HealthStatus {
298 pub is_healthy: bool,
300 pub response_time: Duration,
302 pub last_check: Instant,
304 pub error_message: Option<String>,
306 pub metadata: BTreeMap<String, String>,
308}
309
310#[derive(Debug, Clone, Serialize, Deserialize)]
312pub struct IntegrationData {
313 pub data_type: String,
315 pub payload: Vec<u8>,
317 pub metadata: BTreeMap<String, String>,
319 pub content_type: String,
321 pub encoding: Option<String>,
323}
324
325#[derive(Debug, Clone, Serialize, Deserialize)]
327pub struct IntegrationRequest {
328 pub request_type: String,
330 pub parameters: BTreeMap<String, String>,
332 pub body: Option<Vec<u8>>,
334 pub headers: BTreeMap<String, String>,
336}
337
338#[derive(Debug, Clone, Serialize, Deserialize)]
340pub struct IntegrationResponse {
341 pub success: bool,
343 pub status_code: Option<u16>,
345 pub body: Option<Vec<u8>>,
347 pub headers: BTreeMap<String, String>,
349 pub error: Option<String>,
351 pub response_time: Duration,
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct Operation {
358 pub operation_type: String,
360 pub parameters: BTreeMap<String, serde_json::Value>,
362 pub input: Option<IntegrationData>,
364 pub metadata: BTreeMap<String, String>,
366}
367
368#[derive(Debug, Clone, Serialize, Deserialize)]
370pub struct OperationResult {
371 pub success: bool,
373 pub result: Option<IntegrationData>,
375 pub error: Option<String>,
377 pub execution_time: Duration,
379 pub metadata: BTreeMap<String, String>,
381}
382
383impl ExternalIntegrationManager {
384 #[must_use]
386 pub fn new() -> Self {
387 Self {
388 integrations: HashMap::new(),
389 configs: HashMap::new(),
390 retry_policies: HashMap::new(),
391 circuit_breakers: HashMap::new(),
392 }
393 }
394
395 pub fn register_integration(
397 &mut self,
398 name: &str,
399 integration: Arc<dyn ExternalIntegration + Send + Sync>,
400 config: IntegrationConfig,
401 ) -> SklResult<()> {
402 let circuit_breaker = CircuitBreakerState {
404 state: CircuitState::Closed,
405 failure_count: 0,
406 last_failure: None,
407 config: CircuitBreakerConfig {
408 failure_threshold: 5,
409 reset_timeout: Duration::from_secs(60),
410 success_threshold: 3,
411 },
412 };
413
414 let retry_policy = RetryPolicy {
416 max_attempts: 3,
417 backoff: BackoffStrategy::Exponential {
418 initial: Duration::from_millis(100),
419 max: Duration::from_secs(30),
420 },
421 retry_conditions: vec![
422 RetryCondition::NetworkError,
423 RetryCondition::Timeout,
424 RetryCondition::ServerError,
425 ],
426 };
427
428 self.integrations.insert(name.to_string(), integration);
429 self.configs.insert(name.to_string(), config);
430 self.retry_policies.insert(name.to_string(), retry_policy);
431 self.circuit_breakers
432 .insert(name.to_string(), circuit_breaker);
433
434 Ok(())
435 }
436
437 #[must_use]
439 pub fn get_integration(
440 &self,
441 name: &str,
442 ) -> Option<&Arc<dyn ExternalIntegration + Send + Sync>> {
443 self.integrations.get(name)
444 }
445
446 pub fn send_data(
448 &mut self,
449 integration_name: &str,
450 data: &IntegrationData,
451 ) -> SklResult<IntegrationResponse> {
452 if !self.is_circuit_closed(integration_name) {
454 return Err(SklearsError::InvalidOperation(format!(
455 "Circuit breaker is open for integration '{integration_name}'"
456 )));
457 }
458
459 let integration = self
461 .integrations
462 .get(integration_name)
463 .ok_or_else(|| {
464 SklearsError::InvalidInput(format!("Integration '{integration_name}' not found"))
465 })?
466 .clone();
467
468 self.execute_with_retry(integration_name, || integration.send_data(data))
470 }
471
472 pub fn receive_data(
474 &mut self,
475 integration_name: &str,
476 request: &IntegrationRequest,
477 ) -> SklResult<IntegrationData> {
478 if !self.is_circuit_closed(integration_name) {
480 return Err(SklearsError::InvalidOperation(format!(
481 "Circuit breaker is open for integration '{integration_name}'"
482 )));
483 }
484
485 let integration = self
487 .integrations
488 .get(integration_name)
489 .ok_or_else(|| {
490 SklearsError::InvalidInput(format!("Integration '{integration_name}' not found"))
491 })?
492 .clone();
493
494 self.execute_with_retry(integration_name, || integration.receive_data(request))
496 }
497
498 pub fn execute_operation(
500 &mut self,
501 integration_name: &str,
502 operation: &Operation,
503 ) -> SklResult<OperationResult> {
504 if !self.is_circuit_closed(integration_name) {
506 return Err(SklearsError::InvalidOperation(format!(
507 "Circuit breaker is open for integration '{integration_name}'"
508 )));
509 }
510
511 let integration = self
513 .integrations
514 .get(integration_name)
515 .ok_or_else(|| {
516 SklearsError::InvalidInput(format!("Integration '{integration_name}' not found"))
517 })?
518 .clone();
519
520 self.execute_with_retry(integration_name, || {
522 integration.execute_operation(operation)
523 })
524 }
525
526 #[must_use]
528 pub fn health_check_all(&self) -> HashMap<String, HealthStatus> {
529 let mut results = HashMap::new();
530
531 for (name, integration) in &self.integrations {
532 match integration.health_check() {
533 Ok(status) => {
534 results.insert(name.clone(), status);
535 }
536 Err(e) => {
537 results.insert(
538 name.clone(),
539 HealthStatus {
540 is_healthy: false,
541 response_time: Duration::from_secs(0),
542 last_check: Instant::now(),
543 error_message: Some(e.to_string()),
544 metadata: BTreeMap::new(),
545 },
546 );
547 }
548 }
549 }
550
551 results
552 }
553
554 fn execute_with_retry<T, F>(&mut self, integration_name: &str, mut operation: F) -> SklResult<T>
556 where
557 F: FnMut() -> SklResult<T>,
558 {
559 let retry_policy = self
560 .retry_policies
561 .get(integration_name)
562 .cloned()
563 .unwrap_or_else(|| RetryPolicy {
564 max_attempts: 1,
565 backoff: BackoffStrategy::Fixed(Duration::from_millis(100)),
566 retry_conditions: vec![],
567 });
568
569 let mut attempts = 0;
570 let mut last_error = None;
571
572 while attempts < retry_policy.max_attempts {
573 match operation() {
574 Ok(result) => {
575 self.record_success(integration_name);
577 return Ok(result);
578 }
579 Err(e) => {
580 attempts += 1;
581 last_error = Some(e.clone());
582
583 self.record_failure(integration_name);
585
586 if attempts < retry_policy.max_attempts
588 && self.should_retry(&e, &retry_policy.retry_conditions)
589 {
590 let delay = self.calculate_backoff(&retry_policy.backoff, attempts);
592 std::thread::sleep(delay);
593 } else {
594 break;
595 }
596 }
597 }
598 }
599
600 Err(last_error.unwrap_or_else(|| {
601 SklearsError::InvalidOperation("Operation failed without error details".to_string())
602 }))
603 }
604
605 fn is_circuit_closed(&mut self, integration_name: &str) -> bool {
607 if let Some(circuit_breaker) = self.circuit_breakers.get_mut(integration_name) {
608 match circuit_breaker.state {
609 CircuitState::Closed => true,
610 CircuitState::Open => {
611 if let Some(last_failure) = circuit_breaker.last_failure {
613 if last_failure.elapsed() >= circuit_breaker.config.reset_timeout {
614 circuit_breaker.state = CircuitState::HalfOpen;
615 return true;
616 }
617 }
618 false
619 }
620 CircuitState::HalfOpen => true,
621 }
622 } else {
623 true }
625 }
626
627 fn record_success(&mut self, integration_name: &str) {
629 if let Some(circuit_breaker) = self.circuit_breakers.get_mut(integration_name) {
630 match circuit_breaker.state {
631 CircuitState::HalfOpen => {
632 circuit_breaker.failure_count = 0;
633 circuit_breaker.state = CircuitState::Closed;
634 }
635 CircuitState::Closed => {
636 circuit_breaker.failure_count = 0;
637 }
638 CircuitState::Open => {
639 }
641 }
642 }
643 }
644
645 fn record_failure(&mut self, integration_name: &str) {
647 if let Some(circuit_breaker) = self.circuit_breakers.get_mut(integration_name) {
648 circuit_breaker.failure_count += 1;
649 circuit_breaker.last_failure = Some(Instant::now());
650
651 if circuit_breaker.failure_count >= circuit_breaker.config.failure_threshold {
652 circuit_breaker.state = CircuitState::Open;
653 }
654 }
655 }
656
657 fn should_retry(&self, error: &SklearsError, conditions: &[RetryCondition]) -> bool {
659 for condition in conditions {
660 match condition {
661 RetryCondition::NetworkError => {
662 if error.to_string().contains("network")
664 || error.to_string().contains("connection")
665 {
666 return true;
667 }
668 }
669 RetryCondition::Timeout => {
670 if error.to_string().contains("timeout") {
671 return true;
672 }
673 }
674 RetryCondition::ServerError => {
675 if error.to_string().contains("server error") || error.to_string().contains('5')
676 {
677 return true;
678 }
679 }
680 RetryCondition::StatusCode(_codes) => {
681 }
684 RetryCondition::Custom(_) => {
685 }
687 }
688 }
689 false
690 }
691
692 fn calculate_backoff(&self, strategy: &BackoffStrategy, attempt: usize) -> Duration {
694 match strategy {
695 BackoffStrategy::Fixed(duration) => *duration,
696 BackoffStrategy::Exponential { initial, max } => {
697 let delay = initial.as_millis() * (2_u128.pow(attempt as u32 - 1));
698 Duration::from_millis(delay.min(max.as_millis()) as u64)
699 }
700 BackoffStrategy::Linear { initial, increment } => {
701 *initial + *increment * (attempt as u32 - 1)
702 }
703 }
704 }
705}
706
707#[derive(Debug)]
709pub struct RestApiIntegration {
710 config: Option<IntegrationConfig>,
711 base_url: String,
712}
713
714impl RestApiIntegration {
715 #[must_use]
717 pub fn new(base_url: String) -> Self {
718 Self {
719 config: None,
720 base_url,
721 }
722 }
723}
724
725impl ExternalIntegration for RestApiIntegration {
726 fn initialize(&mut self, config: &IntegrationConfig) -> SklResult<()> {
727 self.config = Some(config.clone());
728 self.base_url = config.connection.endpoint.clone();
729 Ok(())
730 }
731
732 fn health_check(&self) -> SklResult<HealthStatus> {
733 let start_time = Instant::now();
734
735 let is_healthy = true; Ok(HealthStatus {
739 is_healthy,
740 response_time: start_time.elapsed(),
741 last_check: Instant::now(),
742 error_message: None,
743 metadata: BTreeMap::from([
744 ("endpoint".to_string(), self.base_url.clone()),
745 ("integration_type".to_string(), "REST API".to_string()),
746 ]),
747 })
748 }
749
750 fn send_data(&self, data: &IntegrationData) -> SklResult<IntegrationResponse> {
751 let start_time = Instant::now();
752
753 Ok(IntegrationResponse {
757 success: true,
758 status_code: Some(200),
759 body: Some(b"Success".to_vec()),
760 headers: BTreeMap::from([("Content-Type".to_string(), "application/json".to_string())]),
761 error: None,
762 response_time: start_time.elapsed(),
763 })
764 }
765
766 fn receive_data(&self, request: &IntegrationRequest) -> SklResult<IntegrationData> {
767 Ok(IntegrationData {
771 data_type: "json".to_string(),
772 payload: b"{}".to_vec(),
773 metadata: BTreeMap::from([("source".to_string(), "REST API".to_string())]),
774 content_type: "application/json".to_string(),
775 encoding: Some("utf-8".to_string()),
776 })
777 }
778
779 fn execute_operation(&self, operation: &Operation) -> SklResult<OperationResult> {
780 let start_time = Instant::now();
781
782 Ok(OperationResult {
786 success: true,
787 result: Some(IntegrationData {
788 data_type: "operation_result".to_string(),
789 payload: b"{}".to_vec(),
790 metadata: BTreeMap::new(),
791 content_type: "application/json".to_string(),
792 encoding: Some("utf-8".to_string()),
793 }),
794 error: None,
795 execution_time: start_time.elapsed(),
796 metadata: BTreeMap::from([(
797 "operation_type".to_string(),
798 operation.operation_type.clone(),
799 )]),
800 })
801 }
802
803 fn cleanup(&mut self) -> SklResult<()> {
804 Ok(())
806 }
807}
808
809#[derive(Debug)]
811pub struct DatabaseIntegration {
812 config: Option<IntegrationConfig>,
813 connection_string: String,
814}
815
816impl DatabaseIntegration {
817 #[must_use]
819 pub fn new(connection_string: String) -> Self {
820 Self {
821 config: None,
822 connection_string,
823 }
824 }
825}
826
827impl ExternalIntegration for DatabaseIntegration {
828 fn initialize(&mut self, config: &IntegrationConfig) -> SklResult<()> {
829 self.config = Some(config.clone());
830 self.connection_string = config.connection.endpoint.clone();
831 Ok(())
832 }
833
834 fn health_check(&self) -> SklResult<HealthStatus> {
835 let start_time = Instant::now();
836
837 let is_healthy = true; Ok(HealthStatus {
841 is_healthy,
842 response_time: start_time.elapsed(),
843 last_check: Instant::now(),
844 error_message: None,
845 metadata: BTreeMap::from([
846 (
847 "connection_string".to_string(),
848 self.connection_string.clone(),
849 ),
850 ("integration_type".to_string(), "Database".to_string()),
851 ]),
852 })
853 }
854
855 fn send_data(&self, data: &IntegrationData) -> SklResult<IntegrationResponse> {
856 let start_time = Instant::now();
857
858 Ok(IntegrationResponse {
861 success: true,
862 status_code: None,
863 body: None,
864 headers: BTreeMap::new(),
865 error: None,
866 response_time: start_time.elapsed(),
867 })
868 }
869
870 fn receive_data(&self, request: &IntegrationRequest) -> SklResult<IntegrationData> {
871 Ok(IntegrationData {
874 data_type: "sql_result".to_string(),
875 payload: b"[]".to_vec(),
876 metadata: BTreeMap::from([("source".to_string(), "Database".to_string())]),
877 content_type: "application/json".to_string(),
878 encoding: Some("utf-8".to_string()),
879 })
880 }
881
882 fn execute_operation(&self, operation: &Operation) -> SklResult<OperationResult> {
883 let start_time = Instant::now();
884
885 Ok(OperationResult {
888 success: true,
889 result: None,
890 error: None,
891 execution_time: start_time.elapsed(),
892 metadata: BTreeMap::from([(
893 "operation_type".to_string(),
894 operation.operation_type.clone(),
895 )]),
896 })
897 }
898
899 fn cleanup(&mut self) -> SklResult<()> {
900 Ok(())
902 }
903}
904
905impl Default for ExternalIntegrationManager {
906 fn default() -> Self {
907 Self::new()
908 }
909}
910
911impl Default for TimeoutConfig {
912 fn default() -> Self {
913 Self {
914 connect_timeout: Duration::from_secs(10),
915 request_timeout: Duration::from_secs(30),
916 read_timeout: Duration::from_secs(30),
917 }
918 }
919}
920
921impl Default for HealthCheckConfig {
922 fn default() -> Self {
923 Self {
924 endpoint: None,
925 interval: Duration::from_secs(30),
926 timeout: Duration::from_secs(5),
927 failure_threshold: 3,
928 success_threshold: 2,
929 }
930 }
931}
932
933#[allow(non_snake_case)]
934#[cfg(test)]
935mod tests {
936 use super::*;
937 use std::sync::Arc;
938
939 #[test]
940 fn test_integration_manager_creation() {
941 let manager = ExternalIntegrationManager::new();
942 assert!(manager.integrations.is_empty());
943 assert!(manager.configs.is_empty());
944 }
945
946 #[test]
947 fn test_rest_api_integration() {
948 let mut integration = RestApiIntegration::new("https://api.example.com".to_string());
949
950 let config = IntegrationConfig {
951 name: "test_api".to_string(),
952 integration_type: IntegrationType::RestApi,
953 connection: ConnectionConfig {
954 endpoint: "https://api.example.com".to_string(),
955 pool_size: Some(10),
956 keep_alive: true,
957 tls: None,
958 headers: BTreeMap::new(),
959 query_params: BTreeMap::new(),
960 },
961 auth: None,
962 timeout: TimeoutConfig::default(),
963 rate_limit: None,
964 health_check: HealthCheckConfig::default(),
965 };
966
967 assert!(integration.initialize(&config).is_ok());
968 assert!(integration.health_check().is_ok());
969 }
970
971 #[test]
972 fn test_database_integration() {
973 let mut integration =
974 DatabaseIntegration::new("postgresql://localhost:5432/test".to_string());
975
976 let config = IntegrationConfig {
977 name: "test_db".to_string(),
978 integration_type: IntegrationType::Database,
979 connection: ConnectionConfig {
980 endpoint: "postgresql://localhost:5432/test".to_string(),
981 pool_size: Some(5),
982 keep_alive: true,
983 tls: None,
984 headers: BTreeMap::new(),
985 query_params: BTreeMap::new(),
986 },
987 auth: None,
988 timeout: TimeoutConfig::default(),
989 rate_limit: None,
990 health_check: HealthCheckConfig::default(),
991 };
992
993 assert!(integration.initialize(&config).is_ok());
994 assert!(integration.health_check().is_ok());
995 }
996
997 #[test]
998 fn test_integration_manager_registration() {
999 let mut manager = ExternalIntegrationManager::new();
1000 let integration = Arc::new(RestApiIntegration::new(
1001 "https://api.example.com".to_string(),
1002 ));
1003
1004 let config = IntegrationConfig {
1005 name: "test_api".to_string(),
1006 integration_type: IntegrationType::RestApi,
1007 connection: ConnectionConfig {
1008 endpoint: "https://api.example.com".to_string(),
1009 pool_size: Some(10),
1010 keep_alive: true,
1011 tls: None,
1012 headers: BTreeMap::new(),
1013 query_params: BTreeMap::new(),
1014 },
1015 auth: None,
1016 timeout: TimeoutConfig::default(),
1017 rate_limit: None,
1018 health_check: HealthCheckConfig::default(),
1019 };
1020
1021 assert!(manager
1022 .register_integration("test_api", integration, config)
1023 .is_ok());
1024 assert!(manager.get_integration("test_api").is_some());
1025 }
1026
1027 #[test]
1028 fn test_circuit_breaker() {
1029 let circuit_breaker = CircuitBreakerState {
1030 state: CircuitState::Closed,
1031 failure_count: 0,
1032 last_failure: None,
1033 config: CircuitBreakerConfig {
1034 failure_threshold: 3,
1035 reset_timeout: Duration::from_secs(60),
1036 success_threshold: 2,
1037 },
1038 };
1039
1040 assert_eq!(circuit_breaker.state, CircuitState::Closed);
1041 assert_eq!(circuit_breaker.failure_count, 0);
1042 }
1043
1044 #[test]
1045 fn test_retry_policy() {
1046 let retry_policy = RetryPolicy {
1047 max_attempts: 3,
1048 backoff: BackoffStrategy::Exponential {
1049 initial: Duration::from_millis(100),
1050 max: Duration::from_secs(30),
1051 },
1052 retry_conditions: vec![RetryCondition::NetworkError, RetryCondition::Timeout],
1053 };
1054
1055 assert_eq!(retry_policy.max_attempts, 3);
1056 assert_eq!(retry_policy.retry_conditions.len(), 2);
1057 }
1058
1059 #[test]
1060 fn test_health_status() {
1061 let status = HealthStatus {
1062 is_healthy: true,
1063 response_time: Duration::from_millis(50),
1064 last_check: Instant::now(),
1065 error_message: None,
1066 metadata: BTreeMap::from([("service".to_string(), "test".to_string())]),
1067 };
1068
1069 assert!(status.is_healthy);
1070 assert!(status.error_message.is_none());
1071 assert_eq!(status.metadata.len(), 1);
1072 }
1073}