Skip to main content

tatara_core/catalog/
mod.rs

1//! Service catalog types for tatara's consul-like service registry.
2//!
3//! Services are automatically registered when allocations start and
4//! deregistered when they stop or become unhealthy. The catalog is
5//! replicated via Raft for consistency and propagated via gossip for
6//! fast local lookups.
7
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12/// Health status of a service instance.
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
14#[serde(rename_all = "snake_case")]
15pub enum ServiceHealth {
16    Passing,
17    Warning,
18    Critical,
19    Maintenance,
20}
21
22impl Default for ServiceHealth {
23    fn default() -> Self {
24        Self::Passing
25    }
26}
27
28/// A registered service instance in the catalog.
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct ServiceEntry {
31    /// Human-readable service name (e.g., "hanabi", "lilitu-backend").
32    pub service_name: String,
33
34    /// Unique instance ID (e.g., "hanabi-i-{alloc_id}").
35    pub service_id: String,
36
37    /// Node hosting this instance.
38    pub node_id: String,
39
40    /// IP address or hostname.
41    pub address: String,
42
43    /// Port number.
44    pub port: u16,
45
46    /// Optional tags for filtering (e.g., ["production", "v2.1"]).
47    #[serde(default)]
48    pub tags: Vec<String>,
49
50    /// Arbitrary metadata.
51    #[serde(default)]
52    pub meta: HashMap<String, String>,
53
54    /// Current health status.
55    #[serde(default)]
56    pub health: ServiceHealth,
57
58    /// When this instance was registered.
59    pub registered_at: DateTime<Utc>,
60
61    /// Allocation ID that owns this service instance.
62    pub alloc_id: Option<String>,
63}
64
65/// Query parameters for catalog lookups.
66#[derive(Debug, Clone, Default)]
67pub struct ServiceQuery {
68    /// Service name to look up.
69    pub service: String,
70
71    /// Optional tag filter.
72    pub tag: Option<String>,
73
74    /// Only return healthy instances.
75    pub healthy_only: bool,
76
77    /// Prefer instances near this node.
78    pub near: Option<String>,
79}
80
81impl ServiceEntry {
82    /// Check if this entry matches a query.
83    pub fn matches(&self, query: &ServiceQuery) -> bool {
84        if self.service_name != query.service {
85            return false;
86        }
87        if let Some(tag) = &query.tag {
88            if !self.tags.contains(tag) {
89                return false;
90            }
91        }
92        if query.healthy_only && self.health != ServiceHealth::Passing {
93            return false;
94        }
95        true
96    }
97}