use std::fs; use tonic::codegen::InterceptedService; use tonic::transport::{Certificate, Channel, ClientTlsConfig}; use tonic::Request; use crate::auth::AuthInterceptor; use crate::error::{ensure_command_success, ensure_protocol_success, Error}; use crate::generated::mxaccess_gateway::v1::mx_access_gateway_client::MxAccessGatewayClient; use crate::generated::mxaccess_gateway::v1::{ CloseSessionReply, CloseSessionRequest, MxCommandReply, MxCommandRequest, MxEvent, OpenSessionReply, OpenSessionRequest, StreamEventsRequest, }; use crate::options::ClientOptions; use crate::session::Session; pub type RawGatewayClient = MxAccessGatewayClient>; pub type EventStream = std::pin::Pin> + Send + 'static>>; /// Thin owner for the generated gateway client. #[derive(Clone)] pub struct GatewayClient { inner: RawGatewayClient, call_timeout: std::time::Duration, stream_timeout: Option, } impl GatewayClient { pub async fn connect(options: ClientOptions) -> Result { let mut endpoint = Channel::from_shared(options.endpoint().to_owned()).map_err(|source| { Error::InvalidEndpoint { endpoint: options.endpoint().to_owned(), detail: source.to_string(), } })?; endpoint = endpoint.connect_timeout(options.connect_timeout()); if !options.plaintext() { let mut tls = ClientTlsConfig::new(); if let Some(server_name) = options.server_name_override() { tls = tls.domain_name(server_name.to_owned()); } if let Some(ca_file) = options.ca_file() { let certificate = fs::read(ca_file).map_err(|source| Error::InvalidEndpoint { endpoint: options.endpoint().to_owned(), detail: format!("failed to read CA file {}: {source}", ca_file.display()), })?; tls = tls.ca_certificate(Certificate::from_pem(certificate)); } endpoint = endpoint.tls_config(tls)?; } let channel = endpoint.connect().await?; let interceptor = AuthInterceptor::new(options.api_key().cloned()); Ok(Self { inner: MxAccessGatewayClient::with_interceptor(channel, interceptor), call_timeout: options.call_timeout(), stream_timeout: options.stream_timeout(), }) } pub fn raw_client(&mut self) -> &mut RawGatewayClient { &mut self.inner } pub fn into_inner(self) -> RawGatewayClient { self.inner } pub fn session(&self, session_id: impl Into) -> Session { Session::new(session_id, self.clone()) } pub async fn open_session_raw( &self, request: OpenSessionRequest, ) -> Result { let mut client = self.inner.clone(); let response = client.open_session(self.unary_request(request)).await?; Ok(response.into_inner()) } pub async fn open_session(&self, request: OpenSessionRequest) -> Result { let reply = self.open_session_raw(request).await?; ensure_protocol_success("open session", reply.protocol_status.as_ref())?; Ok(Session::new(reply.session_id, self.clone())) } pub async fn close_session_raw( &self, request: CloseSessionRequest, ) -> Result { let mut client = self.inner.clone(); let response = client.close_session(self.unary_request(request)).await?; Ok(response.into_inner()) } pub async fn invoke_raw(&self, request: MxCommandRequest) -> Result { let mut client = self.inner.clone(); let response = client.invoke(self.unary_request(request)).await?; Ok(response.into_inner()) } pub async fn invoke(&self, request: MxCommandRequest) -> Result { ensure_command_success(self.invoke_raw(request).await?) } pub async fn stream_events(&self, request: StreamEventsRequest) -> Result { let mut client = self.inner.clone(); let response = client.stream_events(self.stream_request(request)).await?; let stream = futures_util::StreamExt::map(response.into_inner(), |result| { result.map_err(Error::from) }); Ok(Box::pin(stream)) } fn unary_request(&self, message: T) -> Request { let mut request = Request::new(message); request.set_timeout(self.call_timeout); request } fn stream_request(&self, message: T) -> Request { let mut request = Request::new(message); if let Some(timeout) = self.stream_timeout { request.set_timeout(timeout); } request } }