use std::sync::Arc;
use prost::Message;
use tokio_stream::wrappers::ReceiverStream;
use tonic::metadata::MetadataValue;
use tonic::transport::Channel;
use tracing::{debug, error, info, instrument};
use super::ZerobusStream;
use crate::databricks::zerobus::ephemeral_stream_request::Payload as RequestPayload;
use crate::databricks::zerobus::ephemeral_stream_response::Payload as ResponsePayload;
use crate::databricks::zerobus::zerobus_client::ZerobusClient;
use crate::databricks::zerobus::{
CreateIngestStreamRequest, EphemeralStreamRequest, EphemeralStreamResponse, RecordType,
};
use crate::{HeadersProvider, TableProperties, ZerobusError, ZerobusResult};
impl ZerobusStream {
pub(super) async fn create_stream_connection(
channel: ZerobusClient<Channel>,
table_properties: &TableProperties,
headers_provider: &Arc<dyn HeadersProvider>,
record_type: RecordType,
) -> ZerobusResult<(
tokio::sync::mpsc::Sender<EphemeralStreamRequest>,
tonic::Streaming<EphemeralStreamResponse>,
String,
)> {
let result = Self::create_stream_connection_inner(
channel,
table_properties,
headers_provider,
record_type,
)
.await;
if let Err(err) = &result {
if err.is_auth_rejection() {
headers_provider.invalidate().await;
}
}
result
}
#[instrument(level = "debug", skip_all, fields(table_name = %table_properties.table_name))]
async fn create_stream_connection_inner(
mut channel: ZerobusClient<Channel>,
table_properties: &TableProperties,
headers_provider: &Arc<dyn HeadersProvider>,
record_type: RecordType,
) -> ZerobusResult<(
tokio::sync::mpsc::Sender<EphemeralStreamRequest>,
tonic::Streaming<EphemeralStreamResponse>,
String,
)> {
const CHANNEL_BUFFER_SIZE: usize = 2048;
let (tx, rx) = tokio::sync::mpsc::channel(CHANNEL_BUFFER_SIZE);
let mut request_stream = tonic::Request::new(ReceiverStream::new(rx));
let stream_metadata = request_stream.metadata_mut();
let headers = headers_provider.get_headers().await?;
for (key, value) in headers {
match key {
"x-databricks-zerobus-table-name" => {
let table_name = MetadataValue::try_from(value.as_str())
.map_err(|e| ZerobusError::InvalidTableName(e.to_string()))?;
stream_metadata.insert("x-databricks-zerobus-table-name", table_name);
}
"authorization" => {
let mut auth_value = MetadataValue::try_from(value.as_str()).map_err(|_| {
error!(table_name = %table_properties.table_name, "authorization token is not a valid HTTP header value");
ZerobusError::InvalidUCTokenError(
"authorization token is not a valid HTTP header value".to_string(),
)
})?;
auth_value.set_sensitive(true);
stream_metadata.insert("authorization", auth_value);
}
other_key => {
let header_value = MetadataValue::try_from(value.as_str())
.map_err(|_| ZerobusError::InvalidArgument(other_key.to_string()))?;
stream_metadata.insert(other_key, header_value);
}
}
}
let mut response_grpc_stream = channel
.ephemeral_stream(request_stream)
.await
.map_err(ZerobusError::CreateStreamError)?
.into_inner();
let descriptor_proto = if record_type == RecordType::Proto {
Some(
table_properties
.descriptor_proto
.as_ref()
.ok_or_else(|| {
ZerobusError::InvalidArgument(
"Descriptor proto is required for Proto record type".to_string(),
)
})?
.encode_to_vec(),
)
} else {
None
};
let create_stream_request = RequestPayload::CreateStream(CreateIngestStreamRequest {
table_name: Some(table_properties.table_name.to_string()),
descriptor_proto,
record_type: Some(record_type.into()),
});
debug!("Sending CreateStream request.");
tx.send(EphemeralStreamRequest {
payload: Some(create_stream_request),
})
.await
.map_err(|_| {
error!(table_name = %table_properties.table_name, "Failed to send CreateStream request");
ZerobusError::StreamClosedError(tonic::Status::internal(
"Failed to send CreateStream request",
))
})?;
debug!("Waiting for CreateStream response.");
let create_stream_response = response_grpc_stream.message().await;
match create_stream_response {
Ok(Some(create_stream_response)) => match create_stream_response.payload {
Some(ResponsePayload::CreateStreamResponse(resp)) => {
if let Some(stream_id) = resp.stream_id {
info!(stream_id = %stream_id, "Successfully created stream");
Ok((tx, response_grpc_stream, stream_id))
} else {
error!("Successfully created a stream but stream_id is None");
Err(ZerobusError::CreateStreamError(tonic::Status::internal(
"Successfully created a stream but stream_id is None",
)))
}
}
unexpected_message => {
error!("Unexpected response from server {unexpected_message:?}");
Err(ZerobusError::CreateStreamError(tonic::Status::internal(
"Unexpected response from server",
)))
}
},
Ok(None) => {
info!("Server closed the stream gracefully before sending CreateStream response");
Err(ZerobusError::CreateStreamError(tonic::Status::ok(
"Stream closed gracefully by server",
)))
}
Err(status) => {
error!("CreateStream RPC failed: {status:?}");
Err(ZerobusError::CreateStreamError(status))
}
}
}
}