use std::{
error::Error,
fmt,
future::Future,
pin::Pin,
time::{Duration, SystemTime},
};
use bytes::Bytes;
use crate::{WebSocket, WireRequest, WireResponse, connection::HttpBody};
#[cfg(not(target_arch = "wasm32"))]
pub type CapabilityFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
#[cfg(target_arch = "wasm32")]
pub type CapabilityFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CapabilityLimits {
pub operation_total: Duration,
pub stream_idle: Duration,
pub read_bytes: u64,
pub write_bytes: u64,
pub ws_frame_bytes: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
pub enum CapabilityErrorKind {
Unsupported,
NotFound,
Expired,
Conflict,
Limit,
Transport,
Storage,
Invalid,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
pub enum CapabilityErrorStage {
Start,
BodyTransfer,
Stream,
}
#[derive(Debug)]
pub struct CapabilityError {
kind: CapabilityErrorKind,
stage: CapabilityErrorStage,
message: String,
source: Option<Box<dyn Error + Send + Sync + 'static>>,
}
impl CapabilityError {
pub fn new(
kind: CapabilityErrorKind,
stage: CapabilityErrorStage,
message: impl Into<String>,
) -> Self {
Self {
kind,
stage,
message: message.into(),
source: None,
}
}
pub fn with_source(
kind: CapabilityErrorKind,
stage: CapabilityErrorStage,
message: impl Into<String>,
source: impl Into<Box<dyn Error + Send + Sync + 'static>>,
) -> Self {
Self {
kind,
stage,
message: message.into(),
source: Some(source.into()),
}
}
pub fn kind(&self) -> CapabilityErrorKind {
self.kind
}
pub fn stage(&self) -> CapabilityErrorStage {
self.stage
}
pub fn source_error(&self) -> Option<&(dyn Error + Send + Sync + 'static)> {
self.source.as_deref()
}
}
impl fmt::Display for CapabilityError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} ({:?}/{:?})", self.message, self.kind, self.stage)
}
}
impl Error for CapabilityError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.source
.as_deref()
.map(|source| source as &(dyn Error + 'static))
}
}
pub enum UpstreamConnection {
Connected {
handshake: WireResponse<()>,
socket: WebSocket,
},
Rejected(WireResponse<HttpBody>),
}
impl fmt::Debug for UpstreamConnection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Connected { handshake, .. } => f
.debug_struct("Connected")
.field("handshake", handshake)
.field("socket", &"WebSocket { .. }")
.finish(),
Self::Rejected(response) => f.debug_tuple("Rejected").field(response).finish(),
}
}
}
pub trait Upstream {
type Target;
fn send<'a>(
&'a self,
target: &'a Self::Target,
request: WireRequest<HttpBody>,
) -> CapabilityFuture<'a, Result<WireResponse<HttpBody>, CapabilityError>>;
fn connect<'a>(
&'a self,
target: &'a Self::Target,
request: WireRequest<()>,
) -> CapabilityFuture<'a, Result<UpstreamConnection, CapabilityError>>;
fn limits(&self) -> CapabilityLimits;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResourceMetadata {
pub mime: Option<String>,
pub length: Option<u64>,
pub filename: Option<String>,
pub expires_at: Option<SystemTime>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
pub enum ResourceReference {
Id(String),
Url(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
pub enum PublicationKind {
Id,
Url,
}
pub struct ResourceRead {
pub metadata: ResourceMetadata,
pub body: HttpBody,
}
impl fmt::Debug for ResourceRead {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ResourceRead")
.field("metadata", &self.metadata)
.field("body", &self.body)
.finish()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PublishedResource<H> {
pub handle: H,
pub reference: ResourceReference,
pub metadata: ResourceMetadata,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
pub enum PublicationStatus<H> {
Missing,
Pending,
Published(PublishedResource<H>),
Expired,
}
pub trait ResourceAccess {
type Scope;
type PublishedHandle;
fn resolve<'a>(
&'a self,
scope: &'a Self::Scope,
reference: &'a ResourceReference,
) -> CapabilityFuture<'a, Result<ResourceMetadata, CapabilityError>>;
fn read<'a>(
&'a self,
scope: &'a Self::Scope,
reference: &'a ResourceReference,
) -> CapabilityFuture<'a, Result<ResourceRead, CapabilityError>>;
fn publish<'a>(
&'a self,
scope: &'a Self::Scope,
operation_id: &'a str,
kind: PublicationKind,
metadata: ResourceMetadata,
body: HttpBody,
) -> CapabilityFuture<'a, Result<PublishedResource<Self::PublishedHandle>, CapabilityError>>;
fn publication_status<'a>(
&'a self,
scope: &'a Self::Scope,
operation_id: &'a str,
) -> CapabilityFuture<'a, Result<PublicationStatus<Self::PublishedHandle>, CapabilityError>>;
fn release<'a>(
&'a self,
scope: &'a Self::Scope,
handle: &'a Self::PublishedHandle,
) -> CapabilityFuture<'a, Result<(), CapabilityError>>;
fn limits(&self) -> CapabilityLimits;
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Version(Vec<u8>);
impl Version {
pub fn from_bytes(bytes: impl Into<Vec<u8>>) -> Self {
Self(bytes.into())
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StateEntry {
pub payload: Bytes,
pub version: Version,
pub expires_at: Option<SystemTime>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StateWrite {
pub payload: Bytes,
pub expires_at: Option<SystemTime>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
pub enum CasResult {
Applied(Option<Version>),
Conflict,
}
pub trait StateStore {
type Scope;
fn get<'a>(
&'a self,
scope: &'a Self::Scope,
key: &'a str,
) -> CapabilityFuture<'a, Result<Option<StateEntry>, CapabilityError>>;
fn compare_exchange<'a>(
&'a self,
scope: &'a Self::Scope,
key: &'a str,
expected: Option<Version>,
replacement: Option<StateWrite>,
) -> CapabilityFuture<'a, Result<CasResult, CapabilityError>>;
fn limits(&self) -> CapabilityLimits;
}