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