use cratestack_core::CoolError;
use crate::codec::HttpClientCodec;
use crate::rpc::batch_call::BatchHandle;
use crate::rpc::client::RpcClient;
use crate::rpc::error::{RpcClientError, RpcRemoteError, http_status_for_rpc_code};
#[must_use = "BatchBuilder does nothing until `.send().await`ed"]
pub struct BatchBuilder<C> {
rpc: RpcClient<C>,
frames: Vec<cratestack_core::rpc::RpcRequest>,
encode_errors: std::collections::HashMap<u64, CoolError>,
next_id: u64,
}
impl<C> BatchBuilder<C>
where
C: HttpClientCodec + Clone + Send + 'static,
{
pub(crate) fn new(rpc: RpcClient<C>) -> Self {
Self {
rpc,
frames: Vec::new(),
encode_errors: std::collections::HashMap::new(),
next_id: 0,
}
}
pub fn len(&self) -> usize {
self.frames.len() + self.encode_errors.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
fn next_id(&mut self) -> u64 {
let id = self.next_id;
self.next_id += 1;
id
}
pub(crate) fn push_frame(&mut self, op_id: String, input: serde_json::Value) -> u64 {
let id = self.next_id();
self.frames.push(cratestack_core::rpc::RpcRequest {
id,
op: op_id,
input,
idem: None,
});
id
}
pub(crate) fn push_failed_frame(&mut self, error: CoolError) -> u64 {
let id = self.next_id();
self.encode_errors.insert(id, error);
id
}
pub async fn send(self) -> Result<BatchResults, RpcClientError> {
let encode_errors = self.encode_errors;
let frames = if self.frames.is_empty() {
std::collections::HashMap::new()
} else {
let response_frames = self.rpc.batch(&self.frames).await?;
response_frames.into_iter().map(|f| (f.id, f)).collect()
};
Ok(BatchResults {
frames,
encode_errors,
})
}
}
pub struct BatchResults {
frames: std::collections::HashMap<u64, cratestack_core::rpc::RpcResponseFrame>,
encode_errors: std::collections::HashMap<u64, CoolError>,
}
impl BatchResults {
pub fn take<O>(&mut self, handle: BatchHandle<O>) -> Result<O, RpcClientError>
where
O: serde::de::DeserializeOwned,
{
if let Some(error) = self.encode_errors.remove(&handle.id) {
return Err(RpcClientError::Codec(error));
}
let frame = self.frames.remove(&handle.id).ok_or_else(|| {
RpcClientError::InvalidResponse(format!(
"batch response missing frame for id {}",
handle.id,
))
})?;
match (frame.output, frame.error) {
(Some(output), None) => serde_json::from_value::<O>(output).map_err(|error| {
RpcClientError::Codec(CoolError::Codec(format!(
"decode batch output for id {}: {error}",
handle.id,
)))
}),
(None, Some(body)) => Err(RpcClientError::Remote(RpcRemoteError {
status: http_status_for_rpc_code(&body.code),
body,
})),
(Some(_), Some(_)) | (None, None) => Err(RpcClientError::InvalidResponse(format!(
"batch frame {} has both `output` and `error` set, or neither",
handle.id,
))),
}
}
}