use polyc_state::{
conformance::{SyntheticCommand, SyntheticRecord},
deadline::MonotonicInstant,
error::StateError,
id::{CommandId, OperationFamily, SnapshotId},
page::{Page, PageRequest},
receipt::Receipt,
stream::{StreamChunk, StreamContract, StreamRequest},
};
use connectrpc::client::{ClientConfig, ClientTransport};
use polyc_proto::proto::polychrome::state::v1 as pb;
use polyc_state::conformance::family;
use crate::{
MAX_WIRE_MESSAGE_BYTES,
error::{TransportFallback, from_connect_error},
trace::bounded_traced_options,
wire::{DeclaredCall, Kernel, nanos_from_duration},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Observation {
pub applied_effects: u64,
pub now: MonotonicInstant,
}
pub struct ConformanceClient<T> {
inner: pb::StateConformanceServiceClient<T>,
}
impl<T> ConformanceClient<T>
where
T: ClientTransport,
<T::ResponseBody as connectrpc::http_body::Body>::Error: std::fmt::Display,
{
pub fn new(transport: T, config: ClientConfig) -> Self {
Self {
inner: pb::StateConformanceServiceClient::new(
transport,
config.with_default_max_message_size(MAX_WIRE_MESSAGE_BYTES),
),
}
}
fn fallback(attempted_bytes: usize) -> TransportFallback {
TransportFallback::new(
OperationFamily::new(family::FAMILY),
MAX_WIRE_MESSAGE_BYTES as u64,
attempted_bytes as u64,
)
}
pub async fn submit(
&self,
declared: &DeclaredCall,
command: &SyntheticCommand,
) -> Result<Receipt, StateError> {
self.submit_with_payload(declared, command, Vec::new())
.await
}
pub async fn submit_with_payload(
&self,
declared: &DeclaredCall,
command: &SyntheticCommand,
payload: Vec<u8>,
) -> Result<Receipt, StateError> {
let request = pb::SubmitRequest {
context: buffa::MessageField::some(Kernel(declared).into()),
command: buffa::MessageField::some(Kernel(command).into()),
payload,
__buffa_unknown_fields: buffa::UnknownFields::default(),
};
let attempted = buffa::Message::encoded_len(&request) as usize;
let reply = self
.inner
.submit_with_options(request, bounded_traced_options(declared))
.await
.map_err(|e| from_connect_error(&e, &Self::fallback(attempted)))?
.into_owned();
Ok(
Kernel::<Receipt>::try_from(reply.receipt.into_option().ok_or_else(|| {
StateError::Malformed {
field: "receipt".to_owned(),
reason: "a successful submission carries its receipt".to_owned(),
}
})?)?
.into_inner(),
)
}
pub async fn receipt(
&self,
declared: &DeclaredCall,
command_id: &CommandId,
) -> Result<Option<Receipt>, StateError> {
let request = pb::GetReceiptRequest {
context: buffa::MessageField::some(Kernel(declared).into()),
command_id: command_id.as_str().to_owned(),
__buffa_unknown_fields: buffa::UnknownFields::default(),
};
let attempted = buffa::Message::encoded_len(&request) as usize;
let reply = self
.inner
.get_receipt_with_options(request, bounded_traced_options(declared))
.await
.map_err(|e| from_connect_error(&e, &Self::fallback(attempted)))?
.into_owned();
reply
.receipt
.into_option()
.map(|receipt| Kernel::<Receipt>::try_from(receipt).map(Kernel::into_inner))
.transpose()
}
pub async fn create_snapshot(&self, declared: &DeclaredCall) -> Result<SnapshotId, StateError> {
let request = pb::CreateSnapshotRequest {
context: buffa::MessageField::some(Kernel(declared).into()),
__buffa_unknown_fields: buffa::UnknownFields::default(),
};
let attempted = buffa::Message::encoded_len(&request) as usize;
let reply = self
.inner
.create_snapshot_with_options(request, bounded_traced_options(declared))
.await
.map_err(|e| from_connect_error(&e, &Self::fallback(attempted)))?
.into_owned();
Ok(SnapshotId::new(reply.snapshot))
}
pub async fn read_page(
&self,
declared: &DeclaredCall,
page: &PageRequest,
) -> Result<Page<SyntheticRecord>, StateError> {
let request = pb::ReadPageRequest {
context: buffa::MessageField::some(Kernel(declared).into()),
page: buffa::MessageField::some(Kernel(page).into()),
__buffa_unknown_fields: buffa::UnknownFields::default(),
};
let attempted = buffa::Message::encoded_len(&request) as usize;
let reply = self
.inner
.read_page_with_options(request, bounded_traced_options(declared))
.await
.map_err(|e| from_connect_error(&e, &Self::fallback(attempted)))?
.into_owned();
Ok(
Kernel::<Page<SyntheticRecord>>::try_from(reply.page.into_option().ok_or_else(
|| StateError::Malformed {
field: "page".to_owned(),
reason: "a successful read carries its page".to_owned(),
},
)?)?
.into_inner(),
)
}
pub async fn read_chunk(
&self,
declared: &DeclaredCall,
chunk: &StreamRequest,
) -> Result<StreamChunk<SyntheticRecord>, StateError> {
let request = pb::ReadChunkRequest {
context: buffa::MessageField::some(Kernel(declared).into()),
chunk: buffa::MessageField::some(Kernel(chunk).into()),
__buffa_unknown_fields: buffa::UnknownFields::default(),
};
let attempted = buffa::Message::encoded_len(&request) as usize;
let reply = self
.inner
.read_chunk_with_options(request, bounded_traced_options(declared))
.await
.map_err(|e| from_connect_error(&e, &Self::fallback(attempted)))?
.into_owned();
Ok(Kernel::<StreamChunk<SyntheticRecord>>::try_from(
reply
.chunk
.into_option()
.ok_or_else(|| StateError::Malformed {
field: "chunk".to_owned(),
reason: "a successful read carries its chunk".to_owned(),
})?,
)?
.into_inner())
}
pub async fn stream_contract(
&self,
declared: &DeclaredCall,
) -> Result<StreamContract, StateError> {
let request = pb::DescribeStreamRequest {
context: buffa::MessageField::some(Kernel(declared).into()),
__buffa_unknown_fields: buffa::UnknownFields::default(),
};
let attempted = buffa::Message::encoded_len(&request) as usize;
let reply = self
.inner
.describe_stream_with_options(request, bounded_traced_options(declared))
.await
.map_err(|e| from_connect_error(&e, &Self::fallback(attempted)))?
.into_owned();
Ok(
Kernel::<StreamContract>::try_from(reply.contract.into_option().ok_or_else(|| {
StateError::Malformed {
field: "contract".to_owned(),
reason: "a stream declares its contract".to_owned(),
}
})?)?
.into_inner(),
)
}
pub async fn observe(&self, declared: &DeclaredCall) -> Result<Observation, StateError> {
let request = pb::ObserveRequest {
context: buffa::MessageField::some(Kernel(declared).into()),
__buffa_unknown_fields: buffa::UnknownFields::default(),
};
let attempted = buffa::Message::encoded_len(&request) as usize;
let reply = self
.inner
.observe_with_options(request, bounded_traced_options(declared))
.await
.map_err(|e| from_connect_error(&e, &Self::fallback(attempted)))?
.into_owned();
Ok(Observation {
applied_effects: reply.applied_effects,
now: MonotonicInstant::from_nanos(reply.now_nanos),
})
}
pub async fn advance_clock(
&self,
declared: &DeclaredCall,
budget: std::time::Duration,
) -> Result<MonotonicInstant, StateError> {
let request = pb::AdvanceClockRequest {
context: buffa::MessageField::some(Kernel(declared).into()),
budget_nanos: nanos_from_duration(budget),
__buffa_unknown_fields: buffa::UnknownFields::default(),
};
let attempted = buffa::Message::encoded_len(&request) as usize;
let reply = self
.inner
.advance_clock_with_options(request, bounded_traced_options(declared))
.await
.map_err(|e| from_connect_error(&e, &Self::fallback(attempted)))?
.into_owned();
Ok(MonotonicInstant::from_nanos(reply.now_nanos))
}
}