use core::fmt;
use crate::authentication::{
AsyncAuthenticatedTransport, AuthenticatedRequest, AuthenticationScopePolicy,
BlockingAuthenticatedTransport,
};
use crate::operation::{
CheckedResponseGuard, OperationId, OperationMetadata, RequestIdPolicy, ResponsePolicy,
ResponsePolicyError,
};
use crate::transport::{
BoundTransport, EndpointIdentityError, EndpointPolicy, RawResponsePolicy, ResponseBuffer,
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,
authentication_policy: AuthenticationScopePolicy<'request>,
raw_response_policy: RawResponsePolicy<'request>,
operation_id: Option<OperationId>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PreparedRequestPolicyError {
MissingRequestIdHeader,
}
impl fmt::Display for PreparedRequestPolicyError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::MissingRequestIdHeader => {
"prepared request ID policy requires raw x-request-id admission"
}
})
}
}
impl core::error::Error for PreparedRequestPolicyError {}
impl<'request> PreparedRequest<'request> {
pub fn new(
request: TransportRequest<'request>,
service: ProviderService<'request>,
metadata: OperationMetadata,
response_policy: ResponsePolicy,
authentication_policy: AuthenticationScopePolicy<'request>,
raw_response_policy: RawResponsePolicy<'request>,
) -> Result<Self, PreparedRequestPolicyError> {
if metadata.request_id_policy() != RequestIdPolicy::Discard
&& !raw_response_policy.admits_header("x-request-id")
{
return Err(PreparedRequestPolicyError::MissingRequestIdHeader);
}
Ok(Self {
request,
service,
metadata,
response_policy,
authentication_policy,
raw_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 authentication_policy(self) -> AuthenticationScopePolicy<'request> {
self.authentication_policy
}
#[must_use]
pub const fn raw_response_policy(self) -> RawResponsePolicy<'request> {
self.raw_response_policy
}
#[must_use]
pub const fn authenticated_request(self) -> AuthenticatedRequest<'request, 'request> {
AuthenticatedRequest::new(
self.request,
self.authentication_policy,
self.raw_response_policy,
)
}
#[must_use]
pub const fn operation_id(self) -> Option<OperationId> {
self.operation_id
}
pub fn validate_response<'buffer>(
self,
response: ResponseBuffer<'buffer>,
) -> Result<CheckedResponseGuard<'buffer>, ResponsePolicyError> {
self.response_policy
.validate(response, self.metadata.request_id_policy())
}
pub fn apply_response_metadata_policy(
self,
response: &mut ResponseBuffer<'_>,
) -> Result<(), ResponsePolicyError> {
super::policy::apply_request_id_policy(response, self.metadata.request_id_policy())
}
pub fn execute_blocking<'buffer, T>(
self,
transport: &T,
response_storage: &'buffer mut [u8],
response_header_storage: &'buffer mut [u8],
) -> Result<CheckedResponseGuard<'buffer>, PreparedExecutionError<T::Error>>
where
T: BlockingAuthenticatedTransport + BoundTransport,
{
let mut response = ResponseBuffer::new(
response_storage,
self.raw_response_policy.max_body_bytes(),
response_header_storage,
);
self.verify_endpoint(transport)
.map_err(map_endpoint_error)?;
transport
.send_authenticated(self.authenticated_request(), response.writer())
.map_err(PreparedExecutionError::Transport)?;
self.response_policy
.validate(response, self.metadata.request_id_policy())
.map_err(PreparedExecutionError::ResponsePolicy)
}
pub async fn execute_async<'transport, 'buffer, T>(
&'transport self,
transport: &'transport T,
response_storage: &'buffer mut [u8],
response_header_storage: &'buffer mut [u8],
) -> Result<CheckedResponseGuard<'buffer>, PreparedExecutionError<T::Error>>
where
T: AsyncAuthenticatedTransport + BoundTransport,
'request: 'transport,
{
let mut response = ResponseBuffer::new(
response_storage,
self.raw_response_policy.max_body_bytes(),
response_header_storage,
);
self.verify_endpoint(transport)
.map_err(map_endpoint_error)?;
transport
.send_authenticated(self.authenticated_request(), response.writer())
.await
.map_err(PreparedExecutionError::Transport)?;
self.response_policy
.validate(response, self.metadata.request_id_policy())
.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)
}
}
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("authentication_policy", &self.authentication_policy)
.field("raw_response_policy", &self.raw_response_policy)
.field("operation_id", &self.operation_id)
.finish()
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub enum PreparedExecutionError<E> {
EndpointIdentity(EndpointIdentityError),
EndpointMismatch,
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::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::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,
}
}