1use std::collections::HashMap;
7use std::fs;
8use std::future::Future;
9use std::net::IpAddr;
10use std::pin::Pin;
11use std::sync::Arc;
12use std::task::{Context, Poll};
13
14use http::Uri;
15use hyper_util::rt::TokioIo;
16use tokio::net::TcpStream;
17use tokio_rustls::TlsConnector as RustlsConnector;
18use tonic::Request;
19use tonic::transport::{Certificate, Channel, ClientTlsConfig, Endpoint};
20use tower_service::Service;
21
22use crate::client::{Column, Page};
23use crate::dsn::Dsn;
24use crate::error::{Error, Result};
25use crate::proto;
26use crate::proto::execution_response::Payload;
27use crate::proto::geode_service_client::GeodeServiceClient;
28use crate::types::Value;
29
30#[derive(Debug)]
31struct SkipServerVerification;
32
33impl rustls::client::danger::ServerCertVerifier for SkipServerVerification {
34 fn verify_server_cert(
35 &self,
36 _end_entity: &rustls::pki_types::CertificateDer<'_>,
37 _intermediates: &[rustls::pki_types::CertificateDer<'_>],
38 _server_name: &rustls::pki_types::ServerName<'_>,
39 _ocsp_response: &[u8],
40 _now: rustls::pki_types::UnixTime,
41 ) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
42 Ok(rustls::client::danger::ServerCertVerified::assertion())
43 }
44
45 fn verify_tls12_signature(
46 &self,
47 _message: &[u8],
48 _cert: &rustls::pki_types::CertificateDer<'_>,
49 _dss: &rustls::DigitallySignedStruct,
50 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
51 Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
52 }
53
54 fn verify_tls13_signature(
55 &self,
56 _message: &[u8],
57 _cert: &rustls::pki_types::CertificateDer<'_>,
58 _dss: &rustls::DigitallySignedStruct,
59 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
60 Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
61 }
62
63 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
64 vec![
65 rustls::SignatureScheme::RSA_PKCS1_SHA256,
66 rustls::SignatureScheme::RSA_PKCS1_SHA384,
67 rustls::SignatureScheme::RSA_PKCS1_SHA512,
68 rustls::SignatureScheme::RSA_PSS_SHA256,
69 rustls::SignatureScheme::RSA_PSS_SHA384,
70 rustls::SignatureScheme::RSA_PSS_SHA512,
71 rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
72 rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
73 rustls::SignatureScheme::ED25519,
74 ]
75 }
76}
77
78#[derive(Clone)]
79struct InsecureTlsConnector {
80 config: Arc<rustls::ClientConfig>,
81 server_name: rustls::pki_types::ServerName<'static>,
82}
83
84impl Service<Uri> for InsecureTlsConnector {
85 type Response = TokioIo<tokio_rustls::client::TlsStream<TcpStream>>;
86 type Error = std::io::Error;
87 type Future =
88 Pin<Box<dyn Future<Output = std::result::Result<Self::Response, Self::Error>> + Send>>;
89
90 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
91 Poll::Ready(Ok(()))
92 }
93
94 fn call(&mut self, uri: Uri) -> Self::Future {
95 let addr = match (uri.host(), uri.port_u16()) {
96 (Some(host), Some(port)) => format!("{host}:{port}"),
97 (Some(host), None) => format!("{host}:443"),
98 _ => {
99 return Box::pin(async {
100 Err(std::io::Error::new(
101 std::io::ErrorKind::InvalidInput,
102 "missing host in URI",
103 ))
104 });
105 }
106 };
107 let connector = RustlsConnector::from(self.config.clone());
108 let server_name = self.server_name.clone();
109
110 Box::pin(async move {
111 let stream = TcpStream::connect(addr).await?;
112 stream.set_nodelay(true)?;
113 let tls = connector
114 .connect(server_name, stream)
115 .await
116 .map_err(std::io::Error::other)?;
117 Ok(TokioIo::new(tls))
118 })
119 }
120}
121
122pub struct GrpcClient {
127 dsn: Dsn,
128 session_id: String,
129}
130
131impl GrpcClient {
132 async fn connect_channel(dsn: &Dsn) -> Result<Channel> {
133 let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
135
136 let addr = if dsn.tls_enabled() && !dsn.skip_verify() {
137 format!("https://{}", dsn.address())
138 } else {
139 format!("http://{}", dsn.address())
140 };
141
142 let mut endpoint = Endpoint::from_shared(addr.clone())
143 .map_err(|e| Error::connection(format!("Invalid endpoint: {}", e)))?;
144
145 let tls_server_name =
146 dsn.server_name()
147 .map(str::to_string)
148 .or_else(|| match dsn.host().parse::<IpAddr>() {
149 Ok(_) => Some("localhost".to_string()),
150 Err(_) => None,
151 });
152
153 if dsn.tls_enabled() {
154 if let Some(server_name) = &tls_server_name {
155 let origin = format!("https://{}:{}", server_name, dsn.port())
156 .parse()
157 .map_err(|e| Error::tls(format!("Invalid TLS origin: {}", e)))?;
158 endpoint = endpoint.origin(origin);
159 }
160 }
161
162 if dsn.tls_enabled() && dsn.skip_verify() {
163 let mut client_config = rustls::ClientConfig::builder()
164 .dangerous()
165 .with_custom_certificate_verifier(Arc::new(SkipServerVerification))
166 .with_no_client_auth();
167 client_config.alpn_protocols.push(b"h2".to_vec());
168
169 let server_name = tls_server_name
170 .unwrap_or_else(|| dsn.host().to_string())
171 .try_into()
172 .map_err(|e| Error::tls(format!("Invalid TLS server name: {}", e)))?;
173
174 endpoint
175 .connect_with_connector(InsecureTlsConnector {
176 config: Arc::new(client_config),
177 server_name,
178 })
179 .await
180 .map_err(|e| {
181 Error::connection(format!(
182 "gRPC connection failed to {}: {} ({:?})",
183 addr, e, e
184 ))
185 })
186 } else if dsn.tls_enabled() {
187 let mut tls = ClientTlsConfig::new()
188 .with_enabled_roots()
189 .assume_http2(true);
190
191 if let Some(server_name) = tls_server_name {
192 tls = tls.domain_name(server_name);
193 }
194
195 if let Some(ca_cert_path) = dsn.ca_cert() {
196 let ca_pem = fs::read(ca_cert_path).map_err(|e| {
197 Error::tls(format!(
198 "Failed to read CA certificate {}: {}",
199 ca_cert_path, e
200 ))
201 })?;
202 tls = tls.ca_certificate(Certificate::from_pem(ca_pem));
203 }
204
205 endpoint
206 .tls_config(tls)
207 .map_err(|e| Error::tls(format!("TLS config error: {}", e)))?
208 .connect()
209 .await
210 .map_err(|e| {
211 Error::connection(format!(
212 "gRPC connection failed to {}: {} ({:?})",
213 addr, e, e
214 ))
215 })
216 } else {
217 endpoint.connect().await.map_err(|e| {
218 Error::connection(format!(
219 "gRPC connection failed to {}: {} ({:?})",
220 addr, e, e
221 ))
222 })
223 }
224 }
225
226 async fn connect_service(dsn: &Dsn) -> Result<GeodeServiceClient<Channel>> {
227 let channel = Self::connect_channel(dsn).await?;
228 Ok(GeodeServiceClient::new(channel))
229 }
230
231 pub async fn connect(dsn: &Dsn) -> Result<Self> {
250 let mut grpc_client = Self::connect_service(dsn).await?;
251 let session_id = Self::handshake(
252 &mut grpc_client,
253 dsn.username(),
254 dsn.password(),
255 dsn.graph(),
256 dsn.tenant(),
257 dsn.role(),
258 )
259 .await?;
260
261 Ok(Self {
262 dsn: dsn.clone(),
263 session_id,
264 })
265 }
266
267 async fn handshake(
269 client: &mut GeodeServiceClient<Channel>,
270 username: Option<&str>,
271 password: Option<&str>,
272 graph: Option<&str>,
273 tenant: Option<&str>,
274 role: Option<&str>,
275 ) -> Result<String> {
276 let request = proto::HelloRequest {
277 username: username.unwrap_or("").to_string(),
278 password: password.unwrap_or("").to_string(),
279 tenant_id: tenant.map(String::from),
280 client_name: "geode-rust".to_string(),
281 client_version: crate::VERSION.to_string(),
282 wanted_conformance: "minimum".to_string(),
283 graph: graph.map(String::from),
284 role: role.map(String::from),
285 };
286
287 let response = client
288 .handshake(Request::new(request))
289 .await
290 .map_err(|e| Error::connection(format!("Handshake failed: {}", e)))?;
291
292 let resp = response.into_inner();
293 if !resp.success {
294 return Err(Error::auth(resp.error_message));
295 }
296
297 Ok(resp.session_id)
298 }
299
300 pub async fn query(&mut self, gql: &str) -> Result<(Page, Option<String>)> {
302 self.query_with_params(gql, &HashMap::new()).await
303 }
304
305 pub async fn query_with_params(
307 &mut self,
308 gql: &str,
309 params: &HashMap<String, Value>,
310 ) -> Result<(Page, Option<String>)> {
311 let proto_params: Vec<proto::Param> = params
312 .iter()
313 .map(|(k, v)| proto::Param {
314 name: k.clone(),
315 value: Some(v.to_proto_value()),
316 })
317 .collect();
318
319 let request = proto::ExecuteRequest {
320 session_id: self.session_id.clone(),
321 query: gql.to_string(),
322 params: proto_params,
323 };
324
325 let mut client = Self::connect_service(&self.dsn).await?;
329 let response = client
330 .execute(Request::new(request))
331 .await
332 .map_err(|e| Error::query(format!("Query execution failed: {}", e)))?;
333
334 let mut stream = response.into_inner();
336 let mut columns = Vec::new();
337 let mut rows = Vec::new();
338 let mut final_page = true;
339 let mut ordered = false;
340 let mut order_keys = Vec::new();
341
342 while let Some(exec_resp) = stream
343 .message()
344 .await
345 .map_err(|e| Error::query(format!("Failed to read response: {}", e)))?
346 {
347 if let Some(payload) = exec_resp.payload {
348 match payload {
349 Payload::Schema(schema) => {
350 columns = schema
351 .columns
352 .into_iter()
353 .map(|c| Column {
354 name: c.name,
355 col_type: c.r#type,
356 })
357 .collect();
358 }
359 Payload::Page(page) => {
360 for row in page.rows {
361 let mut row_map = HashMap::new();
362 for (i, col) in columns.iter().enumerate() {
363 let value = if i < row.values.len() {
364 crate::convert::proto_to_value(&row.values[i])
365 } else {
366 Value::null()
367 };
368 row_map.insert(col.name.clone(), value);
369 }
370 rows.push(row_map);
371 }
372 final_page = page.r#final;
373 ordered = page.ordered;
374 order_keys = page.order_keys;
375 }
376 Payload::Error(err) => {
377 return Err(Error::Query {
378 code: err.code,
379 message: err.message,
380 });
381 }
382 Payload::Metrics(_) | Payload::Heartbeat(_) => {
383 }
385 Payload::Explain(_) | Payload::Profile(_) => {
386 }
388 }
389 }
390 }
391
392 Ok((
393 Page {
394 columns,
395 rows,
396 ordered,
397 order_keys,
398 final_page,
399 },
400 None,
401 ))
402 }
403
404 pub async fn begin(&mut self) -> Result<()> {
406 let request = proto::BeginRequest {
407 read_only: false,
408 session_id: self.session_id.clone(),
409 };
410
411 let mut client = Self::connect_service(&self.dsn).await?;
412 client
413 .begin(Request::new(request))
414 .await
415 .map_err(|e| Error::connection(format!("Begin transaction failed: {}", e)))?;
416
417 Ok(())
418 }
419
420 pub async fn commit(&mut self) -> Result<()> {
422 let request = proto::CommitRequest {
423 session_id: self.session_id.clone(),
424 };
425
426 let mut client = Self::connect_service(&self.dsn).await?;
427 client
428 .commit(Request::new(request))
429 .await
430 .map_err(|e| Error::connection(format!("Commit failed: {}", e)))?;
431
432 Ok(())
433 }
434
435 pub async fn rollback(&mut self) -> Result<()> {
437 let request = proto::RollbackRequest {
438 session_id: self.session_id.clone(),
439 };
440
441 let mut client = Self::connect_service(&self.dsn).await?;
442 client
443 .rollback(Request::new(request))
444 .await
445 .map_err(|e| Error::connection(format!("Rollback failed: {}", e)))?;
446
447 Ok(())
448 }
449
450 pub async fn savepoint(&mut self, _name: &str) -> Result<()> {
455 Err(Error::connection(
456 "savepoint is not yet supported via gRPC transport",
457 ))
458 }
459
460 pub async fn rollback_to(&mut self, _name: &str) -> Result<()> {
465 Err(Error::connection(
466 "rollback_to is not yet supported via gRPC transport",
467 ))
468 }
469
470 pub async fn ping(&mut self) -> Result<bool> {
472 let mut client = Self::connect_service(&self.dsn).await?;
473 let response = client
474 .ping(Request::new(proto::PingRequest {}))
475 .await
476 .map_err(|e| Error::connection(format!("Ping failed: {}", e)))?;
477
478 Ok(response.into_inner().ok)
479 }
480
481 pub fn close(&mut self) -> Result<()> {
483 Ok(())
485 }
486}
487
488#[cfg(test)]
489mod tests {
490 use crate::proto;
491
492 #[test]
493 fn test_convert_proto_value_string() {
494 let proto_val = proto::Value {
495 kind: Some(proto::value::Kind::StringVal(proto::StringValue {
496 value: "hello".to_string(),
497 kind: 0,
498 })),
499 };
500 let val = crate::convert::proto_to_value(&proto_val);
501 assert_eq!(val.as_string().unwrap(), "hello");
502 }
503
504 #[test]
505 fn test_convert_proto_value_int() {
506 let proto_val = proto::Value {
507 kind: Some(proto::value::Kind::IntVal(proto::IntValue {
508 value: 42,
509 kind: 0,
510 })),
511 };
512 let val = crate::convert::proto_to_value(&proto_val);
513 assert_eq!(val.as_int().unwrap(), 42);
514 }
515
516 #[test]
517 fn test_convert_proto_value_bool() {
518 let proto_val = proto::Value {
519 kind: Some(proto::value::Kind::BoolVal(true)),
520 };
521 let val = crate::convert::proto_to_value(&proto_val);
522 assert!(val.as_bool().unwrap());
523 }
524
525 #[test]
526 fn test_convert_proto_value_null() {
527 let proto_val = proto::Value {
528 kind: Some(proto::value::Kind::NullVal(proto::NullValue {})),
529 };
530 let val = crate::convert::proto_to_value(&proto_val);
531 assert!(val.is_null());
532 }
533
534 #[test]
535 fn test_convert_proto_value_none() {
536 let proto_val = proto::Value { kind: None };
537 let val = crate::convert::proto_to_value(&proto_val);
538 assert!(val.is_null());
539 }
540
541 #[test]
542 fn test_convert_proto_value_double() {
543 let proto_val = proto::Value {
544 kind: Some(proto::value::Kind::DoubleVal(proto::DoubleValue {
545 value: 3.15,
546 kind: 0,
547 })),
548 };
549 let val = crate::convert::proto_to_value(&proto_val);
550 assert!(val.as_decimal().is_ok());
551 }
552
553 #[test]
554 fn test_convert_proto_value_list() {
555 let proto_val = proto::Value {
556 kind: Some(proto::value::Kind::ListVal(proto::ListValue {
557 values: vec![
558 proto::Value {
559 kind: Some(proto::value::Kind::IntVal(proto::IntValue {
560 value: 1,
561 kind: 0,
562 })),
563 },
564 proto::Value {
565 kind: Some(proto::value::Kind::IntVal(proto::IntValue {
566 value: 2,
567 kind: 0,
568 })),
569 },
570 ],
571 })),
572 };
573 let val = crate::convert::proto_to_value(&proto_val);
574 let arr = val.as_array().unwrap();
575 assert_eq!(arr.len(), 2);
576 assert_eq!(arr[0].as_int().unwrap(), 1);
577 assert_eq!(arr[1].as_int().unwrap(), 2);
578 }
579
580 #[test]
581 fn test_convert_proto_value_map() {
582 let proto_val = proto::Value {
583 kind: Some(proto::value::Kind::MapVal(proto::MapValue {
584 entries: vec![proto::MapEntry {
585 key: "name".to_string(),
586 value: Some(proto::Value {
587 kind: Some(proto::value::Kind::StringVal(proto::StringValue {
588 value: "Alice".to_string(),
589 kind: 0,
590 })),
591 }),
592 }],
593 })),
594 };
595 let val = crate::convert::proto_to_value(&proto_val);
596 let obj = val.as_object().unwrap();
597 assert_eq!(obj.get("name").unwrap().as_string().unwrap(), "Alice");
598 }
599}