use core::fmt;
use crate::operation::{
CheckedResponse, OperationId, OperationMetadata, ResponsePolicy, ResponsePolicyError,
};
use crate::transport::{
AsyncTransport, BlockingTransport, BoundTransport, EndpointIdentity, EndpointIdentityError,
ResponseStorageSanitizer, TransportRequest,
};
use crate::{ApiFamily, Provider};
pub struct PreparationStorage<'storage> {
target: &'storage mut [u8],
body: &'storage mut [u8],
}
impl<'storage> PreparationStorage<'storage> {
#[must_use]
pub const fn new(target: &'storage mut [u8], body: &'storage mut [u8]) -> Self {
Self { target, body }
}
#[must_use]
pub fn into_parts(self) -> (&'storage mut [u8], &'storage mut [u8]) {
(self.target, self.body)
}
}
impl fmt::Debug for PreparationStorage<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("PreparationStorage")
.field("target_capacity", &self.target.len())
.field("body_capacity", &self.body.len())
.finish()
}
}
pub trait PrepareOperation {
type Error;
fn prepare<'storage>(
&self,
storage: PreparationStorage<'storage>,
) -> Result<PreparedRequest<'storage>, Self::Error>;
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ProviderService {
provider: Provider,
family: ApiFamily,
endpoint: EndpointIdentity<'static>,
}
impl ProviderService {
#[must_use]
pub const fn new(
provider: Provider,
family: ApiFamily,
endpoint: EndpointIdentity<'static>,
) -> Self {
Self {
provider,
family,
endpoint,
}
}
#[must_use]
pub const fn provider(self) -> Provider {
self.provider
}
#[must_use]
pub const fn family(self) -> ApiFamily {
self.family
}
#[must_use]
pub const fn endpoint(self) -> EndpointIdentity<'static> {
self.endpoint
}
}
#[derive(Clone, Copy)]
pub struct PreparedRequest<'request> {
request: TransportRequest<'request>,
service: ProviderService,
metadata: OperationMetadata,
response_policy: ResponsePolicy,
operation_id: Option<OperationId>,
}
impl<'request> PreparedRequest<'request> {
#[must_use]
pub const fn new(
request: TransportRequest<'request>,
service: ProviderService,
metadata: OperationMetadata,
response_policy: ResponsePolicy,
) -> Self {
Self {
request,
service,
metadata,
response_policy,
operation_id: None,
}
}
#[must_use]
pub const fn with_operation_id(mut self, operation_id: OperationId) -> Self {
self.operation_id = Some(operation_id);
self
}
#[must_use]
pub const fn transport_request(self) -> TransportRequest<'request> {
self.request
}
#[must_use]
pub const fn service(self) -> ProviderService {
self.service
}
#[must_use]
pub const fn metadata(self) -> OperationMetadata {
self.metadata
}
#[must_use]
pub const fn response_policy(self) -> ResponsePolicy {
self.response_policy
}
#[must_use]
pub const fn operation_id(self) -> Option<OperationId> {
self.operation_id
}
pub fn validate_response<'buffer>(
self,
response: crate::transport::TransportResponse<'buffer>,
) -> Result<CheckedResponse<'buffer>, ResponsePolicyError> {
self.response_policy.validate(response)
}
pub fn execute_blocking<'buffer, T>(
self,
transport: &T,
response_storage: &'buffer mut [u8],
) -> Result<CheckedResponse<'buffer>, PreparedExecutionError<T::Error>>
where
T: BlockingTransport + BoundTransport + ResponseStorageSanitizer,
{
transport.sanitize_response_storage(response_storage);
self.verify_endpoint(transport)
.map_err(map_endpoint_error)?;
let admitted = self.admit_response_storage(response_storage)?;
let response = transport
.send(self.request, admitted)
.map_err(PreparedExecutionError::Transport)?;
self.response_policy
.validate(response)
.map_err(PreparedExecutionError::ResponsePolicy)
}
pub async fn execute_async<'transport, 'buffer, T>(
&'transport self,
transport: &'transport T,
response_storage: &'buffer mut [u8],
) -> Result<CheckedResponse<'buffer>, PreparedExecutionError<T::Error>>
where
T: AsyncTransport + BoundTransport + ResponseStorageSanitizer,
'request: 'transport,
'buffer: 'transport,
{
transport.sanitize_response_storage(response_storage);
self.verify_endpoint(transport)
.map_err(map_endpoint_error)?;
let admitted = self.admit_response_storage(response_storage)?;
let response = transport
.send(self.request, admitted)
.await
.map_err(PreparedExecutionError::Transport)?;
self.response_policy
.validate(response)
.map_err(PreparedExecutionError::ResponsePolicy)
}
fn verify_endpoint<T>(self, transport: &T) -> Result<(), EndpointCheckError>
where
T: BoundTransport,
{
let actual = transport
.endpoint_identity()
.map_err(EndpointCheckError::Invalid)?;
if actual != self.service.endpoint {
return Err(EndpointCheckError::Mismatch);
}
Ok(())
}
fn admit_response_storage<E>(
self,
storage: &mut [u8],
) -> Result<&mut [u8], PreparedExecutionError<E>> {
let admitted_len = core::cmp::min(storage.len(), self.response_policy.max_body_bytes());
storage
.get_mut(..admitted_len)
.ok_or(PreparedExecutionError::ResponseStorageUnavailable)
}
}
impl fmt::Debug for PreparedRequest<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("PreparedRequest")
.field("request", &self.request)
.field("service", &self.service)
.field("metadata", &self.metadata)
.field("response_policy", &self.response_policy)
.field("operation_id", &self.operation_id)
.finish()
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub enum PreparedExecutionError<E> {
EndpointIdentity(EndpointIdentityError),
EndpointMismatch,
ResponseStorageUnavailable,
Transport(E),
ResponsePolicy(ResponsePolicyError),
}
impl<E> fmt::Debug for PreparedExecutionError<E> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::EndpointIdentity(error) => formatter
.debug_tuple("EndpointIdentity")
.field(error)
.finish(),
Self::EndpointMismatch => formatter.write_str("EndpointMismatch"),
Self::ResponseStorageUnavailable => formatter.write_str("ResponseStorageUnavailable"),
Self::Transport(_) => formatter.write_str("Transport([redacted])"),
Self::ResponsePolicy(error) => formatter
.debug_tuple("ResponsePolicy")
.field(error)
.finish(),
}
}
}
impl<E> fmt::Display for PreparedExecutionError<E> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::EndpointIdentity(_) => "transport endpoint identity is invalid",
Self::EndpointMismatch => "transport endpoint differs from prepared service",
Self::ResponseStorageUnavailable => "response storage is unavailable",
Self::Transport(_) => "prepared request transport failed",
Self::ResponsePolicy(_) => "prepared response policy failed",
})
}
}
impl<E: fmt::Debug> core::error::Error for PreparedExecutionError<E> {}
enum EndpointCheckError {
Invalid(EndpointIdentityError),
Mismatch,
}
fn map_endpoint_error<E>(error: EndpointCheckError) -> PreparedExecutionError<E> {
match error {
EndpointCheckError::Invalid(error) => PreparedExecutionError::EndpointIdentity(error),
EndpointCheckError::Mismatch => PreparedExecutionError::EndpointMismatch,
}
}