use std::time::Duration;
use crate::common::error_helpers;
use crate::messages::{IncomingMessages, Notice, ResponseMessage};
use crate::proto::payload::ProtoPayload;
use crate::subscriptions::{DecoderContext, StreamDecoder};
use crate::Error;
pub(crate) const ONE_SHOT_TIMEOUT: Duration = Duration::from_secs(30);
fn fold_one_shot<R>(
response: Option<Result<ResponseMessage, Error>>,
processor: impl FnOnce(&ResponseMessage) -> Result<R, Error>,
) -> Result<R, Error> {
match response {
Some(Ok(message)) => processor(&message),
Some(Err(e)) => Err(e),
None => Err(Error::UnexpectedEndOfStream),
}
}
pub(crate) fn empty_on_end_of_stream<R: Default>(error: Error) -> Result<R, Error> {
match error {
Error::UnexpectedEndOfStream => Ok(R::default()),
other => Err(other),
}
}
pub(crate) fn expect_proto<P, R>(decode: impl Fn(P) -> Result<R, Error>) -> impl Fn(&ResponseMessage) -> Result<R, Error>
where
P: ProtoPayload,
{
move |message| decode(message.expect_type(P::MESSAGE_ID)?.proto()?)
}
pub(crate) trait CancellableOneShot: 'static {
type Proto: ProtoPayload;
type Output: Send + 'static;
fn decode(payload: Self::Proto) -> Result<Self::Output, Error>;
fn cancel(request_id: i32) -> Result<Vec<u8>, Error>;
}
pub(crate) struct OneShot<K: CancellableOneShot>(K::Output);
impl<K: CancellableOneShot> OneShot<K> {
pub(crate) fn into_inner(self) -> K::Output {
self.0
}
}
impl<K: CancellableOneShot> StreamDecoder<OneShot<K>> for OneShot<K> {
const RESPONSE_MESSAGE_IDS: &'static [IncomingMessages] = &[K::Proto::MESSAGE_ID];
fn decode(_context: &DecoderContext, message: &ResponseMessage) -> Result<Self, Error> {
let payload = message.expect_type(K::Proto::MESSAGE_ID)?.proto()?;
K::decode(payload).map(Self)
}
fn cancel_message(_server_version: i32, request_id: Option<i32>, _context: Option<&DecoderContext>) -> Result<Vec<u8>, Error> {
K::cancel(error_helpers::require_request_id_for(request_id, "cancel one-shot request")?)
}
fn is_snapshot_end(&self) -> bool {
true
}
fn rejection_ends_request(_notice: &Notice) -> bool {
true
}
}
#[cfg(test)]
#[path = "request_helpers_tests.rs"]
mod tests;
#[cfg(feature = "sync")]
mod sync_impl {
use std::time::Instant;
use super::{CancellableOneShot, OneShot};
use crate::client::blocking::ClientRequestBuilders;
use crate::client::sync::Client;
use crate::messages::{OutgoingMessages, ResponseMessage};
use crate::subscriptions::common::filter_notice;
use crate::subscriptions::sync::Subscription;
use crate::subscriptions::StreamDecoder;
use crate::Error;
pub fn one_shot_deadline(client: &Client) -> Option<Instant> {
client.one_shot_timeout().map(|timeout| Instant::now() + timeout)
}
pub fn check_deadline(deadline: Option<Instant>) -> Result<(), Error> {
match deadline {
Some(deadline) if Instant::now() >= deadline => Err(Error::Timeout),
_ => Ok(()),
}
}
pub fn one_shot_shared<R>(
client: &Client,
message_type: OutgoingMessages,
encoder: impl Fn() -> Result<Vec<u8>, Error>,
processor: impl Fn(&ResponseMessage) -> Result<R, Error>,
) -> Result<R, Error> {
one_shot_shared_matching(client, message_type, encoder, |message| processor(message).map(Some))
}
pub fn one_shot_shared_matching<R>(
client: &Client,
message_type: OutgoingMessages,
encoder: impl Fn() -> Result<Vec<u8>, Error>,
processor: impl Fn(&ResponseMessage) -> Result<Option<R>, Error>,
) -> Result<R, Error> {
let deadline = Instant::now() + client.one_shot_timeout().unwrap_or(super::ONE_SHOT_TIMEOUT);
crate::common::retry::blocking::retry_on_connection_reset(client, || {
check_deadline(Some(deadline))?;
let request = encoder()?;
let subscription = client.shared_request(message_type).send_raw(request)?;
loop {
if Instant::now() >= deadline {
return Err(Error::Timeout);
}
if let Some(result) = super::fold_one_shot(subscription.next_before(deadline)?, &processor)? {
return Ok(result);
}
}
})
}
pub fn one_shot_by_request_id<R>(
client: &Client,
encoder: impl Fn(i32) -> Result<Vec<u8>, Error>,
processor: impl Fn(&ResponseMessage) -> Result<R, Error>,
) -> Result<R, Error> {
let deadline = one_shot_deadline(client);
crate::common::retry::blocking::retry_on_connection_reset(client, || {
check_deadline(deadline)?;
let request_id = client.mint_request_id();
let request = encoder(request_id.raw())?;
let subscription = client.send_request(request_id, request)?;
let response = match deadline {
None => subscription.next(),
Some(deadline) if Instant::now() >= deadline => return Err(Error::Timeout),
Some(deadline) => subscription.next_before(deadline)?,
};
super::fold_one_shot(response, &processor)
})
}
pub fn one_shot_cancelling<K: CancellableOneShot>(client: &Client, encoder: impl Fn(i32) -> Result<Vec<u8>, Error>) -> Result<K::Output, Error> {
let deadline = one_shot_deadline(client);
crate::common::retry::blocking::retry_on_connection_reset(client, || {
check_deadline(deadline)?;
let builder = client.request();
let request = encoder(builder.request_id())?;
let subscription = builder.send::<OneShot<K>>(request)?;
next_frame(&subscription, deadline).map(OneShot::into_inner)
})
}
pub fn next_frame<T: StreamDecoder<T>>(subscription: &Subscription<T>, deadline: Option<Instant>) -> Result<T, Error> {
loop {
let item = match deadline {
None => subscription.next(),
Some(deadline) => {
let remaining = deadline.saturating_duration_since(Instant::now());
if remaining.is_zero() {
return Err(Error::Timeout);
}
match subscription.next_timeout(remaining) {
None if Instant::now() >= deadline => return Err(Error::Timeout),
item => item,
}
}
};
if let Some(frame) = filter_notice(item.ok_or(Error::UnexpectedEndOfStream)?) {
return frame;
}
}
}
}
#[cfg(feature = "async")]
mod async_impl {
use futures::StreamExt;
use tokio::time::Instant;
use super::{CancellableOneShot, OneShot};
use crate::client::{Client, ClientRequestBuilders};
use crate::messages::{OutgoingMessages, ResponseMessage};
use crate::subscriptions::common::{filter_notice, lag_as_error};
use crate::subscriptions::r#async::Subscription;
use crate::subscriptions::StreamDecoder;
use crate::Error;
pub fn one_shot_deadline(client: &Client) -> Option<Instant> {
client.one_shot_timeout().map(|timeout| Instant::now() + timeout)
}
pub fn check_deadline(deadline: Option<Instant>) -> Result<(), Error> {
match deadline {
Some(deadline) if Instant::now() >= deadline => Err(Error::Timeout),
_ => Ok(()),
}
}
pub async fn one_shot_shared<R>(
client: &Client,
message_type: OutgoingMessages,
encoder: impl Fn() -> Result<Vec<u8>, Error>,
processor: impl Fn(&ResponseMessage) -> Result<R, Error>,
) -> Result<R, Error> {
one_shot_shared_matching(client, message_type, encoder, |message| processor(message).map(Some)).await
}
pub async fn one_shot_shared_matching<R>(
client: &Client,
message_type: OutgoingMessages,
encoder: impl Fn() -> Result<Vec<u8>, Error>,
processor: impl Fn(&ResponseMessage) -> Result<Option<R>, Error>,
) -> Result<R, Error> {
let deadline = Instant::now() + client.one_shot_timeout().unwrap_or(super::ONE_SHOT_TIMEOUT);
crate::common::retry::retry_on_connection_reset(client, || async {
check_deadline(Some(deadline))?;
let request = encoder()?;
let mut subscription = client.shared_request(message_type).send_raw(request).await?;
loop {
if Instant::now() >= deadline {
return Err(Error::Timeout);
}
if let Some(result) = super::fold_one_shot(subscription.next_before(deadline).await?, &processor)? {
return Ok(result);
}
}
})
.await
}
pub async fn one_shot_by_request_id<R>(
client: &Client,
encoder: impl Fn(i32) -> Result<Vec<u8>, Error>,
processor: impl Fn(&ResponseMessage) -> Result<R, Error>,
) -> Result<R, Error> {
let deadline = one_shot_deadline(client);
crate::common::retry::retry_on_connection_reset(client, || async {
check_deadline(deadline)?;
let request_id = client.mint_request_id();
let request = encoder(request_id.raw())?;
let mut subscription = client.send_request(request_id, request).await?;
let response = match deadline {
None => subscription.next().await,
Some(deadline) if Instant::now() >= deadline => return Err(Error::Timeout),
Some(deadline) => subscription.next_before(deadline).await?,
};
super::fold_one_shot(response, &processor)
})
.await
}
pub async fn one_shot_cancelling<K: CancellableOneShot>(
client: &Client,
encoder: impl Fn(i32) -> Result<Vec<u8>, Error>,
) -> Result<K::Output, Error> {
let deadline = one_shot_deadline(client);
crate::common::retry::retry_on_connection_reset(client, || async {
check_deadline(deadline)?;
let builder = client.request();
let request = encoder(builder.request_id())?;
let mut subscription = builder.send::<OneShot<K>>(request).await?;
next_frame(&mut subscription, deadline).await.map(OneShot::into_inner)
})
.await
}
pub async fn next_frame<T: StreamDecoder<T> + Send + 'static>(subscription: &mut Subscription<T>, deadline: Option<Instant>) -> Result<T, Error> {
loop {
let item = match deadline {
None => subscription.next().await,
Some(deadline) if Instant::now() >= deadline => return Err(Error::Timeout),
Some(deadline) => tokio::time::timeout_at(deadline, subscription.next())
.await
.map_err(|_elapsed| Error::Timeout)?,
};
if let Some(frame) = filter_notice(lag_as_error(item.ok_or(Error::UnexpectedEndOfStream)?)) {
return frame;
}
}
}
}
#[cfg(feature = "sync")]
pub mod blocking {
pub(crate) use super::sync_impl::*;
}
#[cfg(all(feature = "sync", not(feature = "async")))]
#[allow(unused_imports)]
pub use sync_impl::*;
#[cfg(feature = "async")]
pub use async_impl::*;