use core::fmt;
use crate::operation::{
CheckedResponse, OperationId, OperationMetadata, ResponsePolicy, ResponsePolicyError,
};
use crate::transport::{
AsyncTransport, BlockingTransport, BoundTransport, EndpointIdentityError, EndpointPolicy,
ResponseStorageSanitizer, TransportRequest,
};
use crate::{ProviderId, ProviderMarker, ServiceId, ServiceMarker};
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<'endpoint> {
provider_id: ProviderId,
service_id: ServiceId,
endpoint_policy: EndpointPolicy<'endpoint>,
}
impl<'endpoint> ProviderService<'endpoint> {
#[must_use]
pub const fn new(
provider_id: ProviderId,
service_id: ServiceId,
endpoint_policy: EndpointPolicy<'endpoint>,
) -> Self {
Self {
provider_id,
service_id,
endpoint_policy,
}
}
#[must_use]
pub const fn from_marker<S: ServiceMarker>(endpoint_policy: EndpointPolicy<'endpoint>) -> Self {
Self::new(<S::Provider as ProviderMarker>::ID, S::ID, endpoint_policy)
}
#[must_use]
pub const fn provider_id(self) -> ProviderId {
self.provider_id
}
#[must_use]
pub const fn service_id(self) -> ServiceId {
self.service_id
}
#[must_use]
pub const fn endpoint_policy(self) -> EndpointPolicy<'endpoint> {
self.endpoint_policy
}
}
#[derive(Clone, Copy)]
pub struct PreparedRequest<'request> {
request: TransportRequest<'request>,
service: ProviderService<'request>,
metadata: OperationMetadata,
response_policy: ResponsePolicy,
operation_id: Option<OperationId>,
}
impl<'request> PreparedRequest<'request> {
#[must_use]
pub const fn new(
request: TransportRequest<'request>,
service: ProviderService<'request>,
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<'request> {
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)?;
self.service
.endpoint_policy
.verify(actual)
.map_err(|_| EndpointCheckError::Mismatch)
}
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,
}
}