cf_system_sdk_directory/grpc/
client.rs1use anyhow::Result;
6use async_trait::async_trait;
7use tonic::transport::Channel;
8
9use crate::api::{DirectoryClient, RegisterInstanceInfo, ServiceEndpoint, ServiceInstanceInfo};
10use toolkit_transport_grpc::client::{GrpcClientConfig, connect_with_retry};
11
12use crate::{
13 DeregisterInstanceRequest, DirectoryServiceClient, GrpcServiceEndpoint, HeartbeatRequest,
14 ListInstancesRequest, RegisterInstanceRequest, ResolveGrpcServiceRequest,
15};
16
17pub struct DirectoryGrpcClient {
25 inner: DirectoryServiceClient<Channel>,
26}
27
28impl DirectoryGrpcClient {
29 pub async fn connect(uri: impl Into<String>) -> Result<Self> {
37 let cfg = GrpcClientConfig::new("directory");
38 Self::connect_with_retry(uri, &cfg).await
39 }
40
41 pub async fn connect_with_retry(
49 uri: impl Into<String>,
50 cfg: &GrpcClientConfig,
51 ) -> Result<Self> {
52 let channel: Channel = connect_with_retry(uri, cfg).await?;
53 Ok(Self {
54 inner: DirectoryServiceClient::new(channel),
55 })
56 }
57
58 pub async fn connect_no_retry(uri: impl Into<String>, cfg: &GrpcClientConfig) -> Result<Self> {
66 let uri_string = uri.into();
67
68 let endpoint = tonic::transport::Endpoint::from_shared(uri_string)?
70 .connect_timeout(cfg.connect_timeout)
71 .timeout(cfg.rpc_timeout);
72
73 let channel = endpoint.connect().await?;
75
76 if cfg.enable_tracing {
77 tracing::debug!(
78 service_name = cfg.service_name,
79 connect_timeout_ms = cfg.connect_timeout.as_millis(),
80 rpc_timeout_ms = cfg.rpc_timeout.as_millis(),
81 "directory gRPC client connected"
82 );
83 }
84
85 Ok(Self {
86 inner: DirectoryServiceClient::new(channel),
87 })
88 }
89
90 #[must_use]
92 pub fn from_channel(channel: Channel) -> Self {
93 Self {
94 inner: DirectoryServiceClient::new(channel),
95 }
96 }
97}
98
99#[async_trait]
100impl DirectoryClient for DirectoryGrpcClient {
101 async fn resolve_grpc_service(&self, service_name: &str) -> Result<ServiceEndpoint> {
102 let mut client = self.inner.clone();
103 let request = tonic::Request::new(ResolveGrpcServiceRequest {
104 service_name: service_name.to_owned(),
105 });
106
107 let response = client
108 .resolve_grpc_service(request)
109 .await
110 .map_err(|e| anyhow::anyhow!("gRPC call failed: {e}"))?;
111
112 let proto_response = response.into_inner();
113 Ok(ServiceEndpoint::new(proto_response.endpoint_uri))
114 }
115
116 async fn list_instances(&self, gear: &str) -> Result<Vec<ServiceInstanceInfo>> {
117 let mut client = self.inner.clone();
118 let request = tonic::Request::new(ListInstancesRequest {
119 gear_name: gear.to_owned(),
120 });
121
122 let response = client
123 .list_instances(request)
124 .await
125 .map_err(|e| anyhow::anyhow!("gRPC call failed: {e}"))?;
126
127 let proto_response = response.into_inner();
128
129 let instances = proto_response
131 .instances
132 .into_iter()
133 .map(|proto_inst| ServiceInstanceInfo {
134 gear: proto_inst.gear_name,
135 instance_id: proto_inst.instance_id,
136 endpoint: ServiceEndpoint::new(proto_inst.endpoint_uri),
137 version: if proto_inst.version.is_empty() {
138 None
139 } else {
140 Some(proto_inst.version)
141 },
142 })
143 .collect();
144
145 Ok(instances)
146 }
147
148 async fn register_instance(&self, info: RegisterInstanceInfo) -> Result<()> {
149 let mut client = self.inner.clone();
150
151 let grpc_services = info
153 .grpc_services
154 .into_iter()
155 .map(|(name, ep)| GrpcServiceEndpoint {
156 service_name: name,
157 endpoint_uri: ep.uri,
158 })
159 .collect();
160
161 let req = RegisterInstanceRequest {
162 gear_name: info.gear,
163 instance_id: info.instance_id,
164 grpc_services,
165 version: info.version.unwrap_or_default(),
166 };
167
168 client
169 .register_instance(tonic::Request::new(req))
170 .await
171 .map_err(|e| anyhow::anyhow!("gRPC register_instance failed: {e}"))?;
172
173 Ok(())
174 }
175
176 async fn deregister_instance(&self, gear: &str, instance_id: &str) -> Result<()> {
177 let mut client = self.inner.clone();
178
179 let req = DeregisterInstanceRequest {
180 gear_name: gear.to_owned(),
181 instance_id: instance_id.to_owned(),
182 };
183
184 client
185 .deregister_instance(tonic::Request::new(req))
186 .await
187 .map_err(|e| anyhow::anyhow!("gRPC deregister_instance failed: {e}"))?;
188
189 Ok(())
190 }
191
192 async fn send_heartbeat(&self, gear: &str, instance_id: &str) -> Result<()> {
193 let mut client = self.inner.clone();
194
195 let req = HeartbeatRequest {
196 gear_name: gear.to_owned(),
197 instance_id: instance_id.to_owned(),
198 };
199
200 client
201 .heartbeat(tonic::Request::new(req))
202 .await
203 .map_err(|e| anyhow::anyhow!("gRPC heartbeat failed: {e}"))?;
204
205 Ok(())
206 }
207}
208
209#[cfg(test)]
210#[cfg_attr(coverage_nightly, coverage(off))]
211mod tests {
212 use super::*;
213
214 #[tokio::test]
215 async fn test_grpc_client_can_be_constructed() {
216 let endpoint = tonic::transport::Endpoint::from_static("http://[::1]:50051");
218
219 let channel_result = endpoint.connect().await;
222
223 if let Ok(channel) = channel_result {
225 let _client = DirectoryGrpcClient::from_channel(channel);
226 }
227 }
228}