use std::error::Error;
use std::fmt;
use std::future::Future;
use crate::{ComponentStateEnvelope, FailureCategory, StopToken};
#[derive(Clone, Copy, Debug)]
pub struct StreamOpenContext<'a> {
inherited: Option<&'a ComponentStateEnvelope>,
stop: &'a StopToken,
}
impl<'a> StreamOpenContext<'a> {
#[must_use]
pub const fn new(inherited: Option<&'a ComponentStateEnvelope>, stop: &'a StopToken) -> Self {
Self { inherited, stop }
}
#[must_use]
pub const fn inherited_state(self) -> Option<&'a ComponentStateEnvelope> {
self.inherited
}
#[must_use]
pub const fn stop_token(self) -> &'a StopToken {
self.stop
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum StreamOpenOutcome {
Restored,
Initial,
}
#[derive(Clone, Copy, Debug)]
pub struct StreamUpdateContext<'a> {
stop: &'a StopToken,
}
impl<'a> StreamUpdateContext<'a> {
#[must_use]
pub const fn new(stop: &'a StopToken) -> Self {
Self { stop }
}
#[must_use]
pub const fn stop_token(self) -> &'a StopToken {
self.stop
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum StreamRuntimeOutcome {
Committed,
Failed,
Stopped,
Unknown,
ResourceBoundary,
}
#[derive(Clone, Copy, Debug)]
pub struct StreamCloseContext<'a> {
stop: &'a StopToken,
outcome: StreamRuntimeOutcome,
}
impl<'a> StreamCloseContext<'a> {
#[must_use]
pub const fn new(stop: &'a StopToken, outcome: StreamRuntimeOutcome) -> Self {
Self { stop, outcome }
}
#[must_use]
pub const fn stop_token(self) -> &'a StopToken {
self.stop
}
#[must_use]
pub const fn outcome(self) -> StreamRuntimeOutcome {
self.outcome
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum StreamCloseOutcome {
Closed,
}
macro_rules! stream_error {
($name:ident, $message:literal) => {
#[doc = $message]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct $name {
category: FailureCategory,
}
impl $name {
#[must_use]
pub const fn new() -> Self {
Self {
category: FailureCategory::UserComponent,
}
}
#[must_use]
pub const fn with_category(category: FailureCategory) -> Self {
Self { category }
}
#[must_use]
pub fn from_error(error: impl Error + Send + Sync + 'static) -> Self {
drop(error);
Self::new()
}
#[must_use]
pub const fn category(self) -> FailureCategory {
self.category
}
}
impl Default for $name {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for $name {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str($message)
}
}
impl Error for $name {}
};
}
stream_error!(StreamOpenError, "item stream open failed");
stream_error!(StreamUpdateError, "item stream update failed");
stream_error!(StreamCloseError, "item stream close failed");
#[diagnostic::on_unimplemented(
message = "`{Self}` is not an OxideBatch `ItemStream`",
label = "this component cannot participate in the ItemStream contract",
note = "implement `ItemStream` with `async fn open`, `async fn update`, and `async fn close`",
note = "the returned futures must be `Send`: do not hold a non-`Send` value across an await"
)]
pub trait ItemStream: Send + Sync {
fn open<'a>(
&'a self,
context: StreamOpenContext<'a>,
) -> impl Future<Output = Result<StreamOpenOutcome, StreamOpenError>> + Send + 'a;
fn update<'a>(
&'a self,
context: StreamUpdateContext<'a>,
) -> impl Future<Output = Result<ComponentStateEnvelope, StreamUpdateError>> + Send + 'a;
fn close<'a>(
&'a self,
context: StreamCloseContext<'a>,
) -> impl Future<Output = Result<StreamCloseOutcome, StreamCloseError>> + Send + 'a;
}
mod sealed {
use super::{
ItemStream, StreamCloseContext, StreamCloseError, StreamCloseOutcome, StreamOpenContext,
StreamOpenError, StreamOpenOutcome, StreamUpdateContext, StreamUpdateError,
};
use crate::{BoxFuture, ComponentStateEnvelope};
pub trait StreamObject: Send + Sync {
fn open_boxed<'a>(
&'a self,
context: StreamOpenContext<'a>,
) -> BoxFuture<'a, Result<StreamOpenOutcome, StreamOpenError>>;
fn update_boxed<'a>(
&'a self,
context: StreamUpdateContext<'a>,
) -> BoxFuture<'a, Result<ComponentStateEnvelope, StreamUpdateError>>;
fn close_boxed<'a>(
&'a self,
context: StreamCloseContext<'a>,
) -> BoxFuture<'a, Result<StreamCloseOutcome, StreamCloseError>>;
}
impl<S: ItemStream> StreamObject for S {
fn open_boxed<'a>(
&'a self,
context: StreamOpenContext<'a>,
) -> BoxFuture<'a, Result<StreamOpenOutcome, StreamOpenError>> {
Box::pin(self.open(context))
}
fn update_boxed<'a>(
&'a self,
context: StreamUpdateContext<'a>,
) -> BoxFuture<'a, Result<ComponentStateEnvelope, StreamUpdateError>> {
Box::pin(self.update(context))
}
fn close_boxed<'a>(
&'a self,
context: StreamCloseContext<'a>,
) -> BoxFuture<'a, Result<StreamCloseOutcome, StreamCloseError>> {
Box::pin(self.close(context))
}
}
}
pub struct BoxedStream(Box<dyn sealed::StreamObject>);
impl BoxedStream {
pub fn new<S: ItemStream + 'static>(stream: S) -> Self {
Self(Box::new(stream))
}
}
impl ItemStream for BoxedStream {
fn open<'a>(
&'a self,
context: StreamOpenContext<'a>,
) -> impl Future<Output = Result<StreamOpenOutcome, StreamOpenError>> + Send + 'a {
self.0.open_boxed(context)
}
fn update<'a>(
&'a self,
context: StreamUpdateContext<'a>,
) -> impl Future<Output = Result<ComponentStateEnvelope, StreamUpdateError>> + Send + 'a {
self.0.update_boxed(context)
}
fn close<'a>(
&'a self,
context: StreamCloseContext<'a>,
) -> impl Future<Output = Result<StreamCloseOutcome, StreamCloseError>> + Send + 'a {
self.0.close_boxed(context)
}
}