batata-client 0.0.2

Rust client for Batata/Nacos service discovery and configuration management
Documentation

use dashmap::DashMap;

use crate::api::naming::{Instance, Service};
use crate::common::build_service_key;

/// Service information cache
pub struct ServiceInfoCache {
    /// Cached services: key -> Service
    services: DashMap<String, Service>,
}

impl ServiceInfoCache {
    pub fn new() -> Self {
        Self {
            services: DashMap::new(),
        }
    }

    /// Get service from cache
    pub fn get(&self, namespace: &str, group_name: &str, service_name: &str) -> Option<Service> {
        let key = build_service_key(service_name, group_name, namespace);
        self.services.get(&key).map(|r| r.value().clone())
    }

    /// Put service into cache
    pub fn put(&self, namespace: &str, service: Service) {
        let key = build_service_key(&service.name, &service.group_name, namespace);
        self.services.insert(key, service);
    }

    /// Remove service from cache
    pub fn remove(
        &self,
        namespace: &str,
        group_name: &str,
        service_name: &str,
    ) -> Option<Service> {
        let key = build_service_key(service_name, group_name, namespace);
        self.services.remove(&key).map(|(_, v)| v)
    }

    /// Check if service exists in cache
    pub fn contains(&self, namespace: &str, group_name: &str, service_name: &str) -> bool {
        let key = build_service_key(service_name, group_name, namespace);
        self.services.contains_key(&key)
    }

    /// Get all healthy instances for a service
    pub fn get_healthy_instances(
        &self,
        namespace: &str,
        group_name: &str,
        service_name: &str,
    ) -> Vec<Instance> {
        self.get(namespace, group_name, service_name)
            .map(|s| s.hosts.into_iter().filter(|i| i.healthy && i.enabled).collect())
            .unwrap_or_default()
    }

    /// Get all instances for a service
    pub fn get_all_instances(
        &self,
        namespace: &str,
        group_name: &str,
        service_name: &str,
    ) -> Vec<Instance> {
        self.get(namespace, group_name, service_name)
            .map(|s| s.hosts)
            .unwrap_or_default()
    }

    /// Update service instances
    pub fn update_instances(
        &self,
        namespace: &str,
        group_name: &str,
        service_name: &str,
        instances: Vec<Instance>,
    ) {
        let key = build_service_key(service_name, group_name, namespace);
        if let Some(mut service) = self.services.get_mut(&key) {
            service.hosts = instances;
        } else {
            let mut service = Service::new(service_name, group_name);
            service.hosts = instances;
            self.services.insert(key, service);
        }
    }

    /// Get all cached service keys
    pub fn keys(&self) -> Vec<String> {
        self.services.iter().map(|r| r.key().clone()).collect()
    }

    /// Clear all cached services
    pub fn clear(&self) {
        self.services.clear();
    }

    /// Get cache size
    pub fn len(&self) -> usize {
        self.services.len()
    }

    /// Check if cache is empty
    pub fn is_empty(&self) -> bool {
        self.services.is_empty()
    }
}

impl Default for ServiceInfoCache {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_service_info_cache() {
        let cache = ServiceInfoCache::new();

        let mut service = Service::new("test-service", "DEFAULT_GROUP");
        service.hosts.push(Instance::new("127.0.0.1", 8080));

        cache.put("public", service);

        assert!(cache.contains("public", "DEFAULT_GROUP", "test-service"));

        let instances = cache.get_all_instances("public", "DEFAULT_GROUP", "test-service");
        assert_eq!(instances.len(), 1);
        assert_eq!(instances[0].ip, "127.0.0.1");
    }
}