micromegas/client/
flightsql_client_factory.rs1use super::flightsql_client::Client;
2use anyhow::{Context, Result};
3use async_trait::async_trait;
4use http::Uri;
5use tonic::transport::{Channel, ClientTlsConfig};
6
7#[async_trait]
9pub trait FlightSQLClientFactory: Send + Sync {
10 async fn make_client(&self) -> Result<Client>;
11}
12
13pub struct BearerFlightSQLClientFactory {
15 url: String,
16 token: String,
17 client_type: Option<String>,
18 extra_metadata: Vec<(String, String)>,
19}
20
21impl BearerFlightSQLClientFactory {
22 pub fn new(url: String, token: String) -> Self {
29 Self {
30 url,
31 token,
32 client_type: None,
33 extra_metadata: Vec::new(),
34 }
35 }
36
37 pub fn new_with_client_type(url: String, token: String, client_type: String) -> Self {
45 Self {
46 url,
47 token,
48 client_type: Some(client_type),
49 extra_metadata: Vec::new(),
50 }
51 }
52
53 pub fn from_env(token: String) -> Result<Self> {
56 let url = std::env::var("MICROMEGAS_FLIGHTSQL_URL")
57 .with_context(|| "error reading MICROMEGAS_FLIGHTSQL_URL environment variable")?;
58 Ok(Self {
59 url,
60 token,
61 client_type: None,
62 extra_metadata: Vec::new(),
63 })
64 }
65
66 pub fn from_env_with_client_type(token: String, client_type: String) -> Result<Self> {
69 let url = std::env::var("MICROMEGAS_FLIGHTSQL_URL")
70 .with_context(|| "error reading MICROMEGAS_FLIGHTSQL_URL environment variable")?;
71 Ok(Self {
72 url,
73 token,
74 client_type: Some(client_type),
75 extra_metadata: Vec::new(),
76 })
77 }
78
79 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
83 self.extra_metadata.push((key.into(), value.into()));
84 self
85 }
86}
87
88pub fn normalize_channel_scheme(url: &str) -> String {
92 let lower = url.to_ascii_lowercase();
93 if lower.starts_with("grpc+tls://") {
94 format!("https://{}", &url["grpc+tls://".len()..])
95 } else if lower.starts_with("grpc://") {
96 format!("http://{}", &url["grpc://".len()..])
97 } else {
98 url.to_string()
99 }
100}
101
102#[async_trait]
103impl FlightSQLClientFactory for BearerFlightSQLClientFactory {
104 async fn make_client(&self) -> Result<Client> {
105 let normalized_url = normalize_channel_scheme(&self.url);
106 let flight_url = normalized_url
107 .parse::<Uri>()
108 .with_context(|| "parsing flightsql url")?;
109 let mut endpoint = Channel::builder(flight_url.clone());
110 if flight_url.scheme_str() == Some("https") {
111 let tls_config = ClientTlsConfig::new().with_native_roots();
112 endpoint = endpoint
113 .tls_config(tls_config)
114 .with_context(|| "tls_config")?;
115 }
116 let channel = endpoint
117 .connect()
118 .await
119 .with_context(|| "connecting grpc channel")?;
120 let mut client = Client::new(channel);
121 let auth_value = if self.token.starts_with("Bearer ") {
122 self.token.clone()
123 } else {
124 format!("Bearer {}", self.token)
125 };
126
127 client
128 .inner_mut()
129 .set_header(http::header::AUTHORIZATION.as_str(), auth_value);
130
131 if let Some(client_type) = &self.client_type {
133 client
134 .inner_mut()
135 .set_header("x-client-type", client_type.clone());
136 }
137
138 for (key, value) in &self.extra_metadata {
140 client.inner_mut().set_header(key, value.clone());
141 }
142
143 client.inner_mut().set_header("preserve_dictionary", "true");
145
146 Ok(client)
147 }
148}