use std::collections::HashMap;
use chrono::{DateTime, Utc};
use super::types::{
ProviderCallHistory, ServiceCall, ServiceCallError, ServiceCallHistory,
ServiceCallHistoryCounts, MAX_CALL_HISTORY, MAX_RESET_COUNTS,
};
use crate::error::WalletError;
struct NamedService<T: ?Sized> {
name: String,
service: Box<T>,
}
pub struct ServiceCollection<T: ?Sized> {
service_name: String,
services: Vec<NamedService<T>>,
index: usize,
since: DateTime<Utc>,
history_by_provider: HashMap<String, InternalProviderHistory>,
}
struct InternalProviderHistory {
service_name: String,
provider_name: String,
calls: Vec<ServiceCall>,
total_counts: ServiceCallHistoryCounts,
reset_counts: Vec<ServiceCallHistoryCounts>,
}
impl<T: ?Sized> ServiceCollection<T> {
pub fn new(service_name: &str) -> Self {
let now = Utc::now();
Self {
service_name: service_name.to_string(),
services: Vec::new(),
index: 0,
since: now,
history_by_provider: HashMap::new(),
}
}
pub fn add(&mut self, name: &str, service: Box<T>) {
self.services.push(NamedService {
name: name.to_string(),
service,
});
}
pub fn len(&self) -> usize {
self.services.len()
}
pub fn is_empty(&self) -> bool {
self.services.is_empty()
}
pub fn service_to_call(&self) -> Option<(&T, &str)> {
if self.services.is_empty() {
return None;
}
let entry = &self.services[self.index];
Some((&*entry.service, &entry.name))
}
pub fn service_name(&self) -> &str {
&self.service_name
}
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> usize {
if !self.services.is_empty() {
self.index = (self.index + 1) % self.services.len();
}
self.index
}
pub fn all_services(&self) -> impl Iterator<Item = (&T, &str)> {
self.services
.iter()
.map(|entry| (&*entry.service, entry.name.as_str()))
}
pub fn move_service_to_last(&mut self, provider_name: &str) {
if let Some(pos) = self.services.iter().position(|s| s.name == provider_name) {
let entry = self.services.remove(pos);
self.services.push(entry);
if self.index >= self.services.len() {
self.index = 0;
}
}
}
pub fn add_service_call_success(
&mut self,
provider: &str,
call: ServiceCall,
_result: Option<String>,
) {
let h = self.ensure_provider_history(provider);
h.calls.insert(0, call);
h.calls.truncate(MAX_CALL_HISTORY);
let now = Utc::now();
h.total_counts.until = now;
h.total_counts.success += 1;
if let Some(rc) = h.reset_counts.first_mut() {
rc.until = now;
rc.success += 1;
}
}
pub fn add_service_call_error(
&mut self,
provider: &str,
mut call: ServiceCall,
error: &WalletError,
) {
call.success = false;
call.error = Some(ServiceCallError::from_wallet_error(error));
let h = self.ensure_provider_history(provider);
h.calls.insert(0, call);
h.calls.truncate(MAX_CALL_HISTORY);
let now = Utc::now();
h.total_counts.until = now;
h.total_counts.failure += 1;
h.total_counts.error += 1;
if let Some(rc) = h.reset_counts.first_mut() {
rc.until = now;
rc.failure += 1;
rc.error += 1;
}
}
pub fn add_service_call_failure(&mut self, provider: &str, call: ServiceCall) {
let h = self.ensure_provider_history(provider);
h.calls.insert(0, call);
h.calls.truncate(MAX_CALL_HISTORY);
let now = Utc::now();
h.total_counts.until = now;
h.total_counts.failure += 1;
if let Some(rc) = h.reset_counts.first_mut() {
rc.until = now;
rc.failure += 1;
}
}
pub fn get_service_call_history(&mut self, reset: bool) -> ServiceCallHistory {
let now = Utc::now();
let mut history_by_provider = HashMap::new();
for (name, h) in &mut self.history_by_provider {
let provider_history = ProviderCallHistory {
service_name: h.service_name.clone(),
provider_name: h.provider_name.clone(),
calls: h.calls.clone(),
total_counts: h.total_counts.clone(),
reset_counts: h.reset_counts.clone(),
};
if reset {
if let Some(rc) = h.reset_counts.first_mut() {
rc.until = now;
}
h.reset_counts
.insert(0, ServiceCallHistoryCounts::new_at(now));
h.reset_counts.truncate(MAX_RESET_COUNTS);
}
history_by_provider.insert(name.clone(), provider_history);
}
ServiceCallHistory {
service_name: self.service_name.clone(),
history_by_provider,
}
}
fn ensure_provider_history(&mut self, provider_name: &str) -> &mut InternalProviderHistory {
self.history_by_provider
.entry(provider_name.to_string())
.or_insert_with(|| {
let now = Utc::now();
InternalProviderHistory {
service_name: self.service_name.clone(),
provider_name: provider_name.to_string(),
calls: Vec::new(),
total_counts: ServiceCallHistoryCounts {
success: 0,
failure: 0,
error: 0,
since: self.since,
until: now,
},
reset_counts: vec![ServiceCallHistoryCounts {
success: 0,
failure: 0,
error: 0,
since: self.since,
until: now,
}],
}
})
}
}