cf_system_sdk_directory/
api.rs

1//! Directory API - contract for service discovery and instance resolution
2//!
3//! This module defines the core traits and types for the directory service API.
4
5use anyhow::Result;
6use async_trait::async_trait;
7
8/// Represents an endpoint where a service can be reached
9#[derive(Clone, Debug, PartialEq, Eq, Hash)]
10pub struct ServiceEndpoint {
11    pub uri: String,
12}
13
14impl ServiceEndpoint {
15    pub fn new(uri: impl Into<String>) -> Self {
16        Self { uri: uri.into() }
17    }
18
19    #[must_use]
20    pub fn http(host: &str, port: u16) -> Self {
21        Self {
22            uri: format!("{}://{}:{}", "http", host, port),
23        }
24    }
25
26    #[must_use]
27    pub fn https(host: &str, port: u16) -> Self {
28        Self {
29            uri: format!("https://{host}:{port}"),
30        }
31    }
32
33    pub fn uds(path: impl AsRef<std::path::Path>) -> Self {
34        Self {
35            uri: format!("unix://{}", path.as_ref().display()),
36        }
37    }
38}
39
40/// Information about a service instance
41#[derive(Debug, Clone)]
42pub struct ServiceInstanceInfo {
43    /// Module name this instance belongs to
44    pub module: String,
45    /// Unique instance identifier
46    pub instance_id: String,
47    /// Primary endpoint for the instance
48    pub endpoint: ServiceEndpoint,
49    /// Optional version string
50    pub version: Option<String>,
51}
52
53/// Information for registering a new module instance
54#[derive(Debug, Clone)]
55pub struct RegisterInstanceInfo {
56    /// Module name
57    pub module: String,
58    /// Unique instance identifier
59    pub instance_id: String,
60    /// Map of gRPC service name to endpoint
61    pub grpc_services: Vec<(String, ServiceEndpoint)>,
62    /// Optional version string
63    pub version: Option<String>,
64}
65
66/// Directory API trait for service discovery and instance management
67///
68/// This trait defines the contract for interacting with the module directory.
69/// It can be implemented by:
70/// - A local implementation that delegates to `ModuleManager`
71/// - A gRPC client for out-of-process modules
72#[async_trait]
73pub trait DirectoryClient: Send + Sync {
74    /// Resolve a gRPC service by its logical name to an endpoint
75    async fn resolve_grpc_service(&self, service_name: &str) -> Result<ServiceEndpoint>;
76
77    /// List all service instances for a given module
78    async fn list_instances(&self, module: &str) -> Result<Vec<ServiceInstanceInfo>>;
79
80    /// Register a new module instance with the directory
81    async fn register_instance(&self, info: RegisterInstanceInfo) -> Result<()>;
82
83    /// Deregister a module instance (for graceful shutdown)
84    async fn deregister_instance(&self, module: &str, instance_id: &str) -> Result<()>;
85
86    /// Send a heartbeat for a module instance to indicate it's still alive
87    async fn send_heartbeat(&self, module: &str, instance_id: &str) -> Result<()>;
88}
89
90#[cfg(test)]
91#[cfg_attr(coverage_nightly, coverage(off))]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn test_service_endpoint_creation() {
97        let http_ep = ServiceEndpoint::http("localhost", 8080);
98        assert_eq!(http_ep.uri, concat!("http", "://localhost:8080"));
99
100        let https_endpoint = ServiceEndpoint::https("localhost", 8443);
101        assert_eq!(https_endpoint.uri, "https://localhost:8443");
102
103        let uds_ep = ServiceEndpoint::uds("/tmp/socket.sock");
104        assert!(uds_ep.uri.starts_with("unix://"));
105        assert!(uds_ep.uri.contains("socket.sock"));
106
107        let custom_ep = ServiceEndpoint::new(concat!("http", "://example.com"));
108        assert_eq!(custom_ep.uri, concat!("http", "://example.com"));
109    }
110
111    #[test]
112    fn test_register_instance_info() {
113        let info = RegisterInstanceInfo {
114            module: "test_module".to_owned(),
115            instance_id: "instance1".to_owned(),
116            grpc_services: vec![(
117                "test.Service".to_owned(),
118                ServiceEndpoint::http("127.0.0.1", 8001),
119            )],
120            version: Some("1.0.0".to_owned()),
121        };
122
123        assert_eq!(info.module, "test_module");
124        assert_eq!(info.instance_id, "instance1");
125        assert_eq!(info.grpc_services.len(), 1);
126    }
127}