batata-consul-client 0.0.2

Rust client for HashiCorp Consul or batata
Documentation
use std::collections::HashMap;
use std::time::Duration;

use serde::{Deserialize, Serialize};

/// Query options for read operations
#[derive(Clone, Debug, Default)]
pub struct QueryOptions {
    /// Datacenter to query
    pub datacenter: Option<String>,
    /// AllowStale allows stale queries
    pub allow_stale: bool,
    /// RequireConsistent forces consistent queries
    pub require_consistent: bool,
    /// UseCache uses Consul's caching feature
    pub use_cache: bool,
    /// MaxAge limits how old a cached value will be returned
    pub max_age: Option<Duration>,
    /// StaleIfError allows stale read on error
    pub stale_if_error: Option<Duration>,
    /// WaitIndex for blocking queries
    pub wait_index: u64,
    /// WaitTime for blocking queries
    pub wait_time: Option<Duration>,
    /// Token for ACL authentication
    pub token: Option<String>,
    /// Namespace (Enterprise only)
    pub namespace: Option<String>,
    /// Partition (Enterprise only)
    pub partition: Option<String>,
    /// Near sorts by nearest node
    pub near: Option<String>,
    /// NodeMeta filters by node metadata
    pub node_meta: HashMap<String, String>,
    /// Filter expression for filtering results
    pub filter: Option<String>,
}

impl QueryOptions {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn with_token(mut self, token: &str) -> Self {
        self.token = Some(token.to_string());
        self
    }

    pub fn with_datacenter(mut self, dc: &str) -> Self {
        self.datacenter = Some(dc.to_string());
        self
    }

    pub fn with_namespace(mut self, ns: &str) -> Self {
        self.namespace = Some(ns.to_string());
        self
    }

    pub fn with_wait(mut self, index: u64, wait_time: Duration) -> Self {
        self.wait_index = index;
        self.wait_time = Some(wait_time);
        self
    }

    pub fn with_stale(mut self) -> Self {
        self.allow_stale = true;
        self
    }

    pub fn with_consistent(mut self) -> Self {
        self.require_consistent = true;
        self
    }
}

/// Write options for write operations
#[derive(Clone, Debug, Default)]
pub struct WriteOptions {
    /// Datacenter to write to
    pub datacenter: Option<String>,
    /// Token for ACL authentication
    pub token: Option<String>,
    /// Namespace (Enterprise only)
    pub namespace: Option<String>,
    /// Partition (Enterprise only)
    pub partition: Option<String>,
}

impl WriteOptions {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn with_token(mut self, token: &str) -> Self {
        self.token = Some(token.to_string());
        self
    }

    pub fn with_datacenter(mut self, dc: &str) -> Self {
        self.datacenter = Some(dc.to_string());
        self
    }

    pub fn with_namespace(mut self, ns: &str) -> Self {
        self.namespace = Some(ns.to_string());
        self
    }
}

/// Metadata returned from query operations
#[derive(Clone, Debug, Default)]
pub struct QueryMeta {
    /// LastIndex can be used for blocking queries
    pub last_index: u64,
    /// LastContact is the time of last contact from leader
    pub last_contact: Duration,
    /// KnownLeader indicates if there is a known leader
    pub known_leader: bool,
    /// RequestTime is how long the request took
    pub request_time: Duration,
    /// AddressTranslationEnabled indicates if address translation is enabled
    pub address_translation_enabled: bool,
    /// CacheHit indicates if the response was a cache hit
    pub cache_hit: bool,
    /// CacheAge is how old the cached response is
    pub cache_age: Option<Duration>,
}

/// Metadata returned from write operations
#[derive(Clone, Debug, Default)]
pub struct WriteMeta {
    /// RequestTime is how long the request took
    pub request_time: Duration,
}

/// Node represents a Consul node
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Node {
    /// Node ID
    #[serde(rename = "ID")]
    pub id: String,
    /// Node name
    pub node: String,
    /// Node address
    pub address: String,
    /// Datacenter
    pub datacenter: String,
    /// Tagged addresses
    #[serde(default)]
    pub tagged_addresses: HashMap<String, String>,
    /// Node metadata
    #[serde(default)]
    pub meta: HashMap<String, String>,
    /// Create index
    pub create_index: u64,
    /// Modify index
    pub modify_index: u64,
}

/// Service definition for a node
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct AgentService {
    /// Service ID
    #[serde(rename = "ID")]
    pub id: String,
    /// Service name
    pub service: String,
    /// Service tags
    #[serde(default)]
    pub tags: Vec<String>,
    /// Service port
    pub port: u16,
    /// Service address
    #[serde(default)]
    pub address: String,
    /// Enable tag override
    #[serde(default)]
    pub enable_tag_override: bool,
    /// Service metadata
    #[serde(default)]
    pub meta: HashMap<String, String>,
    /// Service weights
    #[serde(default)]
    pub weights: Option<ServiceWeights>,
}

/// Service weights for load balancing
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct ServiceWeights {
    /// Passing weight
    pub passing: i32,
    /// Warning weight
    pub warning: i32,
}

impl Default for ServiceWeights {
    fn default() -> Self {
        Self {
            passing: 1,
            warning: 1,
        }
    }
}

/// Catalog service definition
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct CatalogService {
    /// Node ID
    #[serde(rename = "ID")]
    pub id: String,
    /// Node name
    pub node: String,
    /// Node address
    pub address: String,
    /// Datacenter
    pub datacenter: String,
    /// Tagged addresses
    #[serde(default)]
    pub tagged_addresses: HashMap<String, String>,
    /// Node metadata
    #[serde(default)]
    pub node_meta: HashMap<String, String>,
    /// Service ID
    #[serde(rename = "ServiceID")]
    pub service_id: String,
    /// Service name
    pub service_name: String,
    /// Service tags
    #[serde(default)]
    pub service_tags: Vec<String>,
    /// Service address
    #[serde(default)]
    pub service_address: String,
    /// Service port
    pub service_port: u16,
    /// Service metadata
    #[serde(default)]
    pub service_meta: HashMap<String, String>,
    /// Service weights
    #[serde(default)]
    pub service_weights: Option<ServiceWeights>,
    /// Create index
    pub create_index: u64,
    /// Modify index
    pub modify_index: u64,
}

/// Health check definition
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct HealthCheck {
    /// Node name
    pub node: String,
    /// Check ID
    #[serde(rename = "CheckID")]
    pub check_id: String,
    /// Check name
    pub name: String,
    /// Check status (passing, warning, critical)
    pub status: String,
    /// Check notes
    #[serde(default)]
    pub notes: String,
    /// Check output
    #[serde(default)]
    pub output: String,
    /// Service ID
    #[serde(rename = "ServiceID")]
    #[serde(default)]
    pub service_id: String,
    /// Service name
    #[serde(default)]
    pub service_name: String,
    /// Service tags
    #[serde(default)]
    pub service_tags: Vec<String>,
    /// Check type
    #[serde(rename = "Type")]
    #[serde(default)]
    pub check_type: String,
    /// Create index
    pub create_index: u64,
    /// Modify index
    pub modify_index: u64,
}

/// Service entry combining service and health checks
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct ServiceEntry {
    /// Node information
    pub node: Node,
    /// Service information
    pub service: AgentService,
    /// Health checks
    pub checks: Vec<HealthCheck>,
}