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 )
257 .await?;
258
259 Ok(Self {
260 dsn: dsn.clone(),
261 session_id,
262 })
263 }
264
265 async fn handshake(
267 client: &mut GeodeServiceClient<Channel>,
268 username: Option<&str>,
269 password: Option<&str>,
270 graph: Option<&str>,
271 ) -> Result<String> {
272 let request = proto::HelloRequest {
273 username: username.unwrap_or("").to_string(),
274 password: password.unwrap_or("").to_string(),
275 tenant_id: None,
276 client_name: "geode-rust".to_string(),
277 client_version: crate::VERSION.to_string(),
278 wanted_conformance: "minimum".to_string(),
279 graph: graph.map(String::from),
280 };
281
282 let response = client
283 .handshake(Request::new(request))
284 .await
285 .map_err(|e| Error::connection(format!("Handshake failed: {}", e)))?;
286
287 let resp = response.into_inner();
288 if !resp.success {
289 return Err(Error::auth(resp.error_message));
290 }
291
292 Ok(resp.session_id)
293 }
294
295 pub async fn query(&mut self, gql: &str) -> Result<(Page, Option<String>)> {
297 self.query_with_params(gql, &HashMap::new()).await
298 }
299
300 pub async fn query_with_params(
302 &mut self,
303 gql: &str,
304 params: &HashMap<String, Value>,
305 ) -> Result<(Page, Option<String>)> {
306 let proto_params: Vec<proto::Param> = params
307 .iter()
308 .map(|(k, v)| proto::Param {
309 name: k.clone(),
310 value: Some(v.to_proto_value()),
311 })
312 .collect();
313
314 let request = proto::ExecuteRequest {
315 session_id: self.session_id.clone(),
316 query: gql.to_string(),
317 params: proto_params,
318 };
319
320 let mut client = Self::connect_service(&self.dsn).await?;
324 let response = client
325 .execute(Request::new(request))
326 .await
327 .map_err(|e| Error::query(format!("Query execution failed: {}", e)))?;
328
329 let mut stream = response.into_inner();
331 let mut columns = Vec::new();
332 let mut rows = Vec::new();
333 let mut final_page = true;
334 let mut ordered = false;
335 let mut order_keys = Vec::new();
336
337 while let Some(exec_resp) = stream
338 .message()
339 .await
340 .map_err(|e| Error::query(format!("Failed to read response: {}", e)))?
341 {
342 if let Some(payload) = exec_resp.payload {
343 match payload {
344 Payload::Schema(schema) => {
345 columns = schema
346 .columns
347 .into_iter()
348 .map(|c| Column {
349 name: c.name,
350 col_type: c.r#type,
351 })
352 .collect();
353 }
354 Payload::Page(page) => {
355 for row in page.rows {
356 let mut row_map = HashMap::new();
357 for (i, col) in columns.iter().enumerate() {
358 let value = if i < row.values.len() {
359 crate::convert::proto_to_value(&row.values[i])
360 } else {
361 Value::null()
362 };
363 row_map.insert(col.name.clone(), value);
364 }
365 rows.push(row_map);
366 }
367 final_page = page.r#final;
368 ordered = page.ordered;
369 order_keys = page.order_keys;
370 }
371 Payload::Error(err) => {
372 return Err(Error::Query {
373 code: err.code,
374 message: err.message,
375 });
376 }
377 Payload::Metrics(_) | Payload::Heartbeat(_) => {
378 }
380 Payload::Explain(_) | Payload::Profile(_) => {
381 }
383 }
384 }
385 }
386
387 Ok((
388 Page {
389 columns,
390 rows,
391 ordered,
392 order_keys,
393 final_page,
394 },
395 None,
396 ))
397 }
398
399 pub async fn begin(&mut self) -> Result<()> {
401 let request = proto::BeginRequest {
402 read_only: false,
403 session_id: self.session_id.clone(),
404 };
405
406 let mut client = Self::connect_service(&self.dsn).await?;
407 client
408 .begin(Request::new(request))
409 .await
410 .map_err(|e| Error::connection(format!("Begin transaction failed: {}", e)))?;
411
412 Ok(())
413 }
414
415 pub async fn commit(&mut self) -> Result<()> {
417 let request = proto::CommitRequest {
418 session_id: self.session_id.clone(),
419 };
420
421 let mut client = Self::connect_service(&self.dsn).await?;
422 client
423 .commit(Request::new(request))
424 .await
425 .map_err(|e| Error::connection(format!("Commit failed: {}", e)))?;
426
427 Ok(())
428 }
429
430 pub async fn rollback(&mut self) -> Result<()> {
432 let request = proto::RollbackRequest {
433 session_id: self.session_id.clone(),
434 };
435
436 let mut client = Self::connect_service(&self.dsn).await?;
437 client
438 .rollback(Request::new(request))
439 .await
440 .map_err(|e| Error::connection(format!("Rollback failed: {}", e)))?;
441
442 Ok(())
443 }
444
445 pub async fn savepoint(&mut self, _name: &str) -> Result<()> {
450 Err(Error::connection(
451 "savepoint is not yet supported via gRPC transport",
452 ))
453 }
454
455 pub async fn rollback_to(&mut self, _name: &str) -> Result<()> {
460 Err(Error::connection(
461 "rollback_to is not yet supported via gRPC transport",
462 ))
463 }
464
465 pub async fn ping(&mut self) -> Result<bool> {
467 let mut client = Self::connect_service(&self.dsn).await?;
468 let response = client
469 .ping(Request::new(proto::PingRequest {}))
470 .await
471 .map_err(|e| Error::connection(format!("Ping failed: {}", e)))?;
472
473 Ok(response.into_inner().ok)
474 }
475
476 pub fn close(&mut self) -> Result<()> {
478 Ok(())
480 }
481}
482
483#[cfg(test)]
484mod tests {
485 use crate::proto;
486
487 #[test]
488 fn test_convert_proto_value_string() {
489 let proto_val = proto::Value {
490 kind: Some(proto::value::Kind::StringVal(proto::StringValue {
491 value: "hello".to_string(),
492 kind: 0,
493 })),
494 };
495 let val = crate::convert::proto_to_value(&proto_val);
496 assert_eq!(val.as_string().unwrap(), "hello");
497 }
498
499 #[test]
500 fn test_convert_proto_value_int() {
501 let proto_val = proto::Value {
502 kind: Some(proto::value::Kind::IntVal(proto::IntValue {
503 value: 42,
504 kind: 0,
505 })),
506 };
507 let val = crate::convert::proto_to_value(&proto_val);
508 assert_eq!(val.as_int().unwrap(), 42);
509 }
510
511 #[test]
512 fn test_convert_proto_value_bool() {
513 let proto_val = proto::Value {
514 kind: Some(proto::value::Kind::BoolVal(true)),
515 };
516 let val = crate::convert::proto_to_value(&proto_val);
517 assert!(val.as_bool().unwrap());
518 }
519
520 #[test]
521 fn test_convert_proto_value_null() {
522 let proto_val = proto::Value {
523 kind: Some(proto::value::Kind::NullVal(proto::NullValue {})),
524 };
525 let val = crate::convert::proto_to_value(&proto_val);
526 assert!(val.is_null());
527 }
528
529 #[test]
530 fn test_convert_proto_value_none() {
531 let proto_val = proto::Value { kind: None };
532 let val = crate::convert::proto_to_value(&proto_val);
533 assert!(val.is_null());
534 }
535
536 #[test]
537 fn test_convert_proto_value_double() {
538 let proto_val = proto::Value {
539 kind: Some(proto::value::Kind::DoubleVal(proto::DoubleValue {
540 value: 3.15,
541 kind: 0,
542 })),
543 };
544 let val = crate::convert::proto_to_value(&proto_val);
545 assert!(val.as_decimal().is_ok());
546 }
547
548 #[test]
549 fn test_convert_proto_value_list() {
550 let proto_val = proto::Value {
551 kind: Some(proto::value::Kind::ListVal(proto::ListValue {
552 values: vec![
553 proto::Value {
554 kind: Some(proto::value::Kind::IntVal(proto::IntValue {
555 value: 1,
556 kind: 0,
557 })),
558 },
559 proto::Value {
560 kind: Some(proto::value::Kind::IntVal(proto::IntValue {
561 value: 2,
562 kind: 0,
563 })),
564 },
565 ],
566 })),
567 };
568 let val = crate::convert::proto_to_value(&proto_val);
569 let arr = val.as_array().unwrap();
570 assert_eq!(arr.len(), 2);
571 assert_eq!(arr[0].as_int().unwrap(), 1);
572 assert_eq!(arr[1].as_int().unwrap(), 2);
573 }
574
575 #[test]
576 fn test_convert_proto_value_map() {
577 let proto_val = proto::Value {
578 kind: Some(proto::value::Kind::MapVal(proto::MapValue {
579 entries: vec![proto::MapEntry {
580 key: "name".to_string(),
581 value: Some(proto::Value {
582 kind: Some(proto::value::Kind::StringVal(proto::StringValue {
583 value: "Alice".to_string(),
584 kind: 0,
585 })),
586 }),
587 }],
588 })),
589 };
590 let val = crate::convert::proto_to_value(&proto_val);
591 let obj = val.as_object().unwrap();
592 assert_eq!(obj.get("name").unwrap().as_string().unwrap(), "Alice");
593 }
594}